Compare commits

...

38 Commits

Author SHA1 Message Date
Andrew Kushnir de1e35b944 release: cut the v13.2.0 release (#44838) 2022-01-26 11:44:41 -08:00
Dylan Hunn a38ae0aefa refactor(forms): Temporarily disable the typed forms migration. (#44828)
It's best to disable this optional migration in the minor release, although it shouldn't run anyway -- we don't want people accidentally running it.

This PR will be rolled back after the minor is out.

PR Close #44828
2022-01-25 17:03:48 -08:00
Andrew Scott fe3e4d6865 fix(compiler-cli): Handle ng-template with structural directive in indexer (#44788)
An `ng-template` with an inline template (i.e. has a structural
directive) would previously not get an `undefined` `tagName` because the
logic assumed the element would be `t.Element` or `t.Content` and read
the tag name from the `name` property. For a `t.Template`, this exists
instead on the `t.tagName`. The final result would be an `tagName` of `undefined`
for the parent `t.Template`, causing failures in the indexer downstream.

This `undefined` value is actually expected in the renderer code, even
though the type does not specify this possibility. This change updates
the type of `tagName` to be `string|null` and explicitly handles the
case where there is a structural directive on an `ng-template`. You can
see how the two are differentiated in the compliance code that was
modified in this commit.

PR Close #44788
2022-01-25 14:15:45 -08:00
George Kalpakas 6cb7c3ebe3 build(docs-infra): update dgeni-packages to v0.29.3 (#44810)
This version includes angular/dgeni-packages#318 and thus fixes the
rendering of overridden methods in API docs.

Fixes #44468

PR Close #44810
2022-01-25 09:24:41 -08:00
Renovate Bot 966343bb92 build: update angular (#44771)
PR Close #44771
2022-01-25 09:24:00 -08:00
Tim Bowersox 7e93513425 docs: add link to AbstractControlOptions reference (#44764)
PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox 7c7a1a1724 docs: exclude setValue from async-validator-usage docregion (#44764)
Co-authored-by: Andrew Kushnir <43554145+AndrewKushnir@users.noreply.github.com>
PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox 62217efb7e docs: add missing return statement to validate() (#44764)
PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox 1fa14e0c84 docs: remove trailing space in code example (#44764)
PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox a71eb4c18e docs: fix UniqueAlterEgoValidatorDirective (#44764)
PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox ffd62b9f11 docs: expand async validators in reactive forms (#44764)
* Hone docregions for code examples
* Add more detailed description

PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox e545dd8130 docs: revise text for adding async validators to template-driven forms (#44764)
Co-authored-by: Andrew Kushnir <43554145+AndrewKushnir@users.noreply.github.com>
PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox 4883b7d9c7 docs: restore UniqueAlterEgoValidator (#44764)
Update the docregion for the directive class to differentiate them

PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox 8eb1f76341 docs: fix UniqueAlterEgoValidatorDirective (#44764)
* Use correct class name in providers
* Update parameter & return signature for validate()
PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox 37bbd97d6e docs: update async validators section (#44764)
* Update the UniqueAlterEgoValidator code example
* Add async-validator docregion to hero-form-reactive.component.2.ts
* Fix typo under Implementing a custom async validator
* Add h3 sections for adding async validators to reactive & template-driven forms
PR Close #44764
2022-01-24 14:53:17 -08:00
Tim Bowersox 9e9e82d7c7 docs: add note about using NG_ASYNC_VALIDATORS (#44764)
When setting up an async validator in a template-driven form, it's necessary to register the directive with NG_ASYNC_VALIDATORS instead of NG_VALIDATORS. This was not mentioned in the docs.
PR Close #44764
2022-01-24 14:53:17 -08:00
Jessica Janiuk e12dfa535b test(animations): Add bundling symbol test for animations package (#44809)
This adds a full bundling animation symbols test to the test suite.

PR Close #44809
2022-01-24 14:52:31 -08:00
iRealNirmal fa7c7afbe9 refactor(forms): update required validator and checkbox validator to inherit abstractValidator (#44162)
Modified required validator and checkbox validator to inherit abstractValidator.

For every validato type different PR will be raised as discussed in #42378.

Closes #42267

PR Close #44162
2022-01-24 14:50:58 -08:00
Kristiyan Kostadinov a4aa9b3c28 Revert "feat(core): allow for injector to be specified when creating an embedded view (#44666)" (#44807)
This reverts commit b49ffcd50e.

PR Close #44807
2022-01-24 12:22:22 -08:00
Kristiyan Kostadinov 40f27a31d2 Revert "feat(common): add injector input to ngTemplateOutlet (#44761)" (#44807)
This reverts commit ed21f5c753.

PR Close #44807
2022-01-24 12:22:22 -08:00
Kristiyan Kostadinov 94bfcdd9de fix(core): error if NgZone.isInAngularZone is called with a noop zone (#44800)
When the user opts into the noop `NgZone`, they usually still interact with the static methods on the non-noop class. This change adds a check to handle the case where zone.js hasn't been loaded.

Fixes #44784.

PR Close #44800
2022-01-24 10:44:44 -08:00
Wiley Marques f0a8303abe docs(router): fixing pathMatch doc to style code correctly (#44796)
PR Close #44796
2022-01-24 10:42:52 -08:00
Andrew Scott abd1bc8039 fix(compiler): correct spans when parsing bindings with comments (#44785)
The previous fix for correcting spans with comments in
https://github.com/angular/angular/commit/59eef29a6c5d568ca80595cd7018e21ad406c85d
had the unfortunate side effect of _breaking_ the spans with comments
when there was leading whitespace. This happened because the previous
fix was testing one without a comment, identifying that the offset shouldn't
have anything added to it, and then removing that offset adjustment
(`offsets[i] + (expressionText.length - sourceToLex.length)`).

Upon further investigation, this offset adjustment _was actually
necessary_ for when the input had comments, but this was only because
the `stripComments` function used `trim` to remove whitespace for these
cases. This is the real problem -- not only does it create a ton of confusion
but also it means that the behavior of the lexer and resulting spans is
different between inputs with comments and inputs without comments.

After reviewing how the `inputLength` of `_ParseAST` was used, it
appears that the correct behavior would be to _not_ trim the input. The
`inputLength` is used to advance the current index beyond points which
have been processed. This _should_ include any whitespace. Additionally,
`inputLength` doesn't appear to be needed at all. When there was no
comment in the input, it was always equal to the `input.length` anyways.
When there _is_ a comment, it should include that comment anyways to
advance the index beyond the comment.

PR Close #44785
2022-01-24 10:41:54 -08:00
Andrew Scott 7316e72ec5 fix(compiler-cli): properly index <svg> elements when on a template (#44785)
The original fix for svg elements in
https://github.com/angular/angular/commit/92b23f48519a1adb460a431c356bcd9917fb059d
did not account for svg elements when they also had a structural
directive on them, making the node a template. This resulted in the
logic added in fix above not being applied.

PR Close #44785
2022-01-24 10:41:54 -08:00
Doug Parker ac2f262e9a docs: add extended diagnostics documentation (#44704)
This includes the initial documentation for extended diagnostics with a page for each of the two initial checks. They follow the same general formula, and hopefully in the future they can be properly generated from metadata rather than copy-pasted as they are currently.

PR Close #44704
2022-01-24 10:41:13 -08:00
George Kalpakas f2fa67eb0b fix(docs-infra): track error docs during serve-and-sync (#44704)
Refs #42966.

Look for changes in error docs (i.e. docs under `aio/content/error/`) in
`authors-package`, so that such docs are tracked when running the
`serve-and-sync` script.

PR Close #44704
2022-01-24 10:41:13 -08:00
George Kalpakas 9bee561956 fix(docs-infra): track error docs during serve-and-sync (#44704)
Refs #42966.

Look for changes in error docs (i.e. docs under `aio/content/error/`) in
`authors-package`, so that such docs are tracked when running the
`serve-and-sync` script.

PR Close #44704
2022-01-24 10:41:13 -08:00
Andrew Kushnir fce521e4d2 release: cut the v13.2.0-rc.1 release (#44804) 2022-01-24 09:41:52 -08:00
Oleg Postoev ade305757b refactor(common): unused return value from attemptFocus (#44457)
The function attemptFocus had a return value that nowhere is used. But it still saves in the bundle.

PR Close #44457
2022-01-21 13:11:32 -08:00
JoostK ed67a074ce fix(compiler): properly compile DI factories when coverage reporting is enabled (#44732)
When running tests with code coverage using Istanbul, the code is
instrumented with coverage reporting statements. These statements are
also inserted into synthesized constructors, preventing Angular from
properly recognizing them as synthesized constructor.

This commit changes the regex to detect synthesized constructors to allow
for statements within the constructor before the `super(...arguments);`
call. This is limited to code that does not contain a `}`, but this
is sufficient to support Istanbul's coverage instrumentation statements.

The tests have been extended with an input file that is being
instrumented using `babel-plugin-istanbul` for both ES2015 and ES5
targets, in order to verify that the approach works for real-world
usages.

Fixes #31337

PR Close #44732
2022-01-21 13:10:43 -08:00
Renovate Bot 43eb934e6b build: update all non-major dependencies (#44713)
PR Close #44713
2022-01-21 11:21:52 -08:00
Douglas Parker d2ae96f742 fix(compiler-cli): skip ExtendedTemplateCheckerImpl construction if there were configuration errors (#44778)
Previously, if a bad extended diagnostic category was given, it would fail with the expected error as well as an unexpected assertion error:

```
$ ng build -c development
✔ Browser application bundle generation complete.

./src/main.ts - Error: Module build failed (from ./node_modules/@ngtools/webpack/src/ivy/index.js):
Error: Unexpected call to 'assertNever()' with value:
test
    at /home/douglasparker/Source/ng-new/node_modules/@ngtools/webpack/src/ivy/loader.js:77:18
    at processTicksAndRejections (internal/process/task_queues.js:95:5)

./src/polyfills.ts - Error: Module build failed (from ./node_modules/@ngtools/webpack/src/ivy/index.js):
Error: Unexpected call to 'assertNever()' with value:
test
    at /home/douglasparker/Source/ng-new/node_modules/@ngtools/webpack/src/ivy/loader.js:77:18
    at processTicksAndRejections (internal/process/task_queues.js:95:5)

Error: error NG4004: Angular compiler option "extendedDiagnostics.checks['invalidBananaInBox']" has an unknown diagnostic category: "test".

Allowed diagnostic categories are:
warning
error
suppress
```

The assertion comes from `ExtendedTemplateCheckerImpl`, which expects a well-formed configuration, yet the compiler would construct it even when errors were found. This commit skips constructing and running extended diagnostics if the configuration had errors, which should avoid triggering these assertion errors.

I'm unfortunately not able to actually test this change. The test passes even before the fix because the `ngc` binary and end-to-end tests [don't request diagnostics unless the configuration is considered valid](https://github.com/angular/angular/blob/ed21f5c75378e1ce717ee3d76d28c8c994209de1/packages/compiler-cli/src/perform_compile.ts#L292-L293). See [Slack](https://angular-team.slack.com/archives/C4WHZQMRA/p1642641305003800) for more details.

PR Close #44778
2022-01-21 11:20:48 -08:00
Andrew Scott 5626b34264 fix(core): consistently use namespace short name rather than URI (#44766)
`Renderer2` APIs expect to be called with the namespace name rather than
the namespace URI. Rather than passing around the URI and having to
account for different calling contexts, this change consistently uses
the namespace short names.

Importantly, the URI was only used in `component_ref.ts` `create`
(because `getNamespace returned the URIs`) and `createElementNode` in
`node_manipulation.ts` (because `getNamespaceUri` also used the URIs).
In contrast, attributes would use the _short names instead of URIs_
(see `setUpAttributes` in `attrs_utils.ts`). These names are pulled
directly from the attribute, i.e. `xhtml:href` and not converted to URI.
This dichotomy is confusing and unnecessary. The change here aligns the two
approaches in order to provide consistently throughout the system.

This relates to #44766 because the `createElementNode` was calling the
`AnimationRenderer.createElement` which delegates to the
`ServerRenderer`, which in turn was only set up to expect short names.
As a result, the `NAMESPACE_URIS` lookup failed and `Domino` created
the `svg` as a regular `Element` which does not have a `styles`
property.

resolves #44766

PR Close #44766
2022-01-21 11:19:31 -08:00
Andrew Scott 1671212a55 test: Update test to not declare component in multiple modules (#44766)
When running locally, these integration tests appear to fail because the
component is declared in many test modules.

PR Close #44766
2022-01-21 11:19:31 -08:00
Doug Parker 100091ebf0 fix(compiler-cli): remove leftover _extendedTemplateDiagnostics requirements (#44777)
Refs #42966.

There were two remaining places where `_extendedTemplateDiagnostics` needed to be set which should have been removed in #44712 but got missed. This updates them to only require `strictTemplates` and not `_extendedTemplateDiagnostics` so the feature is properly enabled in production.

PR Close #44777
2022-01-20 14:03:46 -08:00
Martin Probst b7ff6f06de refactor(compiler): pass rootDir to tsickle (#44768)
tsickle's underlying API has changed to require passing a rootDir to getGeneratedExterns.
PR Close #44768
2022-01-20 11:16:36 -08:00
Stephanie Tuerk 6b75263303 docs: correct (possible) typo (#44759)
change 'reference' to 'referencing' -- I believe this is is a typo and that this is the proper correction.
PR Close #44759
2022-01-20 09:22:37 -08:00
Dylan Hunn caeb162513 release: cut the v13.2.0-rc.0 release (#44765) 2022-01-19 17:36:11 -08:00
83 changed files with 3818 additions and 1765 deletions
+1
View File
@@ -1271,6 +1271,7 @@ groups:
'docs/NAMING.md',
'aio/content/guide/angular-package-format.md',
'aio/content/errors/*.md',
'aio/content/extended-diagnostics/*.md',
'aio/content/guide/glossary.md',
'aio/content/guide/styleguide.md',
'aio/content/examples/errors/**',
+65 -61
View File
@@ -1,3 +1,68 @@
<a name="13.2.0"></a>
# 13.2.0 (2022-01-26)
## Deprecations
###
- The `CachedResourceLoader` and `RESOURCE_CACHE_PROVIDER` symbols were previously necessary in some cases to test AOT-compiled components with View Engine, but they are no longer needed since Ivy.
- The `ComponentFactory` and `ComponentFactoryResolver` classes are deprecated. Since Ivy, there is no need to resolve Component factories. Please use other APIs where you Component classes can be used directly (without resolving their factories).
- Since Ivy, the CompilerOptions.useJit and CompilerOptions.missingTranslation config options are unused, passing them has no effect.
- tap operator subscribe signature is deprecated
Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments
###
| Commit | Type | Description |
| -- | -- | -- |
| [9c11183e74](https://github.com/angular/angular/commit/9c11183e74980b12c3c5712df174e90af6f19027) | docs | deprecate `CachedResourceLoader` and `RESOURCE_CACHE_PROVIDER` symbols ([#44749](https://github.com/angular/angular/pull/44749)) |
| [9f12e7fea4](https://github.com/angular/angular/commit/9f12e7fea434c6d33e1155994e2c228d51520744) | docs | deprecate `ComponentFactory` and `ComponentFactoryResolver` symbols ([#44749](https://github.com/angular/angular/pull/44749)) |
| [4e95a316ce](https://github.com/angular/angular/commit/4e95a316cef35a771cd8168e3a744eb6bd7f1615) | docs | deprecate unused config options from the `CompilerOptions` interface ([#44749](https://github.com/angular/angular/pull/44749)) |
| [34d2292323](https://github.com/angular/angular/commit/34d2292323a5b5c0751a5f2a3c85ccfe4427bfb5) | docs | update deprecated form of the tap operator in http example ([#44738](https://github.com/angular/angular/pull/44738)) |
### common
| Commit | Type | Description |
| -- | -- | -- |
| [ed21f5c753](https://github.com/angular/angular/commit/ed21f5c75378e1ce717ee3d76d28c8c994209de1) | feat | add injector input to ngTemplateOutlet ([#44761](https://github.com/angular/angular/pull/44761)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [a4ab6d6b72](https://github.com/angular/angular/commit/a4ab6d6b72fe891f232d2446f8b7c454a82a5ba0) | feat | add support for safe calls in templates ([#44580](https://github.com/angular/angular/pull/44580)) |
| [abd1bc8039](https://github.com/angular/angular/commit/abd1bc8039ade4324f91fd616c82e52f05bdbb70) | fix | correct spans when parsing bindings with comments ([#44785](https://github.com/angular/angular/pull/44785)) |
| [ed67a074ce](https://github.com/angular/angular/commit/ed67a074ceed6440bf7f638512a20bbd3be8cbd4) | fix | properly compile DI factories when coverage reporting is enabled ([#44732](https://github.com/angular/angular/pull/44732)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [fa835b5a29](https://github.com/angular/angular/commit/fa835b5a297087087eac2750b662096ed5492cb6) | feat | enable extended diagnostics by default ([#44712](https://github.com/angular/angular/pull/44712)) |
| [73424def13](https://github.com/angular/angular/commit/73424def130ae1d72036142f6f2d7bce63858c9e) | feat | provide the animations for `DirectiveMeta` ([#44630](https://github.com/angular/angular/pull/44630)) |
| [fe3e4d6865](https://github.com/angular/angular/commit/fe3e4d6865c1e760be0511cfd757b4574a67fa5b) | fix | Handle `ng-template` with structural directive in indexer ([#44788](https://github.com/angular/angular/pull/44788)) |
| [7316e72ec5](https://github.com/angular/angular/commit/7316e72ec5ccc475e610bf26b5da451b8ffa50a2) | fix | properly index <svg> elements when on a template ([#44785](https://github.com/angular/angular/pull/44785)) |
| [100091ebf0](https://github.com/angular/angular/commit/100091ebf0ad480e94b5db4f183f9bfcfe8b8274) | fix | remove leftover `_extendedTemplateDiagnostics` requirements ([#44777](https://github.com/angular/angular/pull/44777)) |
| [d2ae96f742](https://github.com/angular/angular/commit/d2ae96f742c82daa6c4d4b8afd3b0959fb9b89d1) | fix | skip `ExtendedTemplateCheckerImpl` construction if there were configuration errors ([#44778](https://github.com/angular/angular/pull/44778)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [9921b2ca35](https://github.com/angular/angular/commit/9921b2ca35f774c2d80317aeeaabf02a6942daa6) | feat | Add a migration for typed forms. ([#44449](https://github.com/angular/angular/pull/44449)) |
| [b49ffcd50e](https://github.com/angular/angular/commit/b49ffcd50e46ca7907ee2049f6cade8e080f5980) | feat | allow for injector to be specified when creating an embedded view ([#44666](https://github.com/angular/angular/pull/44666)) |
| [5626b34264](https://github.com/angular/angular/commit/5626b34264d8893fece74d30db4920a685f3b17f) | fix | consistently use namespace short name rather than URI ([#44766](https://github.com/angular/angular/pull/44766)) |
| [94bfcdd9de](https://github.com/angular/angular/commit/94bfcdd9de234f7189daaa06a6c179d9697bef8d) | fix | error if NgZone.isInAngularZone is called with a noop zone ([#44800](https://github.com/angular/angular/pull/44800)) |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [5ae1030462](https://github.com/angular/angular/commit/5ae1030462a97bda8357415cbfcd34301dfa6499) | feat | Add an unused symbol `AnyForUntypedForms`. ([#44467](https://github.com/angular/angular/pull/44467)) |
| [72092ebd26](https://github.com/angular/angular/commit/72092ebd26d6670a7dd02f6db5515d2afd27c4d6) | feat | Allow a FormControl to use initial value as default. ([#44434](https://github.com/angular/angular/pull/44434)) |
| [f7aa937cac](https://github.com/angular/angular/commit/f7aa937cac0ad315e1012770aa1da97476aced54) | fix | Make some minor fixups for forward-compatibility with typed forms. ([#44540](https://github.com/angular/angular/pull/44540)) |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [af2a1317cb](https://github.com/angular/angular/commit/af2a1317cbfba985233f510bbe9e49016968307c) | feat | support completions for animation ([#44630](https://github.com/angular/angular/pull/44630)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [5a4ddfd4f5](https://github.com/angular/angular/commit/5a4ddfd4f5e17bb4209528cefc3f0674298ab6e8) | feat | Allow symbol keys for `Route` `data` and `resolve` properties ([#44519](https://github.com/angular/angular/pull/44519)) |
## Special Thanks
Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dario Piotrowicz, Derek Cormier, Doug Parker, Douglas Parker, Dylan Hunn, George Kalpakas, Jessica Janiuk, JoostK, Kristiyan Kostadinov, Martin Probst, Oleg Postoev, Stephanie Tuerk, Tim Bowersox, Wiley Marques, Yousaf Nawaz, dario-piotrowicz, iRealNirmal, ivanwonder and shejialuo
<!-- CHANGELOG SPLIT MARKER -->
<a name="13.1.3"></a>
# 13.1.3 (2022-01-19)
### animations
@@ -17,44 +82,6 @@ Alan Agius, Andrew Kushnir, AnkitSharma-007, Daniel Díaz, Dmytro Mezhenskyi, Je
<!-- CHANGELOG SPLIT MARKER -->
<a name="13.2.0-next.2"></a>
# 13.2.0-next.2 (2022-01-12)
### animations
| Commit | Type | Description |
| -- | -- | -- |
| [8ba43a1a1e](https://github.com/angular/angular/commit/8ba43a1a1ea811ad25d6af4384e1cce5a53b0baf) | fix | retain triggers values for moved tracked list items ([#44578](https://github.com/angular/angular/pull/44578)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [a4ab6d6b72](https://github.com/angular/angular/commit/a4ab6d6b72fe891f232d2446f8b7c454a82a5ba0) | feat | add support for safe calls in templates ([#44580](https://github.com/angular/angular/pull/44580)) |
| [b8ed03b5ad](https://github.com/angular/angular/commit/b8ed03b5ad91a2dd62d600240e9056a360b89b90) | fix | correct spans when parsing bindings with comments ([#44678](https://github.com/angular/angular/pull/44678)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [73424def13](https://github.com/angular/angular/commit/73424def130ae1d72036142f6f2d7bce63858c9e) | feat | provide the animations for `DirectiveMeta` ([#44630](https://github.com/angular/angular/pull/44630)) |
| [1a9121826e](https://github.com/angular/angular/commit/1a9121826ea94d98d0603d65628d4b4b08f2f70b) | fix | enable narrowing of using type guard methods ([#44447](https://github.com/angular/angular/pull/44447)) |
| [f8af49eb75](https://github.com/angular/angular/commit/f8af49eb751cabab76c6c7327f377a163f841dc4) | fix | fix crash during type-checking of library builds ([#44587](https://github.com/angular/angular/pull/44587)) |
| [f83fb3a5f9](https://github.com/angular/angular/commit/f83fb3a5f9b146cb5025517950100e98895304ed) | fix | handle property reads of `ThisReceiver` in the indexer ([#44678](https://github.com/angular/angular/pull/44678)) |
| [eeaabe7fbf](https://github.com/angular/angular/commit/eeaabe7fbf7a1cf057c6b740609886415e4037eb) | fix | incorrectly interpreting $any calls with a property read ([#44657](https://github.com/angular/angular/pull/44657)) |
| [92b23f4851](https://github.com/angular/angular/commit/92b23f48519a1adb460a431c356bcd9917fb059d) | fix | properly index <svg> elements ([#44678](https://github.com/angular/angular/pull/44678)) |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [5ae1030462](https://github.com/angular/angular/commit/5ae1030462a97bda8357415cbfcd34301dfa6499) | feat | Add an unused symbol `AnyForUntypedForms`. ([#44467](https://github.com/angular/angular/pull/44467)) |
| [f7aa937cac](https://github.com/angular/angular/commit/f7aa937cac0ad315e1012770aa1da97476aced54) | fix | Make some minor fixups for forward-compatibility with typed forms. ([#44540](https://github.com/angular/angular/pull/44540)) |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [af2a1317cb](https://github.com/angular/angular/commit/af2a1317cbfba985233f510bbe9e49016968307c) | feat | support completions for animation ([#44630](https://github.com/angular/angular/pull/44630)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [5a4ddfd4f5](https://github.com/angular/angular/commit/5a4ddfd4f5e17bb4209528cefc3f0674298ab6e8) | feat | Allow symbol keys for `Route` `data` and `resolve` properties ([#44519](https://github.com/angular/angular/pull/44519)) |
## Special Thanks
Abdurrahman Abu-Hijleh, Adam Plumer, Alex Rickabaugh, AlirezaEbrahimkhani, Andrew Kushnir, Andrew Scott, Borja Paz Rodríguez, Chihab Otmani, Chris Mancini, Dario Piotrowicz, Doug Parker, Dylan Hunn, George Kalpakas, Joey Perrott, JoostK, Kristiyan Kostadinov, Kyoz, Patrick Prakash, Paul Gschwendtner, Serhey Dolgushev, Yousaf Nawaz, Yuchao Wu, alkavats1, dario-piotrowicz, huangqing, ivanwonder, shejialuo, twerske, wszgrcy and zuckjet
<!-- CHANGELOG SPLIT MARKER -->
<a name="13.1.2"></a>
# 13.1.2 (2022-01-12)
### animations
@@ -82,22 +109,6 @@ Abdurrahman Abu-Hijleh, Adam Plumer, Alex Rickabaugh, AlirezaEbrahimkhani, Andre
<!-- CHANGELOG SPLIT MARKER -->
<a name="13.2.0-next.1"></a>
# 13.2.0-next.1 (2021-12-15)
### animations
| Commit | Type | Description |
| -- | -- | -- |
| [38ddae1c2e](https://github.com/angular/angular/commit/38ddae1c2e0de87d5672fda30d4fbe567dab4123) | fix | don't consume instructions for animateChild ([#44357](https://github.com/angular/angular/pull/44357)) |
| [bab7ed3541](https://github.com/angular/angular/commit/bab7ed35418173714509e06ba8b092aff28fff89) | fix | should not invoke disabled child animations ([#37724](https://github.com/angular/angular/pull/37724)) |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [72092ebd26](https://github.com/angular/angular/commit/72092ebd26d6670a7dd02f6db5515d2afd27c4d6) | feat | Allow a FormControl to use initial value as default. ([#44434](https://github.com/angular/angular/pull/44434)) |
## Special Thanks
Alex Rickabaugh, Aristeidis Bampakos, Dylan Hunn, JoostK, Kristiyan Kostadinov, Paul Gschwendtner, Spej, Yousaf Nawaz, dario-piotrowicz, faso-dev, jaybell and zuckjet
<!-- CHANGELOG SPLIT MARKER -->
<a name="13.1.1"></a>
# 13.1.1 (2021-12-15)
### animations
@@ -115,13 +126,6 @@ Aristeidis Bampakos, Dylan Hunn, George Kalpakas, JoostK, Kristiyan Kostadinov,
<!-- CHANGELOG SPLIT MARKER -->
<a name="13.2.0-next.0"></a>
# 13.2.0-next.0 (2021-12-09)
There have been no changes since the previous `13.1.0` release.
<!-- CHANGELOG SPLIT MARKER -->
<a name="13.1.0"></a>
# 13.1.0 (2021-12-09)
## Deprecations
@@ -19,16 +19,21 @@ export class HeroFormReactiveComponent implements OnInit {
heroForm: FormGroup;
ngOnInit(): void {
// #docregion async-validator-usage
const alterEgoControl = new FormControl('', {
asyncValidators: [this.alterEgoValidator.validate.bind(this.alterEgoValidator)],
updateOn: 'blur'
});
// #enddocregion async-validator-usage
alterEgoControl.setValue(this.hero.alterEgo);
this.heroForm = new FormGroup({
name: new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i)
]),
alterEgo: new FormControl(this.hero.alterEgo, {
asyncValidators: [this.alterEgoValidator.validate.bind(this.alterEgoValidator)],
updateOn: 'blur'
}),
alterEgo: alterEgoControl,
power: new FormControl(this.hero.power, Validators.required)
});
}
@@ -39,5 +44,7 @@ export class HeroFormReactiveComponent implements OnInit {
get alterEgo() { return this.heroForm.get('alterEgo'); }
// #docregion async-validator-inject
constructor(private alterEgoValidator: UniqueAlterEgoValidator) {}
// #enddocregion async-validator-inject
}
@@ -15,9 +15,9 @@ export class UniqueAlterEgoValidator implements AsyncValidator {
constructor(private heroesService: HeroesService) {}
validate(
ctrl: AbstractControl
): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return this.heroesService.isAlterEgoTaken(ctrl.value).pipe(
control: AbstractControl
): Observable<ValidationErrors | null> {
return this.heroesService.isAlterEgoTaken(control.value).pipe(
map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)),
catchError(() => of(null))
);
@@ -25,20 +25,24 @@ export class UniqueAlterEgoValidator implements AsyncValidator {
}
// #enddocregion async-validator
// #docregion async-validator-directive
@Directive({
selector: '[appUniqueAlterEgo]',
providers: [
{
provide: NG_ASYNC_VALIDATORS,
useExisting: forwardRef(() => UniqueAlterEgoValidator),
useExisting: forwardRef(() => UniqueAlterEgoValidatorDirective),
multi: true
}
]
})
export class UniqueAlterEgoValidatorDirective {
export class UniqueAlterEgoValidatorDirective implements AsyncValidator {
constructor(private validator: UniqueAlterEgoValidator) {}
validate(control: AbstractControl) {
this.validator.validate(control);
validate(
control: AbstractControl
): Observable<ValidationErrors | null> {
return this.validator.validate(control);
}
}
// #enddocregion async-validator-directive
@@ -35,14 +35,15 @@
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text"
<!-- #docregion alterEgo-input -->
<input type="text"
id="alterEgo"
name="alterEgo"
#alterEgo="ngModel"
[(ngModel)]="hero.alterEgo"
[ngModelOptions]="{ updateOn: 'blur' }"
appUniqueAlterEgo>
<!-- #enddocregion alterEgo-input -->
<div *ngIf="alterEgo.pending">Validating...</div>
<div *ngIf="alterEgo.invalid" class="alert alter-ego-errors">
<div *ngIf="alterEgo.errors?.['uniqueAlterEgo']">
@@ -0,0 +1,47 @@
@name Invalid Banana-in-Box
@description
This diagnostic detects a backwards "banana-in-box" syntax for
[two-way bindings](guide/two-way-binding).
```html
<div ([var])="otherVar"></div>
```
## What's wrong with that?
As it stands, `([var])` is actually an [event binding](guide/event-binding) with
the name `[var]`. The author likely intended this as a two-way binding to a variable named
`var` but as written this binding has no effect.
## What should I do instead?
Fix the typo. As the name suggests, the banana `(` should go *inside* the box `[]`. In this case:
```html
<div [(var)]="otherVar"></div>
```
## Configuration requirements
[`strictTemplates`](/guide/template-typecheck#strict-mode) must be enabled for any extended
diagnostic to emit. `invalidBananaInBox` has no additional requirements beyond `strictTemplates`.
## What if I can't avoid this?
This diagnostic can be disabled by editing the project's `tsconfig.json` file:
```json
{
"angularCompilerOptions": {
"extendedDiagnostics": {
"checks": {
"invalidBananaInBox": "suppress"
}
}
}
}
```
See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.
@@ -0,0 +1,91 @@
@name Nullish coalescing not nullable
@description
This diagnostic detects a useless nullish coalescing operator (`??`) in Angular templates.
Specifically, it looks for operations where the input is not "nullable", meaning its type does not
include `null` or `undefined`. For such values, the right side of the `??` will never be used.
```typescript
import {Component} from '@angular/core';
@Component({
// Template displays `foo` if present, falls back to 'bar' if it is `null`
// or `undefined`.
template: `<div>{{ foo ?? 'bar' }}</div>`,
// ...
})
class MyComponent {
// `foo` is declared as a `string` which *cannot* be `null` or `undefined`.
foo: string = 'test';
}
```
## What's wrong with that?
Using the nullish coalescing operator with a non-nullable input has no effect and is indicative of a
discrepancy between the allowed type of a value and how it is presented in the template. A developer
might reasonably assume that the right side of the nullish coalescing operator is displayed in some
case, but it will never actually be displayed. This can lead to confusion about the expected output
of the program.
## What should I do instead?
Update the template and declared type to be in sync. Double check the type of the input and confirm
whether it is actually expected to be nullable.
If the input should be nullable, add `null` or `undefined` to its type to indicate this.
```typescript
import {Component} from '@angular/core';
@Component({
template: `<div>{{ foo ?? 'bar' }}</div>`,
// ...
})
class MyComponent {
// `foo` is now nullable. If it is ever set to `null`, 'bar' will be displayed.
foo: string | null = 'test';
}
```
If the input should *not* be nullable, delete the `??` operator and its right operand, since the
value is guaranteed by TypeScript to always be non-nullable.
```typescript
import {Component} from '@angular/core';
@Component({
// Template always displays `foo`, which is guaranteed to never be `null` or
// `undefined`.
template: `<div>{{ foo }}</div>`,
// ...
})
class MyComponent {
foo: string = 'test';
}
```
## Configuration requirements
[`strictTemplates`](/guide/template-typecheck#strict-mode) must be enabled for any extended
diagnostic to emit. [`strictNullChecks`](/guide/template-typecheck#strict-null-checks) must also be
enabled to emit any `nullishCoalescingNotNullable` diagnostics.
## What if I can't avoid this?
This diagnostic can be disabled by editing the project's `tsconfig.json` file:
```json
{
"angularCompilerOptions": {
"extendedDiagnostics": {
"checks": {
"nullishCoalescingNotNullable": "suppress"
}
}
}
}
```
See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.
+85
View File
@@ -0,0 +1,85 @@
# Extended Diagnostics
There are many coding patterns that are technically valid to the compiler or runtime, but which may
have complex nuances or caveats. These patterns may not have the intended effect expected by a
developer, which often leads to bugs. The Angular compiler includes "extended diagnostics" which
identify many of these patterns to warn developers about the potential issues and enforce common
best practices within a codebase.
## Diagnostics
Currently, Angular supports the following extended diagnostics:
* [NG8101 - `invalidBananaInBox`](extended-diagnostics/NG8101)
* [NG8102 - `nullishCoalescingNotNullable`](extended-diagnostics/NG8102)
## Configuration
Extended diagnostics are warnings by default and do not block compilation. Each diagnostic can be
configured as either:
* `warning` (default) - The compiler emits the diagnostic as a warning but does not block
compilation. The compiler will still exit with status code 0, even if warnings are emitted.
* `error` - The compiler emits the diagnostic as an error and fails the compilation. The compiler
will exit with a non-zero status code if one or more errors are emitted.
* `suppress` - The compiler does *not* emit the diagnostic at all.
Check severity can be configured in the project's `tsconfig.json` file:
```jsonc
{
"angularCompilerOptions": {
"extendedDiagnostics": {
// The categories to use for specific diagnostics.
"checks": {
// Maps check name to its category.
"invalidBananaInBox": "suppress"
},
// The category to use for any diagnostics not listed in `checks` above.
"defaultCategory": "error"
}
}
}
```
The `checks` field maps the name of individual diagnostics to their associated category. See
[Diagnostics](#diagnostics) for a complete list of extended diagnostics and the name to use for
configuring them.
The `defaultCategory` field is used for any diagnostics that are not explicitly listed under
`checks`. If not set, such diagnostics will be treated as `warning`.
Extended diagnostics will emit when [`strictTemplates`](/guide/template-typecheck#strict-mode) is
enabled. This is required to allow the compiler to better understand Angular template types and
provide accurate and meaningful diagnostics.
## Semantic Versioning
The Angular team intends to add or enable new extended diagnostics in **minor** versions of Angular
(see [semver](https://docs.npmjs.com/about-semantic-versioning)). This means that upgrading Angular
may show new warnings in your existing codebase. This enables the team to deliver features more
quickly and to make extended diagnostics more accessible to developers.
However, setting `"defaultCategory": "error"` will promote such warnings to hard errors. This can
cause a minor version upgrade to introduce compilation errors, which may be seen as a semver
non-compliant breaking change. Any new diagnostics can be suppressed or demoted to warnings via the
above [configuration](#configuration), so the impact of a new diagnostic should be minimal to
projects that treat extended diagnostics as errors by default. Defaulting to error is a very
powerful tool; just be aware of this semver caveat when deciding if `error` is the right default for
your project.
## New Diagnostics
The Angular team is always open to suggestions about new diagnostics that could be added. Extended
diagnostics should generally:
* Detect a common, non-obvious developer mistake with Angular templates.
* Clearly articulate why this pattern can lead to bugs or unintended behavior.
* Suggest one or more clear solutions.
* Have a low (preferrably zero) false-positive rate.
* Apply to the vast majority of Angular applications (not specific to an unofficial library).
* Improve program correctness or performance (not style, that responsibility falls to a linter).
If you have an idea for a compiler check which fits these criteria, consider filing a
[feature request](https://github.com/angular/angular/issues/new?template=2-feature-request.yaml).
+25 -1
View File
@@ -304,7 +304,7 @@ In the following example, an async validator ensures that heroes pick an alter e
New heroes are constantly enlisting and old heroes are leaving the service, so the list of available alter egos cannot be retrieved ahead of time.
To validate the potential alter ego entry, the validator must initiate an asynchronous operation to consult a central database of all currently enlisted heroes.
The following code create the validator class, `UniqueAlterEgoValidator`, which implements the `AsyncValidator` interface.
The following code creates the validator class, `UniqueAlterEgoValidator`, which implements the `AsyncValidator` interface.
<code-example path="form-validation/src/app/shared/alter-ego.directive.ts" region="async-validator"></code-example>
@@ -333,6 +333,30 @@ You could handle the error differently and return the `ValidationError` object i
After some time passes, the observable chain completes and the asynchronous validation is done.
The `pending` flag is set to `false`, and the form validity is updated.
### Adding async validators to reactive forms
To use an async validator in reactive forms, begin by injecting the validator into the constructor of the component class.
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.2.ts" region="async-validator-inject"></code-example>
Then, pass the validator function directly to the `FormControl` to apply it.
In the following example, the `validate` function of `UniqueAlterEgoValidator` is applied to `alterEgoControl` by passing it to the control's `asyncValidators` option and binding it to the instance of `UniqueAlterEgoValidator` that was injected into `HeroFormReactiveComponent`. The value of `asyncValidators` can be either a single async validator function, or an array of functions. To learn more about `FormControl` options, see the [AbstractControlOptions](/api/forms/AbstractControlOptions) API reference.
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.2.ts" region="async-validator-usage"></code-example>
### Adding async validators to template-driven forms
To use an async validator in template-driven forms, create a new directive and register the `NG_ASYNC_VALIDATORS` provider on it.
In the example below, the directive injects the `UniqueAlterEgoValidator` class that contains the actual validation logic and invokes it in the `validate` function, triggered by Angular when validation should happen.
<code-example path="form-validation/src/app/shared/alter-ego.directive.ts" region="async-validator-directive"></code-example>
Then, as with synchronous validators, add the directive's selector to an input to activate it.
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="alterEgo-input" header="template/hero-form-template.component.html (unique-alter-ego-input)"></code-example>
### Optimizing performance of async validators
By default, all validators run after every form value change. With synchronous validators, this does not normally have a noticeable impact on application performance.
@@ -46,7 +46,7 @@ In most cases, Angular sets the template variable's value to the element on whic
In the previous example, `phone` refers to the phone number `<input>`.
The button's click handler passes the `<input>` value to the component's `callPhone()` method.
The `NgForm` directive demonstrates getting a reference to a different value by reference a directive's `exportAs` name.
The `NgForm` directive demonstrates getting a reference to a different value by referencing a directive's `exportAs` name.
In the following example, the template variable, `itemForm`, appears three times separated by HTML.
<code-example path="template-reference-variables/src/app/app.component.html" region="ngForm" header="src/app/hero-form.component.html"></code-example>
+12 -1
View File
@@ -1055,6 +1055,17 @@
}
]
},
{
"title": "Extended Diagnostic Reference",
"tooltip": "Details of extended diagnostics that can be emitted by the Angular compiler",
"children": [
{
"url": "extended-diagnostics",
"title": "Extended Diagnostics Overview",
"tooltip": "Overview of extended diagnostics, what they are, and how to configure them."
}
]
},
{
"title": "Example applications",
"tooltip": "List of all of the example applications in the Angular documentation.",
@@ -1247,4 +1258,4 @@
]
}
]
}
}
+18 -18
View File
@@ -85,39 +85,39 @@
},
"private": true,
"dependencies": {
"@angular/animations": "13.1.2",
"@angular/cdk": "13.1.2",
"@angular/common": "13.1.2",
"@angular/compiler": "13.1.2",
"@angular/core": "13.1.2",
"@angular/elements": "13.1.2",
"@angular/forms": "13.1.2",
"@angular/material": "13.1.2",
"@angular/platform-browser": "13.1.2",
"@angular/platform-browser-dynamic": "13.1.2",
"@angular/router": "13.1.2",
"@angular/service-worker": "13.1.2",
"@angular/animations": "13.1.3",
"@angular/cdk": "13.1.3",
"@angular/common": "13.1.3",
"@angular/compiler": "13.1.3",
"@angular/core": "13.1.3",
"@angular/elements": "13.1.3",
"@angular/forms": "13.1.3",
"@angular/material": "13.1.3",
"@angular/platform-browser": "13.1.3",
"@angular/platform-browser-dynamic": "13.1.3",
"@angular/router": "13.1.3",
"@angular/service-worker": "13.1.3",
"rxjs": "~7.4.0",
"safevalues": "^0.1.8",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "13.1.3",
"@angular-devkit/build-angular": "13.1.4",
"@angular-eslint/builder": "^13.0.0",
"@angular-eslint/eslint-plugin": "^13.0.0",
"@angular-eslint/eslint-plugin-template": "^13.0.0",
"@angular-eslint/template-parser": "^13.0.0",
"@angular/cli": "13.1.3",
"@angular/compiler-cli": "13.1.2",
"@angular/cli": "13.1.4",
"@angular/compiler-cli": "13.1.3",
"@types/jasmine": "~3.10.0",
"@types/lunr": "^2.3.3",
"@types/node": "^12.7.9",
"@types/stemmer": "^1.0.2",
"@types/trusted-types": "^2.0.2",
"@types/xregexp": "^4.3.0",
"@typescript-eslint/eslint-plugin": "5.9.1",
"@typescript-eslint/parser": "5.9.1",
"@typescript-eslint/eslint-plugin": "5.10.0",
"@typescript-eslint/parser": "5.10.0",
"@yarnpkg/lockfile": "^1.1.0",
"archiver": "^5.3.0",
"assert": "^2.0.0",
@@ -128,7 +128,7 @@
"cross-spawn": "^7.0.3",
"css-selector-parser": "^1.4.1",
"dgeni": "^0.4.14",
"dgeni-packages": "^0.29.2",
"dgeni-packages": "^0.29.3",
"entities": "^3.0.0",
"eslint": "^8.0.0",
"eslint-plugin-import": "^2.23.4",
+1
View File
@@ -123,6 +123,7 @@
'.folder-cli',
'.folder-docs',
'.folder-errors',
'.folder-extended-diagnostics',
'.folder-guide',
'.folder-start',
'.folder-tutorial',
@@ -0,0 +1,77 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const path = require('canonical-path');
const Package = require('dgeni').Package;
const basePackage = require('../angular-base-package');
const {CONTENTS_PATH, TEMPLATES_PATH} = require('../config');
const contentPackage = require('../content-package');
const extendedDiagnosticsPackage = new Package('angular-extended-diagnostics', [basePackage, contentPackage])
.factory(require('./readers/extended-diagnostic'))
.processor(require('./processors/processExtendedDiagnosticDocs'))
// Where do we find the extended-diagnostic documentation files?
.config(function(extendedDiagnosticFileReader, readFilesProcessor) {
readFilesProcessor.fileReaders.push(extendedDiagnosticFileReader);
readFilesProcessor.sourceFiles = readFilesProcessor.sourceFiles.concat([
{
basePath: CONTENTS_PATH,
include: `${CONTENTS_PATH}/extended-diagnostics/index.md`,
fileReader: 'contentFileReader',
},
{
basePath: CONTENTS_PATH,
include: `${CONTENTS_PATH}/extended-diagnostics/**/*.md`,
exclude: `${CONTENTS_PATH}/extended-diagnostics/index.md`,
fileReader: 'extendedDiagnosticFileReader',
},
]);
})
// Here we compute the `id`, `code`, `aliases`, `path` and `outputPath` for the
// `extended-diagnostic` docs.
// * The `id` is the same as the `path` (the source path with the `.md` stripped off).
// * The `code` is the `id` without any containing paths (currently all extended diagnostics
// must be on the top level).
// * The `aliases` are used for automatic code linking and search terms.
.config(function(computeIdsProcessor, computePathsProcessor) {
computeIdsProcessor.idTemplates.push({
docTypes: ['extended-diagnostic'],
getId: doc => doc.fileInfo.relativePath.replace(/\.\w*$/, ''), // Strip off the extension.
getAliases: doc => {
doc.code = path.basename(doc.id);
return [doc.id, doc.code];
},
});
computePathsProcessor.pathTemplates = computePathsProcessor.pathTemplates.concat([
{
docTypes: ['extended-diagnostic'],
getPath: doc => doc.id,
outputPathTemplate: '${path}.json',
},
]);
})
// The templates that define how the `extended-diagnostic` doc-type is rendered is found in the
// `${TEMPLATES_PATH}/extended-diagnostic/` directory.
.config(function(templateFinder) {
templateFinder.templateFolders.unshift(path.resolve(TEMPLATES_PATH, 'extended-diagnostic'));
})
// The AIO application expects content files to be provided as JSON files that it requests via
// HTTP. So here we tell the `convertToJsonProcessor` to include docs of type
// `extended-diagnostic` in those that it converts.
.config(function(convertToJsonProcessor, postProcessHtml) {
convertToJsonProcessor.docTypes.push('extended-diagnostic');
postProcessHtml.docTypes.push('extended-diagnostic');
});
module.exports = extendedDiagnosticsPackage;
@@ -0,0 +1,60 @@
module.exports = function processExtendedDiagnosticDocs(createDocMessage) {
return {
$runAfter: ['extra-docs-added'],
$runBefore: ['rendering-docs'],
$process(docs) {
const navigationDoc = docs.find(doc => doc.docType === 'navigation-json');
const extendedDiagnosticsNode =
navigationDoc && findExtendedDiagnosticsNode(navigationDoc.data['SideNav']);
if (!extendedDiagnosticsNode) {
throw new Error(createDocMessage(
'Missing `extended-diagnostics` url - ' +
'This node is needed as a place to insert the generated extended diagnostics docs.',
navigationDoc));
}
docs.forEach(doc => {
if (doc.docType === 'extended-diagnostic') {
// Add to navigation doc.
const title = `${doc.code}: ${doc.name}`;
extendedDiagnosticsNode.children.push({title, tooltip: doc.name, url: doc.path});
}
});
},
};
};
/**
* Look for the `extended-diagnostics` navigation node. It is the node whose first child has
* `url: 'extended-diagnostics'`.
* (NOTE: Using the URL instead of the title, because it is more robust.)
*
* We will "recursively" check all navigation nodes and their children (in breadth-first order),
* until we find the `extended-diagnostics` node. Keep a list of nodes lists to check.
* (NOTE: Each item in the list is a LIST of nodes.)
*/
function findExtendedDiagnosticsNode(nodes) {
const nodesList = [nodes];
while (nodesList.length > 0) {
// Get the first item from the list of nodes lists.
const currentNodes = nodesList.shift();
const extendedDiagnosticsNode = currentNodes.find(isExtendedDiagnosticsNode);
// One of the nodes in `currentNodes` was the `extended-diagnostics` node. Return it.
if (extendedDiagnosticsNode) return extendedDiagnosticsNode;
// The `extended-diagnostics` node is not in `currentNodes`. Check each node's children
// (if any).
currentNodes.forEach(node => node.children && nodesList.push(node.children));
}
// We checked all navigation nodes and their children and did not find the `extended-diagnostics`
// node.
return undefined;
}
function isExtendedDiagnosticsNode(node) {
return node.children && node.children.length && node.children[0].url === 'extended-diagnostics';
}
@@ -0,0 +1,132 @@
const Dgeni = require('dgeni');
const testPackage = require('../../helpers/test-package');
describe('processExtendedDiagnosticDocs processor', () => {
let dgeni, injector, processor, createDocMessage;
beforeEach(() => {
dgeni = new Dgeni([testPackage('angular-extended-diagnostics-package')]);
injector = dgeni.configureInjector();
processor = injector.get('processExtendedDiagnosticDocs');
createDocMessage = injector.get('createDocMessage');
});
it('should be available on the injector', () => {
expect(processor.$process).toBeDefined();
});
it('should run after the correct processor', () => {
expect(processor.$runAfter).toEqual(['extra-docs-added']);
});
it('should run before the correct processor', () => {
expect(processor.$runBefore).toEqual(['rendering-docs']);
});
it(
'should add the extended diagnostic to the `extended-diagnostics` node in the navigation doc ' +
'if there is a top level node with an `extended-diagnostics` url',
() => {
const diagnosticDoc = {
docType: 'extended-diagnostic',
code: '1337',
name: 'diagnostic1',
path: 'extended-diagnostics/diagnostic1',
};
const navigation = {
docType: 'navigation-json',
data: {
SideNav: [
{url: 'some/page', title: 'Some Page'},
{
title: 'Extended diagnostics',
children: [{title: 'Overview', url: 'extended-diagnostics'}],
},
{url: 'other/page', title: 'Other Page'},
],
},
};
processor.$process([diagnosticDoc, navigation]);
expect(navigation.data.SideNav[1].title).toBe('Extended diagnostics');
expect(navigation.data.SideNav[1].children).toEqual([
{url: 'extended-diagnostics', title: 'Overview'},
{
url: 'extended-diagnostics/diagnostic1',
title: '1337: diagnostic1',
tooltip: 'diagnostic1',
},
]);
});
it('should detect the `extended-diagnostics` node if it is nested in another node', () => {
const diagnosticDoc = {
docType: 'extended-diagnostic',
code: '1337',
name: 'diagnostic1',
path: 'extended-diagnostics/diagnostic1',
};
const navigation = {
docType: 'navigation-json',
data: {
SideNav: [
{url: 'some/page', title: 'Some Page'},
{
title: 'Extended diagnostics grandparent',
children: [
{url: 'some/nested/page', title: 'Some Nested Page'},
{
title: 'Extended diagnostics parent',
children: [
{url: 'some/more/nested/page', title: 'Some More Nested Page'},
{
title: 'Extended diagnostics',
children: [{title: 'Overview', url: 'extended-diagnostics'}],
},
{url: 'other/more/nested/page', title: 'Other More Nested Page'},
],
},
{url: 'other/nested/page', title: 'Other Nested Page'},
],
},
{url: 'other/page', title: 'Other Page'},
],
},
};
processor.$process([diagnosticDoc, navigation]);
const diagnosticsContainerNode = navigation.data.SideNav[1].children[1].children[1];
expect(diagnosticsContainerNode.title).toBe('Extended diagnostics');
expect(diagnosticsContainerNode.children).toEqual([
{url: 'extended-diagnostics', title: 'Overview'},
{url: 'extended-diagnostics/diagnostic1', title: '1337: diagnostic1', tooltip: 'diagnostic1'},
]);
});
it('should complain if there is no child with `extended-diagnostics` url', () => {
const diagnosticDoc = {
docType: 'extended-diagnostic',
code: '1337',
name: 'diagnostic1',
path: 'extended-diagnostics/diagnostic1',
};
const navigation = {
docType: 'navigation-json',
data: {
SideNav: [
{url: 'some/page', title: 'Some Page'}, {
title: 'Extended diagnostics',
tooltip: 'Angular extended diagnostic reference',
children: [{title: 'Overview', url: 'not-extended-diagnostics'}],
},
{url: 'other/page', title: 'Other Page'},
]
}
};
expect(() => processor.$process([diagnosticDoc, navigation]))
.toThrowError(createDocMessage(
'Missing `extended-diagnostics` url - This node is needed as a place to insert the ' +
'generated extended diagnostics docs.',
navigation));
});
});
@@ -0,0 +1,24 @@
/**
* @dgService
* @description
* This file reader will pull the contents from an extended diagnostic text file (by default `.md`).
*
* The doc will initially have the form:
* ```
* {
* docType: 'extended-diagnostic',
* content: 'the content of the file',
* }
* ```
*/
module.exports = function extendedDiagnosticFileReader() {
return {
name: 'extendedDiagnosticFileReader',
defaultPattern: /\.md$/,
getDocs: fileInfo => {
// We return a single element array because extended-diagnostic files only contain one
// document.
return [{docType: 'extended-diagnostic', content: fileInfo.content}];
},
};
};
@@ -0,0 +1,43 @@
const path = require('canonical-path');
const Dgeni = require('dgeni');
const testPackage = require('../../helpers/test-package');
describe('extendedDiagnosticFileReader', () => {
let dgeni, injector, fileReader;
beforeEach(() => {
dgeni = new Dgeni([testPackage('angular-extended-diagnostics-package', false)]);
injector = dgeni.configureInjector();
fileReader = injector.get('extendedDiagnosticFileReader');
});
function createFileInfo(file, content, basePath) {
return {
fileReader: fileReader.name,
filePath: file,
baseName: path.basename(file, path.extname(file)),
extension: path.extname(file).replace(/^\./, ''),
basePath: basePath,
relativePath: path.relative(basePath, file),
content: content,
};
}
describe('defaultPattern', () => {
it('should match `.md` files', () => {
expect(fileReader.defaultPattern.test('abc.md')).toBeTrue();
expect(fileReader.defaultPattern.test('abc.js')).toBeFalse();
});
});
describe('getDocs', () => {
it('should return an object containing info about the file and its contents', () => {
const fileInfo = createFileInfo(
'project/path/modules/someModule/foo/docs/subfolder/bar.md', 'A load of content.',
'project/path');
expect(fileReader.getDocs(fileInfo)).toEqual([
{docType: 'extended-diagnostic', content: 'A load of content.'},
]);
});
});
});
@@ -10,9 +10,12 @@ const gitPackage = require('dgeni-packages/git');
const apiPackage = require('../angular-api-package');
const contentPackage = require('../angular-content-package');
const errorsPackage = require('../angular-errors-package');
const extendedDiagnosticsPackage = require('../angular-extended-diagnostics-package');
const cliDocsPackage = require('../cli-docs-package');
module.exports = new Package('angular.io', [gitPackage, apiPackage, contentPackage, cliDocsPackage, errorsPackage])
module.exports = new Package(
'angular.io',
[gitPackage, apiPackage, contentPackage, cliDocsPackage, errorsPackage, extendedDiagnosticsPackage])
// This processor relies upon the versionInfo. See below...
.processor(require('./processors/processNavigationMap'))
@@ -0,0 +1,32 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const Package = require('dgeni').Package;
const errorsPackage = require('../angular-errors-package');
const {CONTENTS_PATH} = require('../config');
const baseAuthoringPackage = require('./base-authoring-package');
function createPackage() {
return new Package('author-errors', [baseAuthoringPackage, errorsPackage])
.config(function(readFilesProcessor) {
readFilesProcessor.sourceFiles = [
{
basePath: CONTENTS_PATH,
include: `${CONTENTS_PATH}/errors/index.md`,
fileReader: 'contentFileReader',
},
{
basePath: CONTENTS_PATH,
include: `${CONTENTS_PATH}/errors/**/*.md`,
exclude: `${CONTENTS_PATH}/errors/index.md`,
fileReader: 'errorFileReader',
},
];
});
}
module.exports = {createPackage};
@@ -0,0 +1,32 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const Package = require('dgeni').Package;
const extendedDiagnosticsPackage = require('../angular-extended-diagnostics-package');
const {CONTENTS_PATH} = require('../config');
const baseAuthoringPackage = require('./base-authoring-package');
function createPackage() {
return new Package('author-extended-diagnostics', [baseAuthoringPackage, extendedDiagnosticsPackage])
.config(function(readFilesProcessor) {
readFilesProcessor.sourceFiles = [
{
basePath: CONTENTS_PATH,
include: `${CONTENTS_PATH}/extended-diagnostics/index.md`,
fileReader: 'contentFileReader',
},
{
basePath: CONTENTS_PATH,
include: `${CONTENTS_PATH}/extended-diagnostics/**/*.md`,
exclude: `${CONTENTS_PATH}/extended-diagnostics/index.md`,
fileReader: 'extendedDiagnosticFileReader',
},
];
});
}
module.exports = {createPackage};
@@ -45,6 +45,18 @@ function createPackage(changedFile) {
console.log('Building API docs for', packageName);
return require('./api-package').createPackage(packageName);
}
const errorsMatch = /^aio\/content\/error\/([^.]+)\.md/.exec(changedFile);
if (errorsMatch) {
console.log('Building errors docs');
return require('./errors-package').createPackage();
}
const diagnosticsMatch = /^aio\/content\/extended-diagnostics\/([^.]+)\.md/.exec(changedFile);
if (diagnosticsMatch) {
console.log('Building extended diagnostics docs');
return require('./extended-diagnostics-package').createPackage();
}
}
module.exports = {
@@ -0,0 +1,12 @@
{% import "lib/githubLinks.html" as github -%}
<h1>{$ doc.code $}: {$ doc.name $}</h1>
<div class="github-links">
{$ github.githubEditLink(doc, versionInfo) $}
</div>
{% block content %}
<div class="content">
<h2>Description</h2>
{$ doc.description | marked $}
</div>
{% endblock %}
+408 -484
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -581,9 +581,8 @@ export class NgTemplateOutlet implements OnChanges {
ngOnChanges(changes: SimpleChanges): void;
ngTemplateOutlet: TemplateRef<any> | null;
ngTemplateOutletContext: Object | null;
ngTemplateOutletInjector: Injector | null;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<NgTemplateOutlet, "[ngTemplateOutlet]", never, { "ngTemplateOutletContext": "ngTemplateOutletContext"; "ngTemplateOutlet": "ngTemplateOutlet"; "ngTemplateOutletInjector": "ngTemplateOutletInjector"; }, {}, never>;
static ɵdir: i0.ɵɵDirectiveDeclaration<NgTemplateOutlet, "[ngTemplateOutlet]", never, { "ngTemplateOutletContext": "ngTemplateOutletContext"; "ngTemplateOutlet": "ngTemplateOutlet"; }, {}, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<NgTemplateOutlet, never>;
}
+1 -5
View File
@@ -1238,7 +1238,7 @@ export type StaticProvider = ValueProvider | ExistingProvider | StaticClassProvi
// @public
export abstract class TemplateRef<C> {
abstract createEmbeddedView(context: C, injector?: Injector): EmbeddedViewRef<C>;
abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
abstract readonly elementRef: ElementRef;
}
@@ -1384,10 +1384,6 @@ export abstract class ViewContainerRef {
}): ComponentRef<C>;
// @deprecated
abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModuleRef?: NgModuleRef<any>): ComponentRef<C>;
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, options?: {
index?: number;
injector?: Injector;
}): EmbeddedViewRef<C>;
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>;
abstract detach(index?: number): ViewRef | null;
abstract get element(): ElementRef;
+4 -6
View File
@@ -167,7 +167,6 @@ export class CheckboxControlValueAccessor extends BuiltInControlValueAccessor im
// @public
export class CheckboxRequiredValidator extends RequiredValidator {
validate(control: AbstractControl): ValidationErrors | null;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<CheckboxRequiredValidator, "input[type=checkbox][required][formControlName],input[type=checkbox][required][formControl],input[type=checkbox][required][ngModel]", never, {}, {}, never>;
// (undocumented)
@@ -694,11 +693,10 @@ export class ReactiveFormsModule {
}
// @public
export class RequiredValidator implements Validator {
registerOnValidatorChange(fn: () => void): void;
get required(): boolean | string;
set required(value: boolean | string);
validate(control: AbstractControl): ValidationErrors | null;
export class RequiredValidator extends AbstractValidatorDirective {
// (undocumented)
enabled(input: boolean): boolean;
required: boolean | string;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<RequiredValidator, ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", never, { "required": "required"; }, {}, never>;
// (undocumented)
+3 -3
View File
@@ -5,7 +5,7 @@
"runtime": 4330,
"main": 458531,
"polyfills": 37123,
"styles": 69863,
"styles": 70394,
"light-theme": 77232,
"dark-theme": 77356
}
@@ -15,9 +15,9 @@
"master": {
"uncompressed": {
"runtime": 4330,
"main": 457719,
"main": 459048,
"polyfills": 37271,
"styles": 69863,
"styles": 70394,
"light-theme": 77232,
"dark-theme": 77356
}
@@ -52,8 +52,8 @@
"master": {
"uncompressed": {
"runtime": 1063,
"main": 162121,
"polyfills": 37206
"main": 163160,
"polyfills": 36975
}
}
},
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "13.2.0-next.2",
"version": "13.2.0",
"private": true,
"description": "Angular - a web framework for modern web apps",
"homepage": "https://github.com/angular/angular",
@@ -38,12 +38,12 @@
},
"// 1": "dependencies are used locally and by bazel",
"dependencies": {
"@angular-devkit/build-angular": "13.1.3",
"@angular-devkit/build-optimizer": "0.1301.3",
"@angular-devkit/core": "13.1.3",
"@angular-devkit/schematics": "13.1.3",
"@angular-devkit/build-angular": "13.1.4",
"@angular-devkit/build-optimizer": "0.1301.4",
"@angular-devkit/core": "13.1.4",
"@angular-devkit/schematics": "13.1.4",
"@angular/animations-12": "npm:@angular/animations@12.2.13",
"@angular/cli": "13.1.3",
"@angular/cli": "13.1.4",
"@angular/common-12": "npm:@angular/common@12.2.13",
"@angular/core-12": "npm:@angular/core@12.2.13",
"@angular/forms-12": "npm:@angular/forms@12.2.13",
@@ -70,7 +70,7 @@
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^21.0.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@schematics/angular": "13.1.3",
"@schematics/angular": "13.1.4",
"@types/angular": "^1.6.47",
"@types/babel__core": "7.1.6",
"@types/babel__generator": "7.6.1",
@@ -137,7 +137,7 @@
"puppeteer": "10.2.0",
"reflect-metadata": "^0.1.3",
"requirejs": "^2.3.6",
"rollup": "~2.63.0",
"rollup": "~2.65.0",
"rollup-plugin-preserve-shebang": "^1.0.1",
"rollup-plugin-sourcemaps": "^0.6.3",
"rxjs": "^6.6.7",
+1 -1
View File
@@ -410,7 +410,7 @@ export function compile({
const hasError = diagnostics.some((diag) => diag.category === ts.DiagnosticCategory.Error);
if (!hasError) {
if (bazelOpts.tsickleGenerateExterns) {
externs += tsickle.getGeneratedExterns(tsickleEmitResult.externs);
externs += tsickle.getGeneratedExterns(tsickleEmitResult.externs, compilerOpts.rootDir!);
}
if (bazelOpts.manifest) {
const manifest = constructManifest(tsickleEmitResult.modulesManifest, bazelHost);
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, EmbeddedViewRef, Injector, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
/**
* @ngModule CommonModule
@@ -49,9 +49,6 @@ export class NgTemplateOutlet implements OnChanges {
*/
@Input() public ngTemplateOutlet: TemplateRef<any>|null = null;
/** Injector to be used within the embedded view. */
@Input() public ngTemplateOutletInjector: Injector|null = null;
constructor(private _viewContainerRef: ViewContainerRef) {}
ngOnChanges(changes: SimpleChanges) {
@@ -62,17 +59,9 @@ export class NgTemplateOutlet implements OnChanges {
viewContainerRef.remove(viewContainerRef.indexOf(this._viewRef));
}
if (this.ngTemplateOutlet) {
const {
ngTemplateOutlet: template,
ngTemplateOutletContext: context,
ngTemplateOutletInjector: injector
} = this;
this._viewRef = viewContainerRef.createEmbeddedView(
template, context, injector ? {injector} : undefined);
} else {
this._viewRef = null;
}
this._viewRef = this.ngTemplateOutlet ?
viewContainerRef.createEmbeddedView(this.ngTemplateOutlet, this.ngTemplateOutletContext) :
null;
} else if (
this._viewRef && changes['ngTemplateOutletContext'] && this.ngTemplateOutletContext) {
this._viewRef.context = this.ngTemplateOutletContext;
+5 -16
View File
@@ -127,7 +127,11 @@ export class BrowserViewportScroller implements ViewportScroller {
this.scrollToElement(elSelected);
// After scrolling to the element, the spec dictates that we follow the focus steps for the
// target. Rather than following the robust steps, simply attempt focus.
this.attemptFocus(elSelected);
//
// @see https://html.spec.whatwg.org/#get-the-focusable-area
// @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus
// @see https://html.spec.whatwg.org/#focusable-area
elSelected.focus();
}
}
@@ -157,21 +161,6 @@ export class BrowserViewportScroller implements ViewportScroller {
this.window.scrollTo(left - offset[0], top - offset[1]);
}
/**
* Calls `focus` on the `focusTarget` and returns `true` if the element was focused successfully.
*
* If `false`, further steps may be necessary to determine a valid substitute to be focused
* instead.
*
* @see https://html.spec.whatwg.org/#get-the-focusable-area
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus
* @see https://html.spec.whatwg.org/#focusable-area
*/
private attemptFocus(focusTarget: HTMLElement): boolean {
focusTarget.focus();
return this.document.activeElement === focusTarget;
}
/**
* We only support scroll restoration when we can get a hold of window.
* This means that we do not support this behavior when running in a web worker.
@@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {Component, ContentChildren, Directive, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, OnDestroy, QueryList, TemplateRef} from '@angular/core';
import {Component, ContentChildren, Directive, Injectable, NO_ERRORS_SCHEMA, OnDestroy, QueryList, TemplateRef} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -29,13 +29,7 @@ describe('NgTemplateOutlet', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent,
CaptureTplRefs,
DestroyableCmpt,
MultiContextComponent,
InjectValueComponent,
],
declarations: [TestComponent, CaptureTplRefs, DestroyableCmpt, MultiContextComponent],
imports: [CommonModule],
providers: [DestroyedSpyService]
});
@@ -268,19 +262,8 @@ describe('NgTemplateOutlet', () => {
expect(componentInstance.context1).toEqual({name: 'two'});
expect(componentInstance.context2).toEqual({name: 'one'});
});
it('should be able to specify an injector', waitForAsync(() => {
const template = `<ng-template #tpl><inject-value></inject-value></ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; injector: injector"></ng-container>`;
fixture = createTestComponent(template);
fixture.componentInstance.injector =
Injector.create({providers: [{provide: templateToken, useValue: 'world'}]});
detectChangesAndExpectText('Hello world');
}));
});
const templateToken = new InjectionToken<string>('templateToken');
@Injectable()
class DestroyedSpyService {
destroyed = false;
@@ -307,15 +290,6 @@ class TestComponent {
currentTplRef!: TemplateRef<any>;
context: any = {foo: 'bar'};
value = 'bar';
injector: Injector|null = null;
}
@Component({
selector: 'inject-value',
template: 'Hello {{tokenValue}}',
})
class InjectValueComponent {
constructor(@Inject(templateToken) public tokenValue: string) {}
}
@Component({
@@ -51,7 +51,7 @@ interface LazyCompilationState {
refEmitter: ReferenceEmitter;
templateTypeChecker: TemplateTypeChecker;
resourceRegistry: ResourceRegistry;
extendedTemplateChecker: ExtendedTemplateChecker;
extendedTemplateChecker: ExtendedTemplateChecker|null;
}
@@ -446,7 +446,7 @@ export class NgCompiler {
diagnostics.push(
...this.getNonTemplateDiagnostics().filter(diag => diag.file === file),
...this.getTemplateDiagnosticsForFile(file, optimizeFor));
if (this.options._extendedTemplateDiagnostics) {
if (this.options.strictTemplates) {
diagnostics.push(...this.getExtendedTemplateDiagnostics(file));
}
return this.addMessageTextDetails(diagnostics);
@@ -460,8 +460,9 @@ export class NgCompiler {
const ttc = compilation.templateTypeChecker;
const diagnostics: ts.Diagnostic[] = [];
diagnostics.push(...ttc.getDiagnosticsForComponent(component));
if (this.options._extendedTemplateDiagnostics) {
const extendedTemplateChecker = compilation.extendedTemplateChecker;
const extendedTemplateChecker = compilation.extendedTemplateChecker;
if (this.options.strictTemplates && extendedTemplateChecker) {
diagnostics.push(...extendedTemplateChecker.getDiagnosticsForComponent(component));
}
return this.addMessageTextDetails(diagnostics);
@@ -886,6 +887,10 @@ export class NgCompiler {
const diagnostics: ts.Diagnostic[] = [];
const compilation = this.ensureAnalyzed();
const extendedTemplateChecker = compilation.extendedTemplateChecker;
if (!extendedTemplateChecker) {
return [];
}
if (sf !== undefined) {
return compilation.traitCompiler.extendedTemplateCheck(sf, extendedTemplateChecker);
}
@@ -1062,8 +1067,11 @@ export class NgCompiler {
reflector, this.adapter, this.incrementalCompilation, scopeRegistry, typeCheckScopeRegistry,
this.delegatingPerfRecorder);
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
templateTypeChecker, checker, ALL_DIAGNOSTIC_FACTORIES, this.options);
// Only construct the extended template checker if the configuration is valid and usable.
const extendedTemplateChecker = this.constructionDiagnostics.length === 0 ?
new ExtendedTemplateCheckerImpl(
templateTypeChecker, checker, ALL_DIAGNOSTIC_FACTORIES, this.options) :
null;
return {
isCore,
@@ -229,20 +229,19 @@ class TemplateVisitor extends TmplAstRecursiveVisitor {
let name: string;
let kind: IdentifierKind.Element|IdentifierKind.Template;
if (node instanceof TmplAstTemplate) {
name = node.tagName;
name = node.tagName ?? 'ng-template';
kind = IdentifierKind.Template;
} else {
// Namespaced elements have a particular format for `node.name` that needs to be handled.
// For example, an `<svg>` element has a `node.name` of `':svg:svg'`.
// TODO(alxhub): properly handle namespaced elements
if (node.name.startsWith(':')) {
name = node.name.split(':').pop()!;
} else {
name = node.name;
}
name = node.name;
kind = IdentifierKind.Element;
}
// Namespaced elements have a particular format for `node.name` that needs to be handled.
// For example, an `<svg>` element has a `node.name` of `':svg:svg'`.
// TODO(alxhub): properly handle namespaced elements
if (name.startsWith(':')) {
name = name.split(':').pop()!;
}
const sourceSpan = node.startSourceSpan;
// An element's or template's source span can be of the form `<element>`, `<element />`, or
// `<element></element>`. Only the selector is interesting to the indexer, so the source is
@@ -35,6 +35,20 @@ runInEachFileSystem(() => {
});
});
it('should handle svg elements on templates', () => {
const template = '<svg *ngIf="true"></svg>';
const refs = getTemplateIdentifiers(bind(template));
const [ref] = Array.from(refs);
expect(ref).toEqual({
kind: IdentifierKind.Template,
name: 'svg',
span: new AbsoluteSourceSpan(1, 4),
usedDirectives: new Set(),
attributes: new Set(),
});
});
it('should handle comments in interpolations', () => {
const template = '{{foo // comment}}';
const refs = getTemplateIdentifiers(bind(template));
@@ -48,6 +62,33 @@ runInEachFileSystem(() => {
});
});
it('should handle whitespace and comments in interpolations', () => {
const template = '{{ foo // comment }}';
const refs = getTemplateIdentifiers(bind(template));
const [ref] = Array.from(refs);
expect(ref).toEqual({
name: 'foo',
kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(5, 8),
target: null,
});
});
it('works when structural directives are on templates', () => {
const template = '<ng-template *ngIf="true">';
const refs = getTemplateIdentifiers(bind(template));
const [ref] = Array.from(refs);
expect(ref).toEqual({
kind: IdentifierKind.Template,
name: 'ng-template',
span: new AbsoluteSourceSpan(1, 12),
usedDirectives: new Set(),
attributes: new Set(),
});
});
it('should generate nothing in empty template', () => {
const template = '';
const refs = getTemplateIdentifiers(bind(template));
@@ -86,7 +86,7 @@ export class ExtendedTemplateCheckerImpl implements ExtendedTemplateChecker {
}
/**
* Converts a `DiagnosticCategoryLabel` to its equivalent `ts.DiagnosticCategory` or `undefined` if
* Converts a `DiagnosticCategoryLabel` to its equivalent `ts.DiagnosticCategory` or `null` if
* the label is `DiagnosticCategoryLabel.Suppress`.
*/
function diagnosticLabelToCategory(label: DiagnosticCategoryLabel): ts.DiagnosticCategory|null {
@@ -20,7 +20,7 @@ consts: function() {
},
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 2, 1, undefined, 0);
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 2, 1, null, 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("ngIf", true);
@@ -19,7 +19,7 @@ consts: function() {
},
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, undefined, 0);
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, null, 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("ngIf", ctx.visible);
@@ -31,7 +31,7 @@ consts: function() {
},
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, undefined, 0);
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, null, 0);
$r3$.ɵɵtemplate(1, MyComponent_ng_container_1_Template, 2, 0, "ng-container", 0);
}
if (rf & 2) {
@@ -1,7 +1,7 @@
consts: [[4, "ngIf"], [3, "dir"]],
template: function TestComp_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtemplate(0, $TestComp_0_Template$, 1, 1, undefined, 0);
$i0$.ɵɵtemplate(0, $TestComp_0_Template$, 1, 1, null, 0);
}
if (rf & 2) {
$i0$.ɵɵproperty("ngIf", true);
@@ -233,6 +233,24 @@ runInEachFileSystem(() => {
const diagnostics = env.driveDiagnostics(0 /* expectedExitCode */);
expect(diagnostics).toEqual([]);
});
it('by throwing an error when given a bad category', () => {
env.tsconfig({
strictTemplates: true,
extendedDiagnostics: {
defaultCategory: 'not-a-category',
},
});
env.write('test.ts', warningComponent);
const diagnostics = env.driveDiagnostics(1 /* expectedExitCode */);
expect(diagnostics.length).toBe(1);
expect(diagnostics[0]).toEqual(jasmine.objectContaining({
code: ngErrorCode(ErrorCode.CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CATEGORY_LABEL),
category: ts.DiagnosticCategory.Error,
}));
});
});
});
});
@@ -41,9 +41,7 @@ export class Parser {
const sourceToLex = this._stripComments(input);
const tokens = this._lexer.tokenize(sourceToLex);
const ast =
new _ParseAST(
input, location, absoluteOffset, tokens, sourceToLex.length, true, this.errors, 0)
.parseChain();
new _ParseAST(input, location, absoluteOffset, tokens, true, this.errors, 0).parseChain();
return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
}
@@ -90,8 +88,7 @@ export class Parser {
this._checkNoInterpolation(input, location, interpolationConfig);
const sourceToLex = this._stripComments(input);
const tokens = this._lexer.tokenize(sourceToLex);
return new _ParseAST(
input, location, absoluteOffset, tokens, sourceToLex.length, false, this.errors, 0)
return new _ParseAST(input, location, absoluteOffset, tokens, false, this.errors, 0)
.parseChain();
}
@@ -138,8 +135,8 @@ export class Parser {
absoluteValueOffset: number): TemplateBindingParseResult {
const tokens = this._lexer.tokenize(templateValue);
const parser = new _ParseAST(
templateValue, templateUrl, absoluteValueOffset, tokens, templateValue.length,
false /* parseAction */, this.errors, 0 /* relative offset */);
templateValue, templateUrl, absoluteValueOffset, tokens, false /* parseAction */,
this.errors, 0 /* relative offset */);
return parser.parseTemplateBindings({
source: templateKey,
span: new AbsoluteSourceSpan(absoluteKeyOffset, absoluteKeyOffset + templateKey.length),
@@ -159,10 +156,9 @@ export class Parser {
const expressionText = expressions[i].text;
const sourceToLex = this._stripComments(expressionText);
const tokens = this._lexer.tokenize(sourceToLex);
const ast = new _ParseAST(
input, location, absoluteOffset, tokens, sourceToLex.length, false,
this.errors, offsets[i])
.parseChain();
const ast =
new _ParseAST(input, location, absoluteOffset, tokens, false, this.errors, offsets[i])
.parseChain();
expressionNodes.push(ast);
}
@@ -180,7 +176,7 @@ export class Parser {
const sourceToLex = this._stripComments(expression);
const tokens = this._lexer.tokenize(sourceToLex);
const ast = new _ParseAST(
expression, location, absoluteOffset, tokens, sourceToLex.length,
expression, location, absoluteOffset, tokens,
/* parseAction */ false, this.errors, 0)
.parseChain();
const strings = ['', '']; // The prefix and suffix strings are both empty
@@ -275,7 +271,7 @@ export class Parser {
private _stripComments(input: string): string {
const i = this._commentStart(input);
return i != null ? input.substring(0, i).trim() : input;
return i != null ? input.substring(0, i) : input;
}
private _commentStart(input: string): number|null {
@@ -392,8 +388,8 @@ export class _ParseAST {
constructor(
public input: string, public location: string, public absoluteOffset: number,
public tokens: Token[], public inputLength: number, public parseAction: boolean,
private errors: ParserError[], private offset: number) {}
public tokens: Token[], public parseAction: boolean, private errors: ParserError[],
private offset: number) {}
peek(offset: number): Token {
const i = this.index + offset;
@@ -429,7 +425,7 @@ export class _ParseAST {
// No tokens have been processed yet; return the next token's start or the length of the input
// if there is no token.
if (this.tokens.length === 0) {
return this.inputLength + this.offset;
return this.input.length + this.offset;
}
return this.next.index + this.offset;
}
@@ -587,7 +583,7 @@ export class _ParseAST {
if (exprs.length == 0) {
// We have no expressions so create an empty expression that spans the entire input length
const artificialStart = this.offset;
const artificialEnd = this.offset + this.inputLength;
const artificialEnd = this.offset + this.input.length;
return new EmptyExpr(
this.span(artificialStart, artificialEnd),
this.sourceSpan(artificialStart, artificialEnd));
@@ -623,7 +619,7 @@ export class _ParseAST {
//
// Therefore, we push the end of the `ParseSpan` for this pipe all the way up to the
// beginning of the next token, or until the end of input if the next token is EOF.
fullSpanEnd = this.next.index !== -1 ? this.next.index : this.inputLength + this.offset;
fullSpanEnd = this.next.index !== -1 ? this.next.index : this.input.length + this.offset;
// The `nameSpan` for an empty pipe name is zero-length at the end of any whitespace
// beyond the pipe character.
+17 -5
View File
@@ -119,11 +119,23 @@ export class Element implements Node {
export class Template implements Node {
constructor(
public tagName: string, public attributes: TextAttribute[], public inputs: BoundAttribute[],
public outputs: BoundEvent[], public templateAttrs: (BoundAttribute|TextAttribute)[],
public children: Node[], public references: Reference[], public variables: Variable[],
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
public endSourceSpan: ParseSourceSpan|null, public i18n?: I18nMeta) {}
// tagName is the name of the container element, if applicable.
// `null` is a special case for when there is a structural directive on an `ng-template` so
// the renderer can differentiate between the synthetic template and the one written in the
// file.
public tagName: string|null,
public attributes: TextAttribute[],
public inputs: BoundAttribute[],
public outputs: BoundEvent[],
public templateAttrs: (BoundAttribute|TextAttribute)[],
public children: Node[],
public references: Reference[],
public variables: Variable[],
public sourceSpan: ParseSourceSpan,
public startSourceSpan: ParseSourceSpan,
public endSourceSpan: ParseSourceSpan|null,
public i18n?: I18nMeta,
) {}
visit<Result>(visitor: Visitor<Result>): Result {
return visitor.visitTemplate(this);
}
@@ -186,7 +186,7 @@ class HtmlAstToIvyAst implements html.Visitor {
const children: t.Node[] =
html.visitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children);
let parsedElement: t.Node|undefined;
let parsedElement: t.Content|t.Template|t.Element|undefined;
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
// `<ng-content>`
if (element.children &&
@@ -235,13 +235,12 @@ class HtmlAstToIvyAst implements html.Visitor {
// the wrapping template to prevent unnecessary i18n instructions from being generated. The
// necessary i18n meta information will be extracted from child elements.
const i18n = isTemplateElement && isI18nRootElement ? undefined : element.i18n;
const name = parsedElement instanceof t.Template ? null : parsedElement.name;
// TODO(pk): test for this case
parsedElement = new t.Template(
(parsedElement as t.Element | t.Content).name, hoistedAttrs.attributes,
hoistedAttrs.inputs, hoistedAttrs.outputs, templateAttrs, [parsedElement],
[/* no references */], templateVariables, element.sourceSpan, element.startSourceSpan,
element.endSourceSpan, i18n);
name, hoistedAttrs.attributes, hoistedAttrs.inputs, hoistedAttrs.outputs, templateAttrs,
[parsedElement], [/* no references */], templateVariables, element.sourceSpan,
element.startSourceSpan, element.endSourceSpan, i18n);
}
if (isI18nRootElement) {
this.inI18nBlock = false;
@@ -17,6 +17,17 @@ describe('expression AST absolute source spans', () => {
.toContain(['foo', new AbsoluteSourceSpan(2, 5)]);
});
it('should handle whitespace in interpolation', () => {
expect(humanizeExpressionSource(parse('{{ foo }}', {preserveWhitespaces: true}).nodes))
.toContain(['foo', new AbsoluteSourceSpan(4, 7)]);
});
it('should handle whitespace and comment in interpolation', () => {
expect(humanizeExpressionSource(
parse('{{ foo // comment }}', {preserveWhitespaces: true}).nodes))
.toContain(['foo', new AbsoluteSourceSpan(4, 7)]);
});
it('should handle comment in an action binding', () => {
expect(humanizeExpressionSource(parse('<button (click)="foo = true // comment">Save</button>', {
preserveWhitespaces: true
@@ -9,6 +9,7 @@
import {BindingType} from '../../src/expression_parser/ast';
import * as t from '../../src/render3/r3_ast';
import {unparse} from '../expression_parser/utils/unparser';
import {parseR3 as parse} from './view/util';
@@ -272,6 +273,18 @@ describe('R3 template transform', () => {
]);
});
it('should support <ng-template> with structural directive', () => {
expectFromHtml('<ng-template *ngIf="true"></ng-template>').toEqual([
['Template'],
['BoundAttribute', 0, 'ngIf', 'true'],
['Template'],
]);
const res = parse('<ng-template *ngIf="true"></ng-template>', {ignoreError: false});
expect((res.nodes[0] as t.Template).tagName).toEqual(null);
expect(((res.nodes[0] as t.Template).children[0] as t.Template).tagName)
.toEqual('ng-template');
});
it('should support reference via #...', () => {
expectFromHtml('<ng-template #a></ng-template>').toEqual([
['Template'],
-5
View File
@@ -19,11 +19,6 @@
"version": "14.0.0-beta",
"description": "As of Angular version 13, `entryComponents` are no longer necessary.",
"factory": "./migrations/entry-components/index"
},
"migration-v14-typed-forms": {
"version": "9999.0.0",
"description": "Experimental migration that adds <any>s for Typed Forms.",
"factory": "./migrations/typed-forms/index"
}
}
}
+194 -188
View File
@@ -80,208 +80,214 @@ describe('Typed Forms migration', () => {
shx.rm('-r', tmpDirPath);
});
describe(`should add ${anySymbolName} to constructors`, () => {
it('for FormControl', async () => {
writeFile('/index.ts', `
import { FormControl } from '@angular/forms';
@Component({template: ''})
export class MyComp {
const fc1 = new FormControl();
new FormControl(42);
constructor() {}
}
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`const fc1 = new FormControl<${anySymbolName}>();`);
expect(tree.readContent('/index.ts')).toContain(`new FormControl<${anySymbolName}>(42);`);
});
// These tests are disabled because the migration is removed from migration.json, and cannot be
// run.
it('for FormGroup', async () => {
writeFile('/index.ts', `
import { FormGroup } from '@angular/forms';
@Component({template: ''})
export class MyComp {
const fg = new FormGroup({foo: 3});
constructor() {}
}
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`const fg = new FormGroup<${anySymbolName}>({foo: 3});`);
});
// describe(`should add ${anySymbolName} to constructors`, () => {
// it('for FormControl', async () => {
// writeFile('/index.ts', `
// import { FormControl } from '@angular/forms';
// @Component({template: ''})
// export class MyComp {
// const fc1 = new FormControl();
// new FormControl(42);
// constructor() {}
// }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts'))
// .toContain(`const fc1 = new FormControl<${anySymbolName}>();`);
// expect(tree.readContent('/index.ts')).toContain(`new FormControl<${anySymbolName}>(42);`);
// });
it('for FormArray', async () => {
writeFile('/index.ts', `
import { FormArray } from '@angular/forms';
@Component({template: ''})
export class MyComp {
const fa = new FormArray([null]);
constructor() {}
}
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`const fa = new FormArray<${anySymbolName}[]>([null]);`);
});
// it('for FormGroup', async () => {
// writeFile('/index.ts', `
// import { FormGroup } from '@angular/forms';
// @Component({template: ''})
// export class MyComp {
// const fg = new FormGroup({foo: 3});
// constructor() {}
// }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts'))
// .toContain(`const fg = new FormGroup<${anySymbolName}>({foo: 3});`);
// });
it('for FormControl with a qualified import', async () => {
writeFile('/index.ts', `
import { FormControl as FC } from '@angular/forms';
@Component({template: ''})
export class MyComp {
const fc = new FC({foo: 3});
constructor() {}
}
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`const fc = new FC<${anySymbolName}>({foo: 3});`);
});
// it('for FormArray', async () => {
// writeFile('/index.ts', `
// import { FormArray } from '@angular/forms';
// @Component({template: ''})
// export class MyComp {
// const fa = new FormArray([null]);
// constructor() {}
// }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts'))
// .toContain(`const fa = new FormArray<${anySymbolName}[]>([null]);`);
// });
it('for FormArray with a qualified import', async () => {
writeFile('/index.ts', `
import { FormArray as FA } from '@angular/forms';
@Component({template: ''})
export class MyComp {
const fa = new FA([null]);
constructor() {}
}
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`const fa = new FA<${anySymbolName}[]>([null]);`);
});
// it('for FormControl with a qualified import', async () => {
// writeFile('/index.ts', `
// import { FormControl as FC } from '@angular/forms';
// @Component({template: ''})
// export class MyComp {
// const fc = new FC({foo: 3});
// constructor() {}
// }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts'))
// .toContain(`const fc = new FC<${anySymbolName}>({foo: 3});`);
// });
it('but not for controls that already have type arguments', async () => {
writeFile('/index.ts', `
import { FormControl } from '@angular/forms';
@Component({template: ''})
export class MyComp {
const fc1 = new FormControl<${anySymbolName}>();
constructor() {}
}
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`const fc1 = new FormControl<${anySymbolName}>();`);
});
});
// it('for FormArray with a qualified import', async () => {
// writeFile('/index.ts', `
// import { FormArray as FA } from '@angular/forms';
// @Component({template: ''})
// export class MyComp {
// const fa = new FA([null]);
// constructor() {}
// }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts'))
// .toContain(`const fa = new FA<${anySymbolName}[]>([null]);`);
// });
describe(`should add ${anySymbolName} to FormBuilder method`, () => {
it('control', async () => {
writeFile('/index.ts', `
import { FormBuilder } from '@angular/forms';
@Component({template: ''})
export class MyComp {
constructor() {
const fb = new FormBuilder();
const fc = fb.control(43);
const fd = new FormBuilder().control(42);
}
}
`);
await runMigration();
expect(tree.readContent('/index.ts')).toContain(`.control<${anySymbolName}>(43);`);
expect(tree.readContent('/index.ts'))
.toContain(`const fd = new FormBuilder().control<${anySymbolName}>(42)`);
});
// it('but not for controls that already have type arguments', async () => {
// writeFile('/index.ts', `
// import { FormControl } from '@angular/forms';
// @Component({template: ''})
// export class MyComp {
// const fc1 = new FormControl<${anySymbolName}>();
// constructor() {}
// }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts'))
// .toContain(`const fc1 = new FormControl<${anySymbolName}>();`);
// });
// });
it('group', async () => {
writeFile('/index.ts', `
import { FormBuilder } from '@angular/forms';
@Component({template: ''})
export class MyComp {
constructor() {
const fb = new FormBuilder();
const fc = fb.group({});
const fd = new FormBuilder().group({});
}
}
`);
await runMigration();
expect(tree.readContent('/index.ts')).toContain(`fb.group<${anySymbolName}>({});`);
expect(tree.readContent('/index.ts'))
.toContain(`const fd = new FormBuilder().group<${anySymbolName}>({})`);
});
// describe(`should add ${anySymbolName} to FormBuilder method`, () => {
// it('control', async () => {
// writeFile('/index.ts', `
// import { FormBuilder } from '@angular/forms';
// @Component({template: ''})
// export class MyComp {
// constructor() {
// const fb = new FormBuilder();
// const fc = fb.control(43);
// const fd = new FormBuilder().control(42);
// }
// }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts')).toContain(`.control<${anySymbolName}>(43);`);
// expect(tree.readContent('/index.ts'))
// .toContain(`const fd = new FormBuilder().control<${anySymbolName}>(42)`);
// });
it('array', async () => {
writeFile('/index.ts', `
import { FormBuilder } from '@angular/forms';
@Component({template: ''})
export class MyComp {
constructor() {
const fb = new FormBuilder();
const fc = fb.array([0]);
const fd = new FormBuilder().array([0]);
}
}
`);
await runMigration();
expect(tree.readContent('/index.ts')).toContain(`fb.array<${anySymbolName}[]>([0]);`);
expect(tree.readContent('/index.ts'))
.toContain(`const fd = new FormBuilder().array<${anySymbolName}[]>([0])`);
});
});
// it('group', async () => {
// writeFile('/index.ts', `
// import { FormBuilder } from '@angular/forms';
// @Component({template: ''})
// export class MyComp {
// constructor() {
// const fb = new FormBuilder();
// const fc = fb.group({});
// const fd = new FormBuilder().group({});
// }
// }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts')).toContain(`fb.group<${anySymbolName}>({});`);
// expect(tree.readContent('/index.ts'))
// .toContain(`const fd = new FormBuilder().group<${anySymbolName}>({})`);
// });
describe('should add import', () => {
it('any when not already imported', async () => {
writeFile('/index.ts', `
import { FormBuilder } from '@angular/forms';
@Component({template: ''})
export class MyComp { }
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`import { ${anySymbolName}, FormBuilder } from '@angular/forms';`);
});
// it('array', async () => {
// writeFile('/index.ts', `
// import { FormBuilder } from '@angular/forms';
// @Component({template: ''})
// export class MyComp {
// constructor() {
// const fb = new FormBuilder();
// const fc = fb.array([0]);
// const fd = new FormBuilder().array([0]);
// }
// }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts')).toContain(`fb.array<${anySymbolName}[]>([0]);`);
// expect(tree.readContent('/index.ts'))
// .toContain(`const fd = new FormBuilder().array<${anySymbolName}[]>([0])`);
// });
// });
it('exclusively when not already imported', async () => {
writeFile('/index.ts', `
import { ${anySymbolName}, FormBuilder } from '@angular/forms';
@Component({template: ''})
export class MyComp { }
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`import { ${anySymbolName}, FormBuilder } from '@angular/forms';`);
});
});
// describe('should add import', () => {
// it('any when not already imported', async () => {
// writeFile('/index.ts', `
// import { FormBuilder } from '@angular/forms';
// @Component({template: ''})
// export class MyComp { }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts'))
// .toContain(`import { ${anySymbolName}, FormBuilder } from '@angular/forms';`);
// });
describe('should handle', () => {
it('an integrated example', async () => {
writeFile('/index.ts', `
import { Component } from '@angular/core';
import { AbstractControl, FormArray, FormBuilder, FormControl as FC, FormGroup } from '@angular/forms';
// it('exclusively when not already imported', async () => {
// writeFile('/index.ts', `
// import { ${anySymbolName}, FormBuilder } from '@angular/forms';
// @Component({template: ''})
// export class MyComp { }
// `);
// await runMigration();
// expect(tree.readContent('/index.ts'))
// .toContain(`import { ${anySymbolName}, FormBuilder } from '@angular/forms';`);
// });
// });
@Component({template: ''})
export class MyComponent {
private _control = new FC(42);
private _group = new FormGroup({});
private _array = new FormArray([]);
// describe('should handle', () => {
// it('an integrated example', async () => {
// writeFile('/index.ts', `
// import { Component } from '@angular/core';
// import { AbstractControl, FormArray, FormBuilder, FormControl as FC, FormGroup } from
// '@angular/forms';
private fb = new FormBuilder();
// @Component({template: ''})
// export class MyComponent {
// private _control = new FC(42);
// private _group = new FormGroup({});
// private _array = new FormArray([]);
build() {
const c = this.fb.control(42);
const g = this.fb.group({one: this.fb.control('')});
const a = this.fb.array([42]);
const fc2 = new FC(0);
}
}
`);
await runMigration();
[`import { ${
anySymbolName}, AbstractControl, FormArray, FormBuilder, FormControl as FC, FormGroup } from '@angular/forms';`,
`private _control = new FC<${anySymbolName}>(42)`,
`private _group = new FormGroup<${anySymbolName}>({})`,
`private _array = new FormArray<${anySymbolName}[]>([])`,
`const fc2 = new FC<${anySymbolName}>(0)`, `const c = this.fb.control<${anySymbolName}>(42)`,
`const g = this.fb.group<${anySymbolName}>({one: this.fb.control<${anySymbolName}>('')})`,
`const a = this.fb.array<${anySymbolName}[]>([42])`]
.forEach(t => expect(tree.readContent('/index.ts')).toContain(t));
});
});
// private fb = new FormBuilder();
// build() {
// const c = this.fb.control(42);
// const g = this.fb.group({one: this.fb.control('')});
// const a = this.fb.array([42]);
// const fc2 = new FC(0);
// }
// }
// `);
// await runMigration();
// [`import { ${
// anySymbolName}, AbstractControl, FormArray, FormBuilder, FormControl as FC, FormGroup
// } from '@angular/forms';`,
// `private _control = new FC<${anySymbolName}>(42)`,
// `private _group = new FormGroup<${anySymbolName}>({})`,
// `private _array = new FormArray<${anySymbolName}[]>([])`,
// `const fc2 = new FC<${anySymbolName}>(0)`, `const c =
// this.fb.control<${anySymbolName}>(42)`, `const g = this.fb.group<${anySymbolName}>({one:
// this.fb.control<${anySymbolName}>('')})`, `const a =
// this.fb.array<${anySymbolName}[]>([42])`]
// .forEach(t => expect(tree.readContent('/index.ts')).toContain(t));
// });
// });
function writeFile(filePath: string, contents: string) {
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
+4 -20
View File
@@ -6,12 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injector} from '../di/injector';
import {assertLContainer} from '../render3/assert';
import {ChainedInjector} from '../render3/chained_injector';
import {createLView, renderView} from '../render3/instructions/shared';
import {TContainerNode, TNode, TNodeType} from '../render3/interfaces/node';
import {DECLARATION_LCONTAINER, INJECTOR, LView, LViewFlags, QUERIES, TView} from '../render3/interfaces/view';
import {DECLARATION_LCONTAINER, LView, LViewFlags, QUERIES, TView} from '../render3/interfaces/view';
import {getCurrentTNode, getLView} from '../render3/state';
import {ViewRef as R3_ViewRef} from '../render3/view_ref';
import {assertDefined} from '../util/assert';
@@ -57,10 +55,9 @@ export abstract class TemplateRef<C> {
* and attaches it to the view container.
* @param context The data-binding context of the embedded view, as declared
* in the `<ng-template>` usage.
* @param injector Injector to be used within the embedded view.
* @returns The new embedded view object.
*/
abstract createEmbeddedView(context: C, injector?: Injector): EmbeddedViewRef<C>;
abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
/**
* @internal
@@ -80,12 +77,11 @@ const R3TemplateRef = class TemplateRef<T> extends ViewEngineTemplateRef<T> {
super();
}
override createEmbeddedView(context: T, injector?: Injector): EmbeddedViewRef<T> {
override createEmbeddedView(context: T): EmbeddedViewRef<T> {
const embeddedTView = this._declarationTContainer.tViews as TView;
const embeddedLView = createLView(
this._declarationLView, embeddedTView, context, LViewFlags.CheckAlways, null,
embeddedTView.declTNode, null, null, null,
createEmbeddedViewInjector(injector, this._declarationLView[INJECTOR]));
embeddedTView.declTNode, null, null, null, null);
const declarationLContainer = this._declarationLView[this._declarationTContainer.index];
ngDevMode && assertLContainer(declarationLContainer);
@@ -102,18 +98,6 @@ const R3TemplateRef = class TemplateRef<T> extends ViewEngineTemplateRef<T> {
}
};
function createEmbeddedViewInjector(
embeddedViewInjector: Injector|undefined, declarationViewInjector: Injector|null): Injector|
null {
if (!embeddedViewInjector) {
return null;
}
return declarationViewInjector ?
new ChainedInjector(embeddedViewInjector, declarationViewInjector) :
embeddedViewInjector;
}
/**
* Creates a TemplateRef given a node.
*
+2 -38
View File
@@ -90,24 +90,6 @@ export abstract class ViewContainerRef {
*/
abstract get length(): number;
/**
* Instantiates an embedded view and inserts it
* into this container.
* @param templateRef The HTML template that defines the view.
* @param context The data-binding context of the embedded view, as declared
* in the `<ng-template>` usage.
* @param options Extra configuration for the created view. Includes:
* * index: The 0-based index at which to insert the new view into this container.
* If not specified, appends the new view as the last entry.
* * injector: Injector to be used within the embedded view.
*
* @returns The `ViewRef` instance for the newly created view.
*/
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, options?: {
index?: number,
injector?: Injector
}): EmbeddedViewRef<C>;
/**
* Instantiates an embedded view and inserts it
* into this container.
@@ -276,27 +258,9 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
return this._lContainer.length - CONTAINER_HEADER_OFFSET;
}
override createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, options?: {
index?: number,
injector?: Injector
}): EmbeddedViewRef<C>;
override createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number):
EmbeddedViewRef<C>;
override createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, indexOrOptions?: number|{
index?: number,
injector?: Injector
}): EmbeddedViewRef<C> {
let index: number|undefined;
let injector: Injector|undefined;
if (typeof indexOrOptions === 'number') {
index = indexOrOptions;
} else if (indexOrOptions != null) {
index = indexOrOptions.index;
injector = indexOrOptions.injector;
}
const viewRef = templateRef.createEmbeddedView(context || <any>{}, injector);
EmbeddedViewRef<C> {
const viewRef = templateRef.createEmbeddedView(context || <any>{});
this.insert(viewRef, index);
return viewRef;
}
@@ -63,7 +63,7 @@ export const ES2015_INHERITED_CLASS_WITH_CTOR =
* and inherit a constructor.
*/
export const ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR =
/^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(\)\s*{\s*super\(\.\.\.arguments\)/;
/^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(\)\s*{[^}]*super\(\.\.\.arguments\)/;
/**
* Determine whether a stringified type is a class which delegates its constructor
@@ -1,36 +0,0 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injector} from '../di/injector';
import {InjectFlags} from '../di/interface/injector';
import {ProviderToken} from '../di/provider_token';
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider_flags';
/**
* Injector that looks up a value using a specific injector, before falling back to the module
* injector. Used primarily when creating components or embedded views dynamically.
*/
export class ChainedInjector implements Injector {
constructor(private injector: Injector, private parentInjector: Injector) {}
get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T {
const value = this.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as T, flags);
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
// Return the value from the root element injector when
// - it provides it
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
// - the module injector should not be checked
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
return value;
}
return this.parentInjector.get(token, notFoundValue, flags);
}
}
+26 -4
View File
@@ -9,6 +9,8 @@
import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
import {InjectionToken} from '../di/injection_token';
import {Injector} from '../di/injector';
import {InjectFlags} from '../di/interface/injector';
import {ProviderToken} from '../di/provider_token';
import {Type} from '../interface/type';
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
@@ -17,9 +19,9 @@ import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory
import {RendererFactory2} from '../render/api';
import {Sanitizer} from '../sanitization/sanitizer';
import {VERSION} from '../version';
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider_flags';
import {assertComponentType} from './assert';
import {ChainedInjector} from './chained_injector';
import {createRootComponent, createRootComponentView, createRootContext, LifecycleHooksFeature} from './component';
import {getComponentDef} from './definition';
import {NodeInjector} from './di';
@@ -78,6 +80,26 @@ export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>('SCHEDUL
factory: () => defaultScheduler,
});
function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injector): Injector {
return {
get: <T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T => {
const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as T, flags);
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
// Return the value from the root element injector when
// - it provides it
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
// - the module injector should not be checked
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
return value;
}
return moduleInjector.get(token, notFoundValue, flags);
}
};
}
/**
* Render3 implementation of {@link viewEngine_ComponentFactory}.
*/
@@ -114,11 +136,11 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
ngModule = ngModule || this.ngModule;
const rootViewInjector = ngModule ? new ChainedInjector(injector, ngModule.injector) : injector;
const rootViewInjector =
ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
const rendererFactory =
rootViewInjector.get(RendererFactory2, domRendererFactory3 as RendererFactory2) as
RendererFactory3;
rootViewInjector.get(RendererFactory2, domRendererFactory3) as RendererFactory3;
const sanitizer = rootViewInjector.get(Sanitizer, null);
const hostRenderer = rendererFactory.createRenderer(null, this.componentDef);
@@ -48,7 +48,7 @@ import {LView, TData} from './view';
* index + 7: cumulative bloom filter
* index + 8: cumulative bloom filter
* index + TNODE: TNode associated with this `NodeInjector`
* `const tNode = tView.data[index + NodeInjectorOffset.TNODE]`
* `canst tNode = tView.data[index + NodeInjectorOffset.TNODE]`
* ```
*/
export const enum NodeInjectorOffset {
+10 -2
View File
@@ -6,5 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
export const MATH_ML_NAMESPACE = 'http://www.w3.org/1998/MathML/';
export const SVG_NAMESPACE = 'svg';
export const SVG_NAMESPACE_URI = 'http://www.w3.org/2000/svg';
export const MATH_ML_NAMESPACE = 'math';
export const MATH_ML_NAMESPACE_URI = 'http://www.w3.org/1998/MathML/';
export function getNamespaceUri(namespace: string): string|null {
const name = namespace.toLowerCase();
return name === SVG_NAMESPACE ? SVG_NAMESPACE_URI :
(name === MATH_ML_NAMESPACE ? MATH_ML_NAMESPACE_URI : null);
}
@@ -25,6 +25,7 @@ import {isProceduralRenderer, ProceduralRenderer3, Renderer3, unusedValueExportT
import {RComment, RElement, RNode, RText} from './interfaces/renderer_dom';
import {isLContainer, isLView} from './interfaces/type_checks';
import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DestroyHookData, FLAGS, HookData, HookFn, HOST, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView, TViewType, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
import {getNamespaceUri} from './namespaces';
import {assertTNodeType} from './node_assert';
import {profiler, ProfilerEvent} from './profiler';
import {getLViewParent} from './util/view_traversal_utils';
@@ -132,8 +133,9 @@ export function createElementNode(
if (isProceduralRenderer(renderer)) {
return renderer.createElement(name, namespace);
} else {
return namespace === null ? renderer.createElement(name) :
renderer.createElementNS(namespace, name);
const namespaceUri = namespace !== null ? getNamespaceUri(namespace) : null;
return namespaceUri === null ? renderer.createElement(name) :
renderer.createElementNS(namespaceUri, name);
}
}
+2 -1
View File
@@ -153,7 +153,8 @@ export class NgZone {
}
static isInAngularZone(): boolean {
return Zone.current.get('isAngularZone') === true;
// Zone needs to be checked, because this method might be called even when NoopNgZone is used.
return typeof Zone !== 'undefined' && Zone.current.get('isAngularZone') === true;
}
static assertInAngularZone(): void {
-599
View File
@@ -3546,603 +3546,4 @@ describe('di', () => {
TestBed.configureTestingModule({declarations: [App]});
expect(() => TestBed.createComponent(App)).toThrowError(/NullInjectorError/);
});
describe('injector when creating embedded view', () => {
const token = new InjectionToken<string>('greeting');
@Directive({selector: 'menu-trigger'})
class MenuTrigger {
@Input('triggerFor') menu!: TemplateRef<unknown>;
constructor(private viewContainerRef: ViewContainerRef) {}
open(injector: Injector|undefined) {
this.viewContainerRef.createEmbeddedView(this.menu, undefined, {injector});
}
}
it('should be able to provide an injection token through a custom injector', () => {
@Directive({selector: 'menu'})
class Menu {
constructor(@Inject(token) public tokenValue: string) {}
}
@Component({
template: `
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
<ng-template #menuTemplate>
<menu></menu>
</ng-template>
`
})
class App {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
@ViewChild(Menu) menu!: Menu;
}
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu]});
const injector = Injector.create({providers: [{provide: token, useValue: 'hello'}]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.trigger.open(injector);
fixture.detectChanges();
expect(fixture.componentInstance.menu.tokenValue).toBe('hello');
});
it('should be able to provide an injection token to a nested template through a custom injector',
() => {
@Directive({selector: 'menu'})
class Menu {
constructor(@Inject(token) public tokenValue: string) {}
}
@Component({
template: `
<menu-trigger #outerTrigger [triggerFor]="outerTemplate"></menu-trigger>
<ng-template #outerTemplate>
<menu></menu>
<menu-trigger #innerTrigger [triggerFor]="innerTemplate"></menu-trigger>
<ng-template #innerTemplate>
<menu #innerMenu></menu>
</ng-template>
</ng-template>
`
})
class App {
@ViewChild('outerTrigger', {read: MenuTrigger}) outerTrigger!: MenuTrigger;
@ViewChild('innerTrigger', {read: MenuTrigger}) innerTrigger!: MenuTrigger;
@ViewChild('innerMenu', {read: Menu}) innerMenu!: Menu;
}
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.outerTrigger.open(
Injector.create({providers: [{provide: token, useValue: 'hello'}]}));
fixture.detectChanges();
fixture.componentInstance.innerTrigger.open(undefined);
fixture.detectChanges();
expect(fixture.componentInstance.innerMenu.tokenValue).toBe('hello');
});
it('should be able to resolve a token from a custom grandparent injector if the token is not provided in the parent',
() => {
@Directive({selector: 'menu'})
class Menu {
constructor(@Inject(token) public tokenValue: string) {}
}
@Component({
template: `
<menu-trigger #grandparentTrigger [triggerFor]="grandparentTemplate"></menu-trigger>
<ng-template #grandparentTemplate>
<menu></menu>
<menu-trigger #parentTrigger [triggerFor]="parentTemplate"></menu-trigger>
<ng-template #parentTemplate>
<menu></menu>
<menu-trigger #childTrigger [triggerFor]="childTemplate"></menu-trigger>
<ng-template #childTemplate>
<menu #childMenu></menu>
</ng-template>
</ng-template>
</ng-template>
`
})
class App {
@ViewChild('grandparentTrigger', {read: MenuTrigger}) grandparentTrigger!: MenuTrigger;
@ViewChild('parentTrigger', {read: MenuTrigger}) parentTrigger!: MenuTrigger;
@ViewChild('childTrigger', {read: MenuTrigger}) childTrigger!: MenuTrigger;
@ViewChild('childMenu', {read: Menu}) childMenu!: Menu;
}
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.grandparentTrigger.open(
Injector.create({providers: [{provide: token, useValue: 'hello'}]}));
fixture.detectChanges();
fixture.componentInstance.parentTrigger.open(Injector.create({providers: []}));
fixture.detectChanges();
fixture.componentInstance.childTrigger.open(undefined);
fixture.detectChanges();
expect(fixture.componentInstance.childMenu.tokenValue).toBe('hello');
});
it('should resolve value from node injector if it is lower than embedded view injector', () => {
@Directive({selector: 'menu'})
class Menu {
constructor(@Inject(token) public tokenValue: string) {}
}
@Component({
selector: 'wrapper',
providers: [{provide: token, useValue: 'hello from wrapper'}],
template: `
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
<ng-template #menuTemplate>
<menu></menu>
</ng-template>
`
})
class Wrapper {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
@ViewChild(Menu) menu!: Menu;
}
@Component({
template: `
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
<ng-template #menuTemplate>
<wrapper></wrapper>
</ng-template>
`
})
class App {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
@ViewChild(Wrapper) wrapper!: Wrapper;
}
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu, Wrapper]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.trigger.open(
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]}));
fixture.detectChanges();
fixture.componentInstance.wrapper.trigger.open(undefined);
fixture.detectChanges();
expect(fixture.componentInstance.wrapper.menu.tokenValue).toBe('hello from wrapper');
});
it('should be able to inject a value provided at the module level', () => {
@Directive({selector: 'menu'})
class Menu {
constructor(@Inject(token) public tokenValue: string) {}
}
@Component({
template: `
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
<ng-template #menuTemplate>
<menu></menu>
</ng-template>
`
})
class App {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
@ViewChild(Menu) menu!: Menu;
}
@NgModule({
declarations: [App, MenuTrigger, Menu],
exports: [App, MenuTrigger, Menu],
providers: [{provide: token, useValue: 'hello'}]
})
class Module {
}
TestBed.configureTestingModule({imports: [Module]});
const injector = Injector.create({providers: []});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.trigger.open(injector);
fixture.detectChanges();
expect(fixture.componentInstance.menu.tokenValue).toBe('hello');
});
it('should have value from custom injector take precedence over module injector', () => {
@Directive({selector: 'menu'})
class Menu {
constructor(@Inject(token) public tokenValue: string) {}
}
@Component({
template: `
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
<ng-template #menuTemplate>
<menu></menu>
</ng-template>
`
})
class App {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
@ViewChild(Menu) menu!: Menu;
}
@NgModule({
declarations: [App, MenuTrigger, Menu],
exports: [App, MenuTrigger, Menu],
providers: [{provide: token, useValue: 'hello from module'}]
})
class Module {
}
TestBed.configureTestingModule({imports: [Module]});
const injector =
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.trigger.open(injector);
fixture.detectChanges();
expect(fixture.componentInstance.menu.tokenValue).toBe('hello from injector');
});
it('should be able to inject built-in tokens when a custom injector is provided', () => {
@Directive({selector: 'menu'})
class Menu {
constructor(public elementRef: ElementRef, public changeDetectorRef: ChangeDetectorRef) {}
}
@Component({
template: `
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
<ng-template #menuTemplate>
<menu></menu>
</ng-template>
`
})
class App {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
@ViewChild(Menu) menu!: Menu;
}
TestBed.configureTestingModule({declarations: [App, MenuTrigger, Menu]});
const injector = Injector.create({providers: []});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.trigger.open(injector);
fixture.detectChanges();
expect(fixture.componentInstance.menu.elementRef.nativeElement)
.toBe(fixture.nativeElement.querySelector('menu'));
expect(fixture.componentInstance.menu.changeDetectorRef).toBeTruthy();
});
it('should have value from parent component injector take precedence over module injector',
() => {
@Directive({selector: 'menu'})
class Menu {
constructor(@Inject(token) public tokenValue: string) {}
}
@Component({
template: `
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
<ng-template #menuTemplate>
<menu></menu>
</ng-template>
`,
providers: [{provide: token, useValue: 'hello from parent'}]
})
class App {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
@ViewChild(Menu) menu!: Menu;
}
@NgModule({
declarations: [App, MenuTrigger, Menu],
exports: [App, MenuTrigger, Menu],
providers: [{provide: token, useValue: 'hello from module'}]
})
class Module {
}
TestBed.configureTestingModule({imports: [Module]});
const injector = Injector.create({providers: []});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.trigger.open(injector);
fixture.detectChanges();
expect(fixture.componentInstance.menu.tokenValue).toBe('hello from parent');
});
it('should be able to inject an injectable with dependencies', () => {
@Injectable()
class Greeter {
constructor(@Inject(token) private tokenValue: string) {}
greet() {
return `hello from ${this.tokenValue}`;
}
}
@Directive({selector: 'menu'})
class Menu {
constructor(public greeter: Greeter) {}
}
@Component({
template: `
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
<ng-template #menuTemplate>
<menu></menu>
</ng-template>
`
})
class App {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
@ViewChild(Menu) menu!: Menu;
}
@NgModule({
declarations: [App, MenuTrigger, Menu],
exports: [App, MenuTrigger, Menu],
providers: [{provide: token, useValue: 'module'}]
})
class Module {
}
TestBed.configureTestingModule({imports: [Module]});
const injector = Injector.create({
providers: [
{provide: Greeter, useClass: Greeter},
{provide: token, useValue: 'injector'},
]
});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.trigger.open(injector);
fixture.detectChanges();
expect(fixture.componentInstance.menu.greeter.greet()).toBe('hello from injector');
});
it('should be able to inject a value from a grandparent component when a custom injector is provided',
() => {
@Directive({selector: 'menu'})
class Menu {
constructor(@Inject(token) public tokenValue: string) {}
}
@Component({
selector: 'parent',
template: `
<menu-trigger [triggerFor]="menuTemplate"></menu-trigger>
<ng-template #menuTemplate>
<menu></menu>
</ng-template>
`
})
class Parent {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
@ViewChild(Menu) menu!: Menu;
}
@Component({
template: '<parent></parent>',
providers: [{provide: token, useValue: 'hello from grandparent'}]
})
class GrandParent {
@ViewChild(Parent) parent!: Parent;
}
TestBed.configureTestingModule({declarations: [GrandParent, Parent, MenuTrigger, Menu]});
const injector = Injector.create({providers: []});
const fixture = TestBed.createComponent(GrandParent);
fixture.detectChanges();
fixture.componentInstance.parent.trigger.open(injector);
fixture.detectChanges();
expect(fixture.componentInstance.parent.menu.tokenValue).toBe('hello from grandparent');
});
it('should be able to use a custom injector when created through TemplateRef', () => {
let injectedValue: string|undefined;
@Directive({selector: 'menu'})
class Menu {
constructor(@Inject(token) tokenValue: string) {
injectedValue = tokenValue;
}
}
@Component({
template: `
<ng-template>
<menu></menu>
</ng-template>
`
})
class App {
@ViewChild(TemplateRef) template!: TemplateRef<unknown>;
}
@NgModule({
declarations: [App, Menu],
exports: [App, Menu],
providers: [{provide: token, useValue: 'hello from module'}]
})
class Module {
}
TestBed.configureTestingModule({imports: [Module]});
const injector =
Injector.create({providers: [{provide: token, useValue: 'hello from injector'}]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.template.createEmbeddedView({}, injector);
fixture.detectChanges();
expect(injectedValue).toBe('hello from injector');
});
it('should use a custom injector when the view is created outside of the declaration view',
() => {
const declarerToken = new InjectionToken<string>('declarerToken');
const creatorToken = new InjectionToken<string>('creatorToken');
@Directive({selector: 'menu'})
class Menu {
constructor(
@Inject(token) public tokenValue: string,
@Optional() @Inject(declarerToken) public declarerTokenValue: string,
@Optional() @Inject(creatorToken) public creatorTokenValue: string) {}
}
@Component({
selector: 'declarer',
template: '<ng-template><menu></menu></ng-template>',
providers: [{provide: declarerToken, useValue: 'hello from declarer'}]
})
class Declarer {
@ViewChild(Menu) menu!: Menu;
@ViewChild(TemplateRef) template!: TemplateRef<unknown>;
}
@Component({
selector: 'creator',
template: '<menu-trigger></menu-trigger>',
providers: [{provide: creatorToken, useValue: 'hello from creator'}]
})
class Creator {
@ViewChild(MenuTrigger) trigger!: MenuTrigger;
}
@Component({
template: `
<declarer></declarer>
<creator></creator>
`
})
class App {
@ViewChild(Declarer) declarer!: Declarer;
@ViewChild(Creator) creator!: Creator;
}
TestBed.configureTestingModule(
{declarations: [App, MenuTrigger, Menu, Declarer, Creator]});
const injector = Injector.create({providers: [{provide: token, useValue: 'hello'}]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const {declarer, creator} = fixture.componentInstance;
creator.trigger.menu = declarer.template;
creator.trigger.open(injector);
fixture.detectChanges();
expect(declarer.menu.tokenValue).toBe('hello');
expect(declarer.menu.declarerTokenValue).toBe('hello from declarer');
expect(declarer.menu.creatorTokenValue).toBeNull();
});
it('should behave consistently with `createComponent` when token is shadowed in node injector',
() => {
@Directive({selector: 'trigger'})
class Trigger {
constructor(public viewContainerRef: ViewContainerRef) {}
}
@Directive({selector: 'overlay'})
class Overlay {
constructor(@Inject(token) public tokenValue: string) {}
}
@Component({
selector: 'overlay-host',
template: '<overlay></overlay>',
providers: [{provide: token, useValue: 'hello from parent'}],
})
class OverlayHost {
@ViewChild(Overlay) overlay!: Overlay;
}
@Component({
selector: 'wrapper',
template: '<ng-content></ng-content>',
providers: [{provide: token, useValue: 'hello from parent'}],
})
class Wrapper {
}
@Component({
template: `
<trigger></trigger>
<wrapper>
<ng-template #template>
<overlay></overlay>
</ng-template>
</wrapper>
`
})
class App {
@ViewChild(Trigger) trigger!: Trigger;
@ViewChild('template', {read: TemplateRef}) template!: TemplateRef<any>;
@ViewChild(Overlay) overlayInTemplate!: Overlay;
openFromTemplate(injector: Injector) {
this.trigger.viewContainerRef.createEmbeddedView(this.template, null, {injector});
}
openFromComponent(injector: Injector) {
return this.trigger.viewContainerRef.createComponent(OverlayHost, {injector});
}
}
TestBed.configureTestingModule(
{declarations: [App, Trigger, Overlay, OverlayHost, Wrapper]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const providers = [{provide: token, useValue: 'hello from custom injector'}];
fixture.componentInstance.openFromTemplate(Injector.create({providers}));
fixture.detectChanges();
const componentRef =
fixture.componentInstance.openFromComponent(Injector.create({providers}));
fixture.detectChanges();
// The node injector is expected to take precedence over the provided injector, despite
// technically being higher in the tree, because the custom one is provided as a module
// injector. This is consistent with how `createComponent` has always worked and avoids
// ambiguity as to whether the provided injector should be in the declaration or insertion
// node injector tree.
expect(fixture.componentInstance.overlayInTemplate.tokenValue).toBe('hello from parent');
expect(componentRef.instance.overlay.tokenValue).toBe('hello from parent');
});
});
});
@@ -542,10 +542,6 @@ describe('runtime i18n', () => {
TestBed.configureTestingModule({
providers: [
{provide: DOCUMENT, useFactory: _document, deps: []},
// TODO(FW-811): switch back to default server renderer (i.e. remove the line
// below) once it starts to support Ivy namespace format (URIs) correctly. For
// now, use `DomRenderer` that supports Ivy namespace format.
{provide: RendererFactory2, useClass: DomRendererFactory2}
],
});
});
@@ -197,10 +197,6 @@ describe('ViewContainerRef', () => {
declarations: [TestComp, SvgComp, MathMLComp],
providers: [
{provide: DOCUMENT, useFactory: _document, deps: []},
// TODO(FW-811): switch back to default server renderer (i.e. remove the line below)
// once it starts to support Ivy namespace format (URIs) correctly. For now, use
// `DomRenderer` that supports Ivy namespace format.
{provide: RendererFactory2, useClass: DomRendererFactory2}
],
});
const fixture = TestBed.createComponent(TestComp);
+1
View File
@@ -12,6 +12,7 @@ yarn bazel run //packages/core/test/bundling/forms:symbol_test.accept
yarn bazel run //packages/core/test/bundling/hello_world:symbol_test.accept
yarn bazel run //packages/core/test/bundling/router:symbol_test.accept
yarn bazel run //packages/core/test/bundling/todo:symbol_test.accept
yarn bazel run //packages/core/test/bundling/animations:symbol_test.accept
```
## Running all symbol tests
@@ -0,0 +1,64 @@
load("//tools:defaults.bzl", "app_bundle", "jasmine_node_test", "ng_module", "ts_library")
load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test")
load("@npm//http-server:index.bzl", "http_server")
package(default_visibility = ["//visibility:public"])
ng_module(
name = "animations",
srcs = ["index.ts"],
deps = [
"//packages/animations",
"//packages/core",
"//packages/platform-browser",
"//packages/platform-browser/animations",
],
)
app_bundle(
name = "bundle",
entry_point = ":index.ts",
deps = [
":animations",
"@npm//rxjs",
],
)
ts_library(
name = "test_lib",
testonly = True,
srcs = glob(["*_spec.ts"]),
deps = [
"//packages:types",
"//packages/compiler",
"//packages/core",
"//packages/core/testing",
"//packages/private/testing",
],
)
jasmine_node_test(
name = "test",
data = [
":bundle.debug.min.js",
":bundle.js",
":bundle.min.js",
":bundle.min.js.br",
],
deps = [":test_lib"],
)
js_expected_symbol_test(
name = "symbol_test",
src = ":bundle.debug.min.js",
golden = ":bundle.golden_symbols.json",
)
http_server(
name = "prodserver",
data = [
"index.html",
":bundle.debug.min.js",
":bundle.min.js",
],
)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
<!doctype html>
<html>
<head>
<title>Angular Animations Example</title>
</head>
<body>
<!-- The Angular application will be bootstrapped into this element. -->
<app-root></app-root>
<!--
Script tag which bootstraps the application. Use `?debug` in URL to select
the debug version of the script.
There are two scripts sources: `bundle.min.js` and `bundle.debug.min.js` You can
switch between which bundle the browser loads to experiment with the application.
- `bundle.min.js`: Is what the site would serve to their users. It has gone
through rollup, build-optimizer, and uglify with tree shaking.
- `bundle.debug.min.js`: Is what the developer would like to see when debugging
the application. It has also gone through full pipeline of esbuild, babel optimization,
plugins from the devkit and terser, however mangling is disabled and the minified
file is formatted using prettier (to ease debugging).
-->
<script>
document.write('<script src="' +
(document.location.search.endsWith('debug') ? '/bundle.debug.min.js' : '/bundle.min.js') +
'"></' + 'script>');
</script>
</body>
</html>
@@ -0,0 +1,45 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {animate, style, transition, trigger} from '@angular/animations';
import {Component, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core';
import {BrowserModule, platformBrowser} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
@Component({
selector: 'app-animations',
template: `
<div [@myAnimation]="exp"></div>
`,
animations:
[trigger('myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])])]
})
class AnimationsComponent {
exp: any = false;
}
@Component({
selector: 'app-root',
template: `
<app-animations></app-animations>
`
})
class RootComponent {
}
@NgModule({
declarations: [RootComponent, AnimationsComponent],
imports: [BrowserModule, BrowserAnimationsModule],
})
class AnimationsExampleModule {
ngDoBootstrap(app: any) {
app.bootstrap(RootComponent);
}
}
(window as any).waitForApp =
platformBrowser().bootstrapModule(AnimationsExampleModule, {ngZone: 'noop'});
@@ -0,0 +1,36 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import '@angular/compiler';
import * as fs from 'fs';
import * as path from 'path';
const UTF8 = {
encoding: 'utf-8'
};
const PACKAGE = 'angular/packages/core/test/bundling/animations';
describe('treeshaking with uglify', () => {
let content: string;
// We use the debug version as otherwise symbols/identifiers would be mangled (and the test would
// always pass)
const contentPath = require.resolve(path.join(PACKAGE, 'bundle.debug.min.js'));
beforeAll(() => {
content = fs.readFileSync(contentPath, UTF8);
});
it('should drop unused TypeScript helpers', () => {
expect(content).not.toContain('__asyncGenerator');
});
it('should not contain rxjs from commonjs distro', () => {
expect(content).not.toContain('commonjsGlobal');
expect(content).not.toContain('createCommonjsModule');
});
});
@@ -77,9 +77,6 @@
{
"name": "COMPOSITION_BUFFER_MODE"
},
{
"name": "ChainedInjector"
},
{
"name": "ChangeDetectionStrategy"
},
@@ -29,6 +29,9 @@
{
"name": "AbstractFormGroupDirective"
},
{
"name": "AbstractValidatorDirective"
},
{
"name": "AnonymousSubject"
},
@@ -77,9 +80,6 @@
{
"name": "COMPOSITION_BUFFER_MODE"
},
{
"name": "ChainedInjector"
},
{
"name": "ChangeDetectionStrategy"
},
@@ -1310,6 +1310,9 @@
{
"name": "notFoundValueOrThrow"
},
{
"name": "nullValidator"
},
{
"name": "observable"
},
@@ -1400,6 +1403,9 @@
{
"name": "renderView"
},
{
"name": "requiredValidator"
},
{
"name": "resetPreOrderHookFlags"
},
@@ -92,9 +92,6 @@
{
"name": "CatchSubscriber"
},
{
"name": "ChainedInjector"
},
{
"name": "ChangeDetectionStrategy"
},
@@ -2,9 +2,6 @@
{
"name": "CLEAN_PROMISE"
},
{
"name": "ChainedInjector"
},
{
"name": "ChangeDetectionStrategy"
},
@@ -292,6 +292,51 @@ class TestObj {
expect(isDelegateCtor(ChildWithCtorComplexBase)).toBe(false);
});
it('should deal with coverage instrumentation statements', () => {
// The Istanbul coverage tool augments code with additional statements to capture coverage
// information, and this test verifies that those do not interfere with our ability to
// detect delegate constructors.
const Es2015ClassNoCtor = `class TestService extends Parent {
constructor() {
cov_8nt6qq5zt().f[2]++;
cov_8nt6qq5zt().s[6]++;
super(...arguments);
cov_8nt6qq5zt().s[7]++;
this.foo = 'bar';
}
}`;
const Es2015ClassWithCtor = `class TestService extends Parent {
constructor() {
cov_8nt6qq5zt().f[2]++;
cov_8nt6qq5zt().s[6]++;
super();
cov_8nt6qq5zt().s[7]++;
this.foo = 'bar';
}
}`;
const Es5ClassNoCtor = `function TestService() {
cov_8nt6qq5zt().f[4]++;
var _this = (cov_8nt6qq5zt().s[8]++, (cov_8nt6qq5zt().b[0][0]++, _super !== null) && (cov_8nt6qq5zt().b[0][1]++, _super.apply(this, arguments)) || (cov_8nt6qq5zt().b[0][2]++, this));
cov_8nt6qq5zt().s[9]++;
_this.foo = 'bar';
cov_8nt6qq5zt().s[10]++;
return _this;
}`;
const Es5ClassWithCtor = `function TestService() {
cov_8nt6qq5zt().f[4]++;
var _this = _super.call();
cov_8nt6qq5zt().s[9]++;
_this.foo = 'bar';
cov_8nt6qq5zt().s[10]++;
return _this;
}`;
expect(isDelegateCtor(Es2015ClassNoCtor)).toBe(true);
expect(isDelegateCtor(Es2015ClassWithCtor)).toBe(false);
expect(isDelegateCtor(Es5ClassNoCtor)).toBe(true);
expect(isDelegateCtor(Es5ClassWithCtor)).toBe(false);
});
it('should properly handle all class forms', () => {
const ctor = (str: string) => expect(isDelegateCtor(str)).toBe(false);
const noCtor = (str: string) => expect(isDelegateCtor(str)).toBe(true);
@@ -299,6 +344,7 @@ class TestObj {
ctor(`class Bar extends Foo {constructor(){}}`);
ctor(`class Bar extends Foo { constructor ( ) {} }`);
ctor(`class Bar extends Foo { other(){}; constructor(){} }`);
ctor(`class Bar extends Foo { constructor(){} a(){super(...arguments)} }`);
noCtor(`class extends Foo{}`);
noCtor(`class extends Foo {}`);
+31 -40
View File
@@ -15,22 +15,33 @@ import {emailValidator, maxLengthValidator, maxValidator, minLengthValidator, mi
/**
* Method that updates string to integer if not already a number
*
* @param value The value to convert to integer
* @returns value of parameter in number or integer.
* @param value The value to convert to integer.
* @returns value of parameter converted to number or integer.
*/
function toInteger(value: string|number): number {
return typeof value === 'number' ? value : parseInt(value, 10);
}
/**
* Method that converts null, false or 'false' string to boolean.
*
* @param value input value.
* @returns value of parameter converted to boolean.
*/
function toBoolean(input: unknown): boolean {
return input != null && input !== false && `${input}` !== 'false';
}
/**
* Method that ensures that provided value is a float (and converts it to float if needed).
*
* @param value The value to convert to float
* @returns value of parameter in number or float.
* @param value The value to convert to float.
* @returns value of parameter converted to number or float.
*/
function toFloat(value: string|number): number {
return typeof value === 'number' ? value : parseFloat(value);
}
/**
* @description
* Defines the map of errors returned from failed validation checks.
@@ -356,41 +367,27 @@ export const CHECKBOX_REQUIRED_VALIDATOR: StaticProvider = {
selector:
':not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]',
providers: [REQUIRED_VALIDATOR],
host: {'[attr.required]': 'required ? "" : null'}
host: {'[attr.required]': '_enabled ? "" : null'}
})
export class RequiredValidator implements Validator {
private _required = false;
private _onChange?: () => void;
export class RequiredValidator extends AbstractValidatorDirective {
/**
* @description
* Tracks changes to the required attribute bound to this directive.
*/
@Input()
get required(): boolean|string {
return this._required;
}
@Input() required!: boolean|string;
set required(value: boolean|string) {
this._required = value != null && value !== false && `${value}` !== 'false';
if (this._onChange) this._onChange();
}
/** @internal */
override inputName = 'required';
/**
* Method that validates whether the control is empty.
* Returns the validation result if enabled, otherwise null.
* @nodoc
*/
validate(control: AbstractControl): ValidationErrors|null {
return this.required ? requiredValidator(control) : null;
}
/** @internal */
override normalizeInput = (input: unknown): boolean => toBoolean(input);
/**
* Registers a callback function to call when the validator inputs change.
* @nodoc
*/
registerOnValidatorChange(fn: () => void): void {
this._onChange = fn;
/** @internal */
override createValidator = (input: boolean): ValidatorFn => requiredValidator;
/** @nodoc */
override enabled(input: boolean): boolean {
return input;
}
}
@@ -420,17 +417,11 @@ export class RequiredValidator implements Validator {
selector:
'input[type=checkbox][required][formControlName],input[type=checkbox][required][formControl],input[type=checkbox][required][ngModel]',
providers: [CHECKBOX_REQUIRED_VALIDATOR],
host: {'[attr.required]': 'required ? "" : null'}
host: {'[attr.required]': '_enabled ? "" : null'}
})
export class CheckboxRequiredValidator extends RequiredValidator {
/**
* Method that validates whether or not the checkbox has been checked.
* Returns the validation result if enabled, otherwise null.
* @nodoc
*/
override validate(control: AbstractControl): ValidationErrors|null {
return this.required ? requiredTrueValidator(control) : null;
}
/** @internal */
override createValidator = (input: unknown): ValidatorFn => requiredTrueValidator;
}
/**
@@ -2268,6 +2268,21 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
expect(checkbox.nativeElement.checked).toBe(true);
expect(control.hasError('required')).toEqual(false);
checkbox.nativeElement.required = false;
dispatchEvent(checkbox.nativeElement, 'change');
fixture.detectChanges();
expect(checkbox.nativeElement.checked).toBe(true);
expect(control.hasError('required')).toEqual(false);
checkbox.nativeElement.checked = false;
checkbox.nativeElement.required = true;
dispatchEvent(checkbox.nativeElement, 'change');
fixture.detectChanges();
expect(checkbox.nativeElement.checked).toBe(false);
expect(control.hasError('required')).toEqual(true);
});
// Note: this scenario goes against validator function rules were `null` is the only
@@ -1315,6 +1315,14 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
expect(input.nativeElement.checked).toBe(false);
expect(control.hasError('required')).toBe(true);
fixture.componentInstance.required = false;
dispatchEvent(input.nativeElement, 'change');
fixture.detectChanges();
tick();
expect(input.nativeElement.checked).toBe(false);
expect(control.hasError('required')).toBe(false);
}));
it('should validate email', fakeAsync(() => {
+1 -1
View File
@@ -183,7 +183,7 @@ function toAttributeCssSelector(attribute: t.TextAttribute|t.BoundAttribute|t.Bo
}
function getNodeName(node: t.Template|t.Element): string {
return node instanceof t.Template ? node.tagName : node.name;
return node instanceof t.Template ? (node.tagName ?? 'ng-template') : node.name;
}
/**
@@ -384,7 +384,7 @@ describe('getSemanticDiagnostics', () => {
expect(diags[0].category).toEqual(ts.DiagnosticCategory.Warning);
});
it('should not produce invalid banana in box warning without the required flags', () => {
it('should not produce invalid banana in box warning without `strictTemplates`', () => {
const files = {
'app.ts': `
import {Component} from '@angular/core';
@@ -397,7 +397,9 @@ describe('getSemanticDiagnostics', () => {
}
`
};
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
const project = createModuleAndProjectWithDeclarations(env, 'test', files, {
strictTemplates: false,
});
const diags = project.getDiagnosticsForFile('app.ts');
expect(diags.length).toEqual(0);
@@ -426,7 +428,7 @@ describe('getSemanticDiagnostics', () => {
expect(diags[0].category).toEqual(ts.DiagnosticCategory.Warning);
});
it('should not produce invalid banana in box warning in external html file without the required flags',
it('should not produce invalid banana in box warning in external html file without `strictTemplates`',
() => {
const files = {
'app.ts': `
@@ -441,7 +443,9 @@ describe('getSemanticDiagnostics', () => {
`,
'app.html': `<div ([foo])="bar"></div>`
};
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
const project = createModuleAndProjectWithDeclarations(env, 'test', files, {
strictTemplates: false,
});
const diags = project.getDiagnosticsForFile('app.html');
expect(diags.length).toEqual(0);
@@ -17,6 +17,7 @@ export const NAMESPACE_URIS: {[ns: string]: string} = {
'xlink': 'http://www.w3.org/1999/xlink',
'xml': 'http://www.w3.org/XML/1998/namespace',
'xmlns': 'http://www.w3.org/2000/xmlns/',
'math': 'http://www.w3.org/1998/MathML/',
};
const COMPONENT_REGEX = /%COMP%/g;
@@ -144,9 +145,7 @@ class DefaultDomRenderer2 implements Renderer2 {
createElement(name: string, namespace?: string): any {
if (namespace) {
// In cases where Ivy (not ViewEngine) is giving us the actual namespace, the look up by key
// will result in undefined, so we just return the namespace here.
return document.createElementNS(NAMESPACE_URIS[namespace] || namespace, name);
return document.createElementNS(NAMESPACE_URIS[namespace], name);
}
return document.createElement(name);
@@ -199,8 +198,6 @@ class DefaultDomRenderer2 implements Renderer2 {
setAttribute(el: any, name: string, value: string, namespace?: string): void {
if (namespace) {
name = namespace + ':' + name;
// TODO(FW-811): Ivy may cause issues here because it's passing around
// full URIs for namespaces, therefore this lookup will fail.
const namespaceUri = NAMESPACE_URIS[namespace];
if (namespaceUri) {
el.setAttributeNS(namespaceUri, name, value);
@@ -214,15 +211,10 @@ class DefaultDomRenderer2 implements Renderer2 {
removeAttribute(el: any, name: string, namespace?: string): void {
if (namespace) {
// TODO(FW-811): Ivy may cause issues here because it's passing around
// full URIs for namespaces, therefore this lookup will fail.
const namespaceUri = NAMESPACE_URIS[namespace];
if (namespaceUri) {
el.removeAttributeNS(namespaceUri, name);
} else {
// TODO(FW-811): Since ivy is passing around full URIs for namespaces
// this could result in properties like `http://www.w3.org/2000/svg:cx="123"`,
// which is wrong.
el.removeAttribute(`${namespace}:${name}`);
}
} else {
@@ -72,8 +72,6 @@ class DefaultServerRenderer2 implements Renderer2 {
createElement(name: string, namespace?: string, debugInfo?: any): any {
if (namespace) {
const doc = this.document || getDOM().getDefaultDocument();
// TODO(FW-811): Ivy may cause issues here because it's passing around
// full URIs for namespaces, therefore this lookup will fail.
return doc.createElementNS(NAMESPACE_URIS[namespace], name);
}
@@ -131,8 +129,6 @@ class DefaultServerRenderer2 implements Renderer2 {
setAttribute(el: any, name: string, value: string, namespace?: string): void {
if (namespace) {
// TODO(FW-811): Ivy may cause issues here because it's passing around
// full URIs for namespaces, therefore this lookup will fail.
el.setAttributeNS(NAMESPACE_URIS[namespace], namespace + ':' + name, value);
} else {
el.setAttribute(name, value);
@@ -141,8 +137,6 @@ class DefaultServerRenderer2 implements Renderer2 {
removeAttribute(el: any, name: string, namespace?: string): void {
if (namespace) {
// TODO(FW-811): Ivy may cause issues here because it's passing around
// full URIs for namespaces, therefore this lookup will fail.
el.removeAttributeNS(NAMESPACE_URIS[namespace], name);
} else {
el.removeAttribute(name);
@@ -10,7 +10,7 @@ import {animate, AnimationBuilder, state, style, transition, trigger} from '@ang
import {DOCUMENT, isPlatformServer, PlatformLocation, ɵgetDOM as getDOM} from '@angular/common';
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {ApplicationRef, CompilerFactory, Component, destroyPlatform, getPlatform, HostListener, Inject, Injectable, Input, NgModule, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation} from '@angular/core';
import {ApplicationRef, CompilerFactory, Component, destroyPlatform, getPlatform, HostBinding, HostListener, Inject, Injectable, Input, NgModule, NgZone, OnInit, PLATFORM_ID, PlatformRef, ViewEncapsulation} from '@angular/core';
import {inject, waitForAsync} from '@angular/core/testing';
import {BrowserModule, makeStateKey, Title, TransferState} from '@angular/platform-browser';
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, platformDynamicServer, PlatformState, renderModule, renderModuleFactory, ServerModule, ServerTransferStateModule} from '@angular/platform-server';
@@ -20,11 +20,16 @@ import {first} from 'rxjs/operators';
@Component({selector: 'app', template: `Works!`})
class MyServerApp {
}
@NgModule({
declarations: [MyServerApp],
exports: [MyServerApp],
})
export class MyServerAppModule {
}
@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
imports: [ServerModule],
imports: [MyServerAppModule, ServerModule],
})
class ExampleModule {
}
@@ -73,8 +78,8 @@ function asyncRejectRenderHook() {
@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
imports:
[MyServerAppModule, BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
providers: [
{provide: BEFORE_APP_SERIALIZED, useFactory: getTitleRenderHook, multi: true, deps: [DOCUMENT]},
]
@@ -84,8 +89,8 @@ class RenderHookModule {
@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
imports:
[MyServerAppModule, BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
providers: [
{provide: BEFORE_APP_SERIALIZED, useFactory: getTitleRenderHook, multi: true, deps: [DOCUMENT]},
{provide: BEFORE_APP_SERIALIZED, useValue: exceptionRenderHook, multi: true},
@@ -97,8 +102,8 @@ class MultiRenderHookModule {
@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
imports:
[MyServerAppModule, BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
providers: [
{
provide: BEFORE_APP_SERIALIZED,
@@ -112,8 +117,8 @@ class AsyncRenderHookModule {
}
@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
imports:
[MyServerAppModule, BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
providers: [
{provide: BEFORE_APP_SERIALIZED, useFactory: getMetaRenderHook, multi: true, deps: [DOCUMENT]},
{
@@ -188,7 +193,11 @@ class SVGServerModule {
@Component({
selector: 'app',
template: `<div [@myAnimation]="state">{{text}}</div>`,
template: `
<div [@myAnimation]="state">
<svg *ngIf="true"></svg>
{{text}}
</div>`,
animations: [trigger(
'myAnimation',
[
@@ -235,8 +244,7 @@ class ExampleStylesModule {
@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
imports: [ServerModule, HttpClientModule, HttpClientTestingModule],
imports: [MyServerAppModule, ServerModule, HttpClientModule, HttpClientTestingModule],
})
export class HttpClientExampleModule {
}
@@ -252,8 +260,7 @@ export class MyHttpInterceptor implements HttpInterceptor {
@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
imports: [ServerModule, HttpClientModule, HttpClientTestingModule],
imports: [MyServerAppModule, ServerModule, HttpClientModule, HttpClientTestingModule],
providers: [
{provide: HTTP_INTERCEPTORS, multi: true, useClass: MyHttpInterceptor},
],
+1 -1
View File
@@ -384,7 +384,7 @@ export interface Route {
* matches a given path and stops when there is a config match. Importantly there must still be a
* config match for each segment of the URL. For example, '/team/11/user' matches the prefix
* 'team/:id' if one of the route's children matches the segment 'user'. That is, the URL
* '/team/11/user` matches the config
* '/team/11/user' matches the config
* `{path: 'team/:id', children: [{path: ':user', component: User}]}`
* but does not match when there are no children as in `{path: 'team/:id', component: Team}`.
*
+332 -54
View File
@@ -42,23 +42,23 @@
"@angular-devkit/core" "13.1.1"
rxjs "6.6.7"
"@angular-devkit/architect@0.1301.3":
version "0.1301.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1301.3.tgz#197f92c984adf22776798ce568e64396e464a03d"
integrity sha512-fFSevgYGZHCybYoyTkZ9b1YCSthBmoi77alwWjqMhYXUNXx7yx50zJZ6Ur2v3YpctVjU6eoGc5FDFyVHwXT0Iw==
"@angular-devkit/architect@0.1301.4":
version "0.1301.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1301.4.tgz#2fc51bcae0dcb581c8be401e2fde7bbd10b43076"
integrity sha512-p6G8CEMnE+gYwxRyEttj3QGsuNJ3Kusi7iwBIzWyf2RpJSdGzXdwUEiRGg6iS0YHFr06/ZFfAWfnM2DQvNm4TA==
dependencies:
"@angular-devkit/core" "13.1.3"
"@angular-devkit/core" "13.1.4"
rxjs "6.6.7"
"@angular-devkit/build-angular@13.1.3":
version "13.1.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-13.1.3.tgz#c04cef8a2d405cb66332b674d204a2717b6807f6"
integrity sha512-C5Qv8aGmpGbETG4Mawly/5LnkRwfJAzANL5BtYJn8ZaDlZKCkhvAaRXHpm4Mdqg5idACAT8hgYqPQvqyEBaVDA==
"@angular-devkit/build-angular@13.1.4":
version "13.1.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-13.1.4.tgz#942023fca240b0f5753dcf65bb67e57e4779bf86"
integrity sha512-MTvlUCb02J4ODXDsnit4N0PR9PkpKeSYpTPueaSBuWTBeP3dvMPZQabvb3C5QT/5yUzBiXQZq11QYx3Gui4StA==
dependencies:
"@ampproject/remapping" "1.0.2"
"@angular-devkit/architect" "0.1301.3"
"@angular-devkit/build-webpack" "0.1301.3"
"@angular-devkit/core" "13.1.3"
"@angular-devkit/architect" "0.1301.4"
"@angular-devkit/build-webpack" "0.1301.4"
"@angular-devkit/core" "13.1.4"
"@babel/core" "7.16.0"
"@babel/generator" "7.16.0"
"@babel/helper-annotate-as-pure" "7.16.0"
@@ -69,7 +69,7 @@
"@babel/runtime" "7.16.3"
"@babel/template" "7.16.0"
"@discoveryjs/json-ext" "0.5.6"
"@ngtools/webpack" "13.1.3"
"@ngtools/webpack" "13.1.4"
ansi-colors "4.1.1"
babel-loader "8.2.3"
babel-plugin-istanbul "6.1.1"
@@ -99,7 +99,7 @@
postcss "8.4.4"
postcss-import "14.0.2"
postcss-loader "6.2.1"
postcss-preset-env "6.7.0"
postcss-preset-env "7.2.3"
regenerator-runtime "0.13.9"
resolve-url-loader "4.0.0"
rxjs "6.6.7"
@@ -194,10 +194,10 @@
optionalDependencies:
esbuild "0.14.2"
"@angular-devkit/build-optimizer@0.1301.3":
version "0.1301.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.1301.3.tgz#d4025ca89f1eff89d5d6243d85ea7ae0acebaf02"
integrity sha512-ZLPPXDUUQWUxYeNR7NGnuMdi2IKJU4WMMg4LmwSlEJzulKO6MbjreXQsQtG1ILxXmVe/eL1v2XVaX0/AyTr9Zw==
"@angular-devkit/build-optimizer@0.1301.4":
version "0.1301.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.1301.4.tgz#69441c53954a7bec4a992fe2c725fa668ee1fec5"
integrity sha512-w09qgZ6oZ8TmLln4IvQuSe/664UITs+pW2PKA4Ya3N4bmnja+Sz7McoH5ifNnBw8QWUgJy9rSDj13bOoe1t7WA==
dependencies:
source-map "0.7.3"
tslib "2.3.1"
@@ -221,12 +221,12 @@
"@angular-devkit/architect" "0.1301.1"
rxjs "6.6.7"
"@angular-devkit/build-webpack@0.1301.3":
version "0.1301.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1301.3.tgz#4f8f9fd9e09992aaf904c4457f268b203c19b45d"
integrity sha512-FFwKdhq5n0lrqkiJRZoWKy21gERtvupkk0BpIVPTbRqyiqB2htiGM995uBBjpeDngytDLx+BwPFipVfQ+WIi9w==
"@angular-devkit/build-webpack@0.1301.4":
version "0.1301.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1301.4.tgz#7dd16ec7fb26c5e177ad26a6f485faf4393774ef"
integrity sha512-IcC3Y5WhreIV0uT90ITqJVgRqFjGwH72hftwLxkslaX+FJDcL36mhsNdyN66BJiuX7R85xUxHq3PEb9cZrllJA==
dependencies:
"@angular-devkit/architect" "0.1301.3"
"@angular-devkit/architect" "0.1301.4"
rxjs "6.6.7"
"@angular-devkit/core@13.1.1":
@@ -241,10 +241,10 @@
rxjs "6.6.7"
source-map "0.7.3"
"@angular-devkit/core@13.1.3":
version "13.1.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-13.1.3.tgz#d1f8a6b4ad4326732a160a7549fccca1369fd108"
integrity sha512-o14jGDk4h14dVYYQafOn+2rq9CDmDMbDV6logqKYCLzTDRlK8gccDnqJM/QKAlfWCzbllZqcHDmg6FyoRLO9RQ==
"@angular-devkit/core@13.1.4":
version "13.1.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-13.1.4.tgz#b5b6ddd674ae351f83beff2e4a0d702096bdfd47"
integrity sha512-225Gjy4iVxh5Jo9njJnaG75M/Dt95UW+dEPCGWKV5E/++7UUlXlo9sNWq8x2vJm2nhtsPkpnXNOt4pW1mIDwqQ==
dependencies:
ajv "8.8.2"
ajv-formats "2.1.1"
@@ -253,12 +253,12 @@
rxjs "6.6.7"
source-map "0.7.3"
"@angular-devkit/schematics@13.1.3":
version "13.1.3"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-13.1.3.tgz#2328f9cf63d6f556392d96b73af794fc52bfc87f"
integrity sha512-TvjThB/pFXNFM0j0WX5yg0L2/3xNsqawQuWhkDJ05MBDEnSxbgv5hmOzNL8SNIEMgP0VbSTHtSg5kZvmNiH7vg==
"@angular-devkit/schematics@13.1.4":
version "13.1.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-13.1.4.tgz#e8ed817887aa51268dec27d8d6188e2f3f10742a"
integrity sha512-yBa7IeC4cLZ7s137NAQD+sMB5c6SI6UJ6xYxl6J/CvV2RLGOZZA93i4GuRALi5s82eLi1fH+HEL/gvf3JQynzQ==
dependencies:
"@angular-devkit/core" "13.1.3"
"@angular-devkit/core" "13.1.4"
jsonc-parser "3.0.0"
magic-string "0.25.7"
ora "5.4.1"
@@ -279,15 +279,15 @@
"@angular/core" "^10.0.0-0 || ^11.0.0"
reflect-metadata "^0.1.13"
"@angular/cli@13.1.3":
version "13.1.3"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-13.1.3.tgz#d143f30ee67481cc315e0d18fecb076101dfa280"
integrity sha512-Ju/A8LFnfcv1PC665a5FiIQx9SXqB+3yWYFXPIiVkkRcye95gpfsbV48WW7QV35gzIwbR1m3H907Zg6ptiNv0A==
"@angular/cli@13.1.4":
version "13.1.4"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-13.1.4.tgz#34e6e87d1c6950408167c41293cf2cd5d1e00a2e"
integrity sha512-PP9xpvDDCHhLTIZjewQQzzf+JbpF2s5mXTW2AgIL/E53ukaVvXwwjFMt9dQvECwut/LDpThoc3OfqcGrmwtqnA==
dependencies:
"@angular-devkit/architect" "0.1301.3"
"@angular-devkit/core" "13.1.3"
"@angular-devkit/schematics" "13.1.3"
"@schematics/angular" "13.1.3"
"@angular-devkit/architect" "0.1301.4"
"@angular-devkit/core" "13.1.4"
"@angular-devkit/schematics" "13.1.4"
"@schematics/angular" "13.1.4"
"@yarnpkg/lockfile" "1.1.0"
ansi-colors "4.1.1"
debug "4.3.3"
@@ -1529,10 +1529,10 @@
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-13.1.1.tgz#736988cda569b9b1eb925908ebe9ccf3faa095eb"
integrity sha512-TGCuscGFNHPoXScswuj9UxNv8E+A/PXs5XH8cyTtbkmlpBec1EShnaUTdDP5nhykjjaWzd3TLChlp9DinE+Ctg==
"@ngtools/webpack@13.1.3":
version "13.1.3"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-13.1.3.tgz#f3e516da2b2a352db9d723e8cebbe15b526de14c"
integrity sha512-6Pf52IbChm/dFuegfv0smeBTW2moi0Gdkyjgk/7VWqE6hN35m+YGrCh+XnPp1POJwOKxhAByhV9zs6NWxrK1vA==
"@ngtools/webpack@13.1.4":
version "13.1.4"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-13.1.4.tgz#359c9078af281413cf9cabd0af06b3fca4ddbf7e"
integrity sha512-s8gzjG2nYHawFhlkHMkQWYrocHkBI1nF6T9K/oCSTIsq6kvTv//Ahno2VdBSgVq8uMnpv1TymvX0nFC4Dgn1HA==
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
version "2.1.8-no-fsevents.3"
@@ -1926,13 +1926,13 @@
colors "~1.2.1"
string-argv "~0.3.1"
"@schematics/angular@13.1.3":
version "13.1.3"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-13.1.3.tgz#c763cdf1a2e0784d5263c23b581bfeb4a4a2f12e"
integrity sha512-IixVWAEtN97N74PCxg3T03Ar/ECjGyJBWKAjKTTCrgNSWhm2mKgIc4RyI6cVCnltfJWIo48fcFhlOx/elShaCg==
"@schematics/angular@13.1.4":
version "13.1.4"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-13.1.4.tgz#8b8c9ad40403c485bae9adeb51649711651189d2"
integrity sha512-P1YsHn1LLAmdpB9X2TBuUgrvEW/KaoBbHr8ifYO8/uQEXyeiIF+So8h/dnegkYkdsr3OwQ2X/j3UF6/+HS0odg==
dependencies:
"@angular-devkit/core" "13.1.3"
"@angular-devkit/schematics" "13.1.3"
"@angular-devkit/core" "13.1.4"
"@angular-devkit/schematics" "13.1.4"
jsonc-parser "3.0.0"
"@sindresorhus/is@^0.14.0":
@@ -3243,6 +3243,18 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autoprefixer@^10.4.2:
version "10.4.2"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b"
integrity sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==
dependencies:
browserslist "^4.19.1"
caniuse-lite "^1.0.30001297"
fraction.js "^4.1.2"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
autoprefixer@^9.6.1:
version "9.8.8"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a"
@@ -3600,6 +3612,17 @@ browserslist@^4.18.1:
node-releases "^2.0.1"
picocolors "^1.0.0"
browserslist@^4.19.1:
version "4.19.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3"
integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==
dependencies:
caniuse-lite "^1.0.30001286"
electron-to-chromium "^1.4.17"
escalade "^3.1.1"
node-releases "^2.0.1"
picocolors "^1.0.0"
browserstack@^1.5.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/browserstack/-/browserstack-1.6.1.tgz#e051f9733ec3b507659f395c7a4765a1b1e358b3"
@@ -3860,6 +3883,11 @@ caniuse-lite@^1.0.30001280:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz#3e9debad420419618cfdf52dc9b6572b28a8fff6"
integrity sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ==
caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001299:
version "1.0.30001300"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz#11ab6c57d3eb6f964cba950401fd00a146786468"
integrity sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==
canonical-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d"
@@ -4824,6 +4852,13 @@ css-blank-pseudo@^0.1.4:
dependencies:
postcss "^7.0.5"
css-blank-pseudo@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.2.tgz#f8660f6a48b17888a9277e53f25cc5abec1f0169"
integrity sha512-hOb1LFjRR+8ocA071xUSmg5VslJ8NGo/I2qpUpdeAYyBVCgupS5O8SEVo4SxEMYyFBNodBkzG3T1iqW9HCXxew==
dependencies:
postcss-selector-parser "^6.0.8"
css-has-pseudo@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee"
@@ -4832,6 +4867,13 @@ css-has-pseudo@^0.10.0:
postcss "^7.0.6"
postcss-selector-parser "^5.0.0-rc.4"
css-has-pseudo@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-3.0.3.tgz#4824a34cb92dae7e09ea1d3fd19691b653412098"
integrity sha512-0gDYWEKaGacwxCqvQ3Ypg6wGdD1AztbMm5h1JsactG2hP2eiflj808QITmuWBpE7sjSEVrAlZhPTVd/nNMj/hQ==
dependencies:
postcss-selector-parser "^6.0.8"
css-loader@6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.5.1.tgz#0c43d4fbe0d97f699c91e9818cb585759091d1b1"
@@ -4853,6 +4895,11 @@ css-prefers-color-scheme@^3.1.1:
dependencies:
postcss "^7.0.5"
css-prefers-color-scheme@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.2.tgz#d5c03a980caab92d8beeee176a8795d331e0c727"
integrity sha512-gv0KQBEM+q/XdoKyznovq3KW7ocO7k+FhPP+hQR1MenJdu0uPGS6IZa9PzlbqBeS6XcZJNAoqoFxlAUW461CrA==
css-select@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067"
@@ -4904,6 +4951,11 @@ cssdb@^4.4.0:
resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0"
integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==
cssdb@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-5.1.0.tgz#ec728d5f5c0811debd0820cbebda505d43003400"
integrity sha512-/vqjXhv1x9eGkE/zO6o8ZOI7dgdZbLVLUGyVRbPgk6YipXbW87YzUCcO+Jrmi5bwJlAH6oD+MNeZyRgXea1GZw==
cssesc@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
@@ -5631,6 +5683,11 @@ electron-to-chromium@^1.3.896:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.16.tgz#38ddecc616385e6f101359d1b978c802664157d2"
integrity sha512-BQb7FgYwnu6haWLU63/CdVW+9xhmHls3RCQUFiV4lvw3wimEHTVcUk2hkuZo76QhR8nnDdfZE7evJIZqijwPdA==
electron-to-chromium@^1.4.17:
version "1.4.49"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.49.tgz#5b6a3dc032590beef4be485a4b0b3fe7d0e3dfd7"
integrity sha512-k/0t1TRfonHIp8TJKfjBu2cKj8MqYTiEpOhci+q7CVEE5xnCQnx1pTa+V8b/sdhe4S3PR4p4iceEQWhGrKQORQ==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -6797,6 +6854,11 @@ forwarded@0.2.0:
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
fraction.js@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.2.tgz#13e420a92422b6cf244dff8690ed89401029fbe8"
integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@@ -11183,6 +11245,13 @@ postcss-attribute-case-insensitive@^4.0.1:
postcss "^7.0.2"
postcss-selector-parser "^6.0.2"
postcss-attribute-case-insensitive@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.0.tgz#39cbf6babf3ded1e4abf37d09d6eda21c644105c"
integrity sha512-b4g9eagFGq9T5SWX4+USfVyjIb3liPnjhHHRMP7FMB2kFVpYyfEscV0wP3eaXhKlcHKUut8lt5BGoeylWA/dBQ==
dependencies:
postcss-selector-parser "^6.0.2"
postcss-color-functional-notation@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0"
@@ -11191,6 +11260,13 @@ postcss-color-functional-notation@^2.0.1:
postcss "^7.0.2"
postcss-values-parser "^2.0.0"
postcss-color-functional-notation@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.1.tgz#a25e9e1855e14d04319222a689f120b3240d39e0"
integrity sha512-62OBIXCjRXpQZcFOYIXwXBlpAVWrYk8ek1rcjvMING4Q2cf0ipyN9qT+BhHA6HmftGSEnFQu2qgKO3gMscl3Rw==
dependencies:
postcss-value-parser "^4.2.0"
postcss-color-gray@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547"
@@ -11208,6 +11284,13 @@ postcss-color-hex-alpha@^5.0.3:
postcss "^7.0.14"
postcss-values-parser "^2.0.1"
postcss-color-hex-alpha@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.2.tgz#7a248b006dd47bd83063f662352d31fd982f74ec"
integrity sha512-gyx8RgqSmGVK156NAdKcsfkY3KPGHhKqvHTL3hhveFrBBToguKFzhyiuk3cljH6L4fJ0Kv+JENuPXs1Wij27Zw==
dependencies:
postcss-value-parser "^4.2.0"
postcss-color-mod-function@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d"
@@ -11225,6 +11308,13 @@ postcss-color-rebeccapurple@^4.0.1:
postcss "^7.0.2"
postcss-values-parser "^2.0.0"
postcss-color-rebeccapurple@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.0.2.tgz#5d397039424a58a9ca628762eb0b88a61a66e079"
integrity sha512-SFc3MaocHaQ6k3oZaFwH8io6MdypkUtEy/eXzXEB1vEQlO3S3oDc/FSZA8AsS04Z25RirQhlDlHLh3dn7XewWw==
dependencies:
postcss-value-parser "^4.2.0"
postcss-custom-media@^7.0.8:
version "7.0.8"
resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c"
@@ -11232,6 +11322,18 @@ postcss-custom-media@^7.0.8:
dependencies:
postcss "^7.0.14"
postcss-custom-media@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz#1be6aff8be7dc9bf1fe014bde3b71b92bb4552f1"
integrity sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g==
postcss-custom-properties@^12.1.2:
version "12.1.3"
resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.3.tgz#8e37651c7188e72e6762eeae8db39755e84d3a64"
integrity sha512-rtu3otIeY532PnEuuBrIIe+N+pcdbX/7JMZfrcL09wc78YayrHw5E8UkDfvnlOhEUrI4ptCuzXQfj+Or6spbGA==
dependencies:
postcss-value-parser "^4.2.0"
postcss-custom-properties@^8.0.11:
version "8.0.11"
resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97"
@@ -11248,6 +11350,13 @@ postcss-custom-selectors@^5.1.2:
postcss "^7.0.2"
postcss-selector-parser "^5.0.0-rc.3"
postcss-custom-selectors@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.0.tgz#022839e41fbf71c47ae6e316cb0e6213012df5ef"
integrity sha512-/1iyBhz/W8jUepjGyu7V1OPcGbc636snN1yXEQCinb6Bwt7KxsiU7/bLQlp8GwAXzCh7cobBU5odNn/2zQWR8Q==
dependencies:
postcss-selector-parser "^6.0.4"
postcss-dir-pseudo-class@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2"
@@ -11256,6 +11365,13 @@ postcss-dir-pseudo-class@^5.0.0:
postcss "^7.0.2"
postcss-selector-parser "^5.0.0-rc.3"
postcss-dir-pseudo-class@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.3.tgz#febfe305e75267913a53bf5094c7679f5cfa9b55"
integrity sha512-qiPm+CNAlgXiMf0J5IbBBEXA9l/Q5HGsNGkL3znIwT2ZFRLGY9U2fTUpa4lqCUXQOxaLimpacHeQC80BD2qbDw==
dependencies:
postcss-selector-parser "^6.0.8"
postcss-double-position-gradients@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e"
@@ -11264,6 +11380,13 @@ postcss-double-position-gradients@^1.0.0:
postcss "^7.0.5"
postcss-values-parser "^2.0.0"
postcss-double-position-gradients@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.0.4.tgz#2484b9785ef3ba81b0f03a279c52ec58fc5344c2"
integrity sha512-qz+s5vhKJlsHw8HjSs+HVk2QGFdRyC68KGRQGX3i+GcnUjhWhXQEmCXW6siOJkZ1giu0ddPwSO6I6JdVVVPoog==
dependencies:
postcss-value-parser "^4.2.0"
postcss-env-function@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7"
@@ -11272,6 +11395,13 @@ postcss-env-function@^2.0.2:
postcss "^7.0.2"
postcss-values-parser "^2.0.0"
postcss-env-function@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-4.0.4.tgz#4e85359ca4fcdde4ec4b73752a41de818dbe91cc"
integrity sha512-0ltahRTPtXSIlEZFv7zIvdEib7HN0ZbUQxrxIKn8KbiRyhALo854I/CggU5lyZe6ZBvSTJ6Al2vkZecI2OhneQ==
dependencies:
postcss-value-parser "^4.2.0"
postcss-focus-visible@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e"
@@ -11279,6 +11409,13 @@ postcss-focus-visible@^4.0.0:
dependencies:
postcss "^7.0.2"
postcss-focus-visible@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-6.0.3.tgz#14635b71a6b9140f488f11f26cbc9965a13f6843"
integrity sha512-ozOsg+L1U8S+rxSHnJJiET6dNLyADcPHhEarhhtCI9DBLGOPG/2i4ddVoFch9LzrBgb8uDaaRI4nuid2OM82ZA==
dependencies:
postcss-selector-parser "^6.0.8"
postcss-focus-within@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680"
@@ -11286,6 +11423,13 @@ postcss-focus-within@^3.0.0:
dependencies:
postcss "^7.0.2"
postcss-focus-within@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-5.0.3.tgz#0b0bf425f14a646bbfd973b463e2d20d85a3a841"
integrity sha512-fk9y2uFS6/Kpp7/A9Hz9Z4rlFQ8+tzgBcQCXAFSrXFGAbKx+4ZZOmmfHuYjCOMegPWoz0pnC6fNzi8j7Xyqp5Q==
dependencies:
postcss-selector-parser "^6.0.8"
postcss-font-variant@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641"
@@ -11293,6 +11437,11 @@ postcss-font-variant@^4.0.0:
dependencies:
postcss "^7.0.2"
postcss-font-variant@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66"
integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==
postcss-gap-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715"
@@ -11300,6 +11449,11 @@ postcss-gap-properties@^2.0.0:
dependencies:
postcss "^7.0.2"
postcss-gap-properties@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-3.0.2.tgz#562fbf43a6a721565b3ca0e01008690991d2f726"
integrity sha512-EaMy/pbxtQnKDsnbEjdqlkCkROTQZzolcLKgIE+3b7EuJfJydH55cZeHfm+MtIezXRqhR80VKgaztO/vHq94Fw==
postcss-image-set-function@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288"
@@ -11308,6 +11462,13 @@ postcss-image-set-function@^3.0.1:
postcss "^7.0.2"
postcss-values-parser "^2.0.0"
postcss-image-set-function@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-4.0.4.tgz#ce91579ab2c1386d412ff5cd5e733c474b1f75ee"
integrity sha512-BlEo9gSTj66lXjRNByvkMK9dEdEGFXRfGjKRi9fo8s0/P3oEk74cAoonl/utiM50E2OPVb/XSu+lWvdW4KtE/Q==
dependencies:
postcss-value-parser "^4.2.0"
postcss-import@14.0.2:
version "14.0.2"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.0.2.tgz#60eff77e6be92e7b67fe469ec797d9424cae1aa1"
@@ -11324,6 +11485,11 @@ postcss-initial@^3.0.0:
dependencies:
postcss "^7.0.2"
postcss-initial@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-4.0.1.tgz#529f735f72c5724a0fb30527df6fb7ac54d7de42"
integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==
postcss-lab-function@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e"
@@ -11333,6 +11499,13 @@ postcss-lab-function@^2.0.1:
postcss "^7.0.2"
postcss-values-parser "^2.0.0"
postcss-lab-function@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.0.3.tgz#633745b324afbcd5881da85fe2cef58b17487536"
integrity sha512-MH4tymWmefdZQ7uVG/4icfLjAQmH6o2NRYyVh2mKoB4RXJp9PjsyhZwhH4ouaCQHvg+qJVj3RzeAR1EQpIlXZA==
dependencies:
postcss-value-parser "^4.2.0"
postcss-loader@6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef"
@@ -11349,6 +11522,11 @@ postcss-logical@^3.0.0:
dependencies:
postcss "^7.0.2"
postcss-logical@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-5.0.3.tgz#9934e0fb16af70adbd94217b24d2f315ceb5c2f0"
integrity sha512-P5NcHWYrif0vK8rgOy/T87vg0WRIj3HSknrvp1wzDbiBeoDPVmiVRmkown2eSQdpPveat/MC1ess5uhzZFVnqQ==
postcss-media-minmax@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5"
@@ -11356,6 +11534,11 @@ postcss-media-minmax@^4.0.0:
dependencies:
postcss "^7.0.2"
postcss-media-minmax@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5"
integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==
postcss-modules-extract-imports@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
@@ -11384,6 +11567,13 @@ postcss-modules-values@^4.0.0:
dependencies:
icss-utils "^5.0.0"
postcss-nesting@^10.1.2:
version "10.1.2"
resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.2.tgz#2e5f811b3d75602ea18a95dd445bde5297145141"
integrity sha512-dJGmgmsvpzKoVMtDMQQG/T6FSqs6kDtUDirIfl4KnjMCiY9/ETX8jdKyCd20swSRAbUYkaBKV20pxkzxoOXLqQ==
dependencies:
postcss-selector-parser "^6.0.8"
postcss-nesting@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052"
@@ -11398,6 +11588,11 @@ postcss-overflow-shorthand@^2.0.0:
dependencies:
postcss "^7.0.2"
postcss-overflow-shorthand@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.2.tgz#b4e9c89728cd1e4918173dfb95936b75f78d4148"
integrity sha512-odBMVt6PTX7jOE9UNvmnLrFzA9pXS44Jd5shFGGtSHY80QCuJF+14McSy0iavZggRZ9Oj//C9vOKQmexvyEJMg==
postcss-page-break@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf"
@@ -11405,6 +11600,11 @@ postcss-page-break@^2.0.0:
dependencies:
postcss "^7.0.2"
postcss-page-break@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f"
integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==
postcss-place@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62"
@@ -11413,6 +11613,13 @@ postcss-place@^4.0.1:
postcss "^7.0.2"
postcss-values-parser "^2.0.0"
postcss-place@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-7.0.3.tgz#ca8040dfd937c7769a233a3bd6e66e139cf89e62"
integrity sha512-tDQ3m+GYoOar+KoQgj+pwPAvGHAp/Sby6vrFiyrELrMKQJ4AejL0NcS0mm296OKKYA2SRg9ism/hlT/OLhBrdQ==
dependencies:
postcss-value-parser "^4.2.0"
postcss-preset-env@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5"
@@ -11456,6 +11663,45 @@ postcss-preset-env@6.7.0:
postcss-selector-matches "^4.0.0"
postcss-selector-not "^4.0.0"
postcss-preset-env@7.2.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.2.3.tgz#01b9b6eea0ff16c27a3d514f10105d56363428a6"
integrity sha512-Ok0DhLfwrcNGrBn8sNdy1uZqWRk/9FId0GiQ39W4ILop5GHtjJs8bu1MY9isPwHInpVEPWjb4CEcEaSbBLpfwA==
dependencies:
autoprefixer "^10.4.2"
browserslist "^4.19.1"
caniuse-lite "^1.0.30001299"
css-blank-pseudo "^3.0.2"
css-has-pseudo "^3.0.3"
css-prefers-color-scheme "^6.0.2"
cssdb "^5.0.0"
postcss-attribute-case-insensitive "^5.0.0"
postcss-color-functional-notation "^4.2.1"
postcss-color-hex-alpha "^8.0.2"
postcss-color-rebeccapurple "^7.0.2"
postcss-custom-media "^8.0.0"
postcss-custom-properties "^12.1.2"
postcss-custom-selectors "^6.0.0"
postcss-dir-pseudo-class "^6.0.3"
postcss-double-position-gradients "^3.0.4"
postcss-env-function "^4.0.4"
postcss-focus-visible "^6.0.3"
postcss-focus-within "^5.0.3"
postcss-font-variant "^5.0.0"
postcss-gap-properties "^3.0.2"
postcss-image-set-function "^4.0.4"
postcss-initial "^4.0.1"
postcss-lab-function "^4.0.3"
postcss-logical "^5.0.3"
postcss-media-minmax "^5.0.0"
postcss-nesting "^10.1.2"
postcss-overflow-shorthand "^3.0.2"
postcss-page-break "^3.0.4"
postcss-place "^7.0.3"
postcss-pseudo-class-any-link "^7.0.2"
postcss-replace-overflow-wrap "^4.0.0"
postcss-selector-not "^5.0.0"
postcss-pseudo-class-any-link@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1"
@@ -11464,6 +11710,13 @@ postcss-pseudo-class-any-link@^6.0.0:
postcss "^7.0.2"
postcss-selector-parser "^5.0.0-rc.3"
postcss-pseudo-class-any-link@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.0.2.tgz#6284c2f970715c78fe992d2fac1130e9991585c9"
integrity sha512-CG35J1COUH7OOBgpw5O+0koOLUd5N4vUGKUqSAuIe4GiuLHWU96Pqp+UPC8QITTd12zYAFx76pV7qWT/0Aj/TA==
dependencies:
postcss-selector-parser "^6.0.8"
postcss-replace-overflow-wrap@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c"
@@ -11471,6 +11724,11 @@ postcss-replace-overflow-wrap@^3.0.0:
dependencies:
postcss "^7.0.2"
postcss-replace-overflow-wrap@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319"
integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==
postcss-selector-matches@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff"
@@ -11487,6 +11745,13 @@ postcss-selector-not@^4.0.0:
balanced-match "^1.0.0"
postcss "^7.0.2"
postcss-selector-not@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-5.0.0.tgz#ac5fc506f7565dd872f82f5314c0f81a05630dc7"
integrity sha512-/2K3A4TCP9orP4TNS7u3tGdRFVKqz/E6pX3aGnriPG0jU78of8wsUcqE4QAhWEU0d+WnMSF93Ah3F//vUtK+iQ==
dependencies:
balanced-match "^1.0.0"
postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c"
@@ -11504,11 +11769,24 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-selector-parser@^6.0.8:
version "6.0.8"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz#f023ed7a9ea736cd7ef70342996e8e78645a7914"
integrity sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f"
@@ -12532,10 +12810,10 @@ rollup@~1.11.3:
"@types/node" "^11.13.9"
acorn "^6.1.1"
rollup@~2.63.0:
version "2.63.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.63.0.tgz#fe2f7fec2133f3fab9e022b9ac245628d817c6bb"
integrity sha512-nps0idjmD+NXl6OREfyYXMn/dar3WGcyKn+KBzPdaLecub3x/LrId0wUcthcr8oZUAcZAR8NKcfGGFlNgGL1kQ==
rollup@~2.65.0:
version "2.65.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.65.0.tgz#936ed140ac28f87aff22c6ac950b3d0303fadc3b"
integrity sha512-ohZVYrhtVMTqqeqH26sngfMiyGDg6gCUReOsoflXvYpzUkDHp8sVG8F9FQxjs72OfnLWpXP2nNNqQ9I0vkRovA==
optionalDependencies:
fsevents "~2.3.2"