From 45263351576cd2bd57fdc03c255513b784965248 Mon Sep 17 00:00:00 2001 From: Sasidharan SD Date: Thu, 9 Nov 2023 00:19:01 +0530 Subject: [PATCH 01/43] docs: fix developer preview link (#52693) PR Close #52693 --- adev/src/content/guide/templates/control-flow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/guide/templates/control-flow.md b/adev/src/content/guide/templates/control-flow.md index 02f659e4fe4d2..6a5ff2e47c7bb 100644 --- a/adev/src/content/guide/templates/control-flow.md +++ b/adev/src/content/guide/templates/control-flow.md @@ -2,7 +2,7 @@ Angular templates support *control flow blocks* that let you conditionally show, hide, and repeat elements. -IMPORTANT: Angular built-in control flow is in [developer preview](/guide/releases#developer-preview). It is ready to try, but may change before becoming stable. +IMPORTANT: Angular built-in control flow is in [developer preview](reference/releases#developer-preview). It is ready to try, but may change before becoming stable. ## `@if` block conditionals From 8fee560ca76701470221c55357325d8baa1f04d2 Mon Sep 17 00:00:00 2001 From: Marianna Maglio Date: Wed, 8 Nov 2023 16:42:18 +0100 Subject: [PATCH 02/43] docs: fix typos in tutorials/learn-angular (#52677) PR Close #52677 --- .../learn-angular/steps/21-constructor-based-di/README.md | 2 +- .../tutorials/learn-angular/steps/24-create-a-pipe/README.md | 4 ++-- .../learn-angular/steps/3-composing-components/README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adev/src/content/tutorials/learn-angular/steps/21-constructor-based-di/README.md b/adev/src/content/tutorials/learn-angular/steps/21-constructor-based-di/README.md index 19bb307650ae1..85a54af40f27c 100644 --- a/adev/src/content/tutorials/learn-angular/steps/21-constructor-based-di/README.md +++ b/adev/src/content/tutorials/learn-angular/steps/21-constructor-based-di/README.md @@ -2,7 +2,7 @@ In previous activities you used the `inject()` function to make resources available, "providing" them to your components. The `inject()` function is one pattern and it is useful to know that there is another pattern for injecting resources called constructor-based dependency injection. -You specify the resources as parameters to the `constructor` function of a component. Angular will make those resources available to your compoonent. +You specify the resources as parameters to the `constructor` function of a component. Angular will make those resources available to your component.

In this activity you will learn to use constructor-based dependency injection. diff --git a/adev/src/content/tutorials/learn-angular/steps/24-create-a-pipe/README.md b/adev/src/content/tutorials/learn-angular/steps/24-create-a-pipe/README.md index 4c6a49e99ddae..8075194b36511 100644 --- a/adev/src/content/tutorials/learn-angular/steps/24-create-a-pipe/README.md +++ b/adev/src/content/tutorials/learn-angular/steps/24-create-a-pipe/README.md @@ -12,7 +12,7 @@ A pipe is a TypeScript class with a `@Pipe` decorator. Here's an example: import {Pipe, PipeTransform} from '@angular/core'; @Pipe({ - standaone: true, + standalone: true, name: 'star', }) export class StarPipe implements PipeTransform { @@ -37,7 +37,7 @@ In `reverse.pipe.ts` add the `@Pipe` decorator to the `ReversePipe` class and pr ```ts @Pipe({ - standaone: true, + standalone: true, name: 'reverse' }) ``` diff --git a/adev/src/content/tutorials/learn-angular/steps/3-composing-components/README.md b/adev/src/content/tutorials/learn-angular/steps/3-composing-components/README.md index 713b3ec146fdf..ffd2780eb5202 100644 --- a/adev/src/content/tutorials/learn-angular/steps/3-composing-components/README.md +++ b/adev/src/content/tutorials/learn-angular/steps/3-composing-components/README.md @@ -13,7 +13,7 @@ In this example, there are two components `UserComponent` and `AppComponent`. -Update the `AppComponent` template to include a reference to the `UserComponent` which uses the selector `app-user`. Be sure to add `AppComponent` to the imports array of `AppComponent`, this makes it available for use in the `AppComponent` template. +Update the `AppComponent` template to include a reference to the `UserComponent` which uses the selector `app-user`. Be sure to add `UserComponent` to the imports array of `AppComponent`, this makes it available for use in the `AppComponent` template. ```ts template: ``, From fc8f521fc2882111f237dbec8d1f53228cbb7849 Mon Sep 17 00:00:00 2001 From: technbuzz Date: Wed, 8 Nov 2023 18:40:05 +0400 Subject: [PATCH 03/43] docs: fix typo in component output guide (#52673) We should use @Output rathar than @Input decorator for output events. This comments fixes the typo by replacing Input with Output PR Close #52673 --- adev/src/content/guide/components/outputs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/guide/components/outputs.md b/adev/src/content/guide/components/outputs.md index 0f161fd333746..44ac3911aad34 100644 --- a/adev/src/content/guide/components/outputs.md +++ b/adev/src/content/guide/components/outputs.md @@ -57,7 +57,7 @@ The `@Output` decorator accepts a parameter that lets you specify a different na @Component({...}) export class CustomSlider { - @Input('valueChanged') changed = new EventEmitter(); + @Output('valueChanged') changed = new EventEmitter(); } From b25153abb312ab7090f5273ceee4ea1760db3cb6 Mon Sep 17 00:00:00 2001 From: Raghav Kanwal Date: Wed, 8 Nov 2023 02:23:30 -0800 Subject: [PATCH 04/43] docs: update typo in Guide/Testing (#52619) PR Close #52619 --- adev/src/content/guide/testing/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/guide/testing/overview.md b/adev/src/content/guide/testing/overview.md index 262819e27694d..25386be0be9ca 100644 --- a/adev/src/content/guide/testing/overview.md +++ b/adev/src/content/guide/testing/overview.md @@ -18,7 +18,7 @@ ng test The `ng test` command builds the application in *watch mode*, and launches the [Karma test runner](https://karma-runner.github.io). -The console output looks the below: +The console output looks like below: From 01f9b57f6a91f8ab27dd8a26abb49b6077db04ca Mon Sep 17 00:00:00 2001 From: Ryth-cs <49680490+Ryth-cs@users.noreply.github.com> Date: Tue, 7 Nov 2023 23:01:19 +0000 Subject: [PATCH 05/43] docs: correct reactive-forms typo (#52608) PR Close #52608 --- adev/src/content/guide/forms/reactive-forms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/guide/forms/reactive-forms.md b/adev/src/content/guide/forms/reactive-forms.md index 26309767f2931..6c1a395f3d696 100644 --- a/adev/src/content/guide/forms/reactive-forms.md +++ b/adev/src/content/guide/forms/reactive-forms.md @@ -356,7 +356,7 @@ Use the `FormBuilder.array()` method to define the array, and the `FormBuilder.c The aliases control in the form group instance is now populated with a single control until more controls are added dynamically. - + A getter provides access to the aliases in the form array instance compared to repeating the `profileForm.get()` method to get each instance. The form array instance represents an undefined number of controls in an array. It's convenient to access a control through a getter, and this approach is straightforward to repeat for additional controls.
Use the getter syntax to create an `aliases` class property to retrieve the alias's form array control from the parent form group. From 0ffa228165f2d34ed92b01765961998444c3d54b Mon Sep 17 00:00:00 2001 From: Sasidharan SD Date: Wed, 8 Nov 2023 03:55:53 +0530 Subject: [PATCH 06/43] docs: add missing backtick (#52607) PR Close #52607 --- .../tutorials/learn-angular/steps/24-create-a-pipe/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/tutorials/learn-angular/steps/24-create-a-pipe/README.md b/adev/src/content/tutorials/learn-angular/steps/24-create-a-pipe/README.md index 8075194b36511..fec21bd101c73 100644 --- a/adev/src/content/tutorials/learn-angular/steps/24-create-a-pipe/README.md +++ b/adev/src/content/tutorials/learn-angular/steps/24-create-a-pipe/README.md @@ -31,7 +31,7 @@ Alright, it's your turn to give this a try — you'll create the `ReversePipe`: - + In `reverse.pipe.ts` add the `@Pipe` decorator to the `ReversePipe` class and provide the following configuration: From 315389234254cc07b2674527dbb1a6fdecc86eed Mon Sep 17 00:00:00 2001 From: Sasidharan SD Date: Tue, 7 Nov 2023 22:05:32 +0530 Subject: [PATCH 07/43] docs: fix prefetching typo issue (#52590) PR Close #52590 --- adev/src/content/guide/defer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/guide/defer.md b/adev/src/content/guide/defer.md index 424ebf2aac9f5..29d7f4f442dc3 100644 --- a/adev/src/content/guide/defer.md +++ b/adev/src/content/guide/defer.md @@ -4,7 +4,7 @@ Deferrable views can be used in component template to defer the loading of select dependencies within that template. Those dependencies include components, directives, and pipes, and any associated CSS. To use this feature, you can declaratively wrap a section of your template in a `@defer` block which specifies the loading conditions. -Deferrable views support a series of [triggers](guide/defer#triggers), [prefeching](guide/defer#prefetching), and several sub blocks used for [placeholder](guide/defer#placeholder), [loading](guide/defer#loading), and [error](guide/defer#error) state management. You can also create custom conditions with [`when`](guide/defer#when) and [`prefetch when`](guide/defer#prefetching). +Deferrable views support a series of [triggers](guide/defer#triggers), [prefetching](guide/defer#prefetching), and several sub blocks used for [placeholder](guide/defer#placeholder), [loading](guide/defer#loading), and [error](guide/defer#error) state management. You can also create custom conditions with [`when`](guide/defer#when) and [`prefetch when`](guide/defer#prefetching). ```html @defer { From 2ee1b5cddf42b23e3b55041a3638d937dff853e0 Mon Sep 17 00:00:00 2001 From: Sasidharan SD Date: Tue, 7 Nov 2023 21:58:51 +0530 Subject: [PATCH 08/43] docs: fix cli command redirection (#52589) PR Close #52589 --- adev/src/content/tools/cli/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/tools/cli/overview.md b/adev/src/content/tools/cli/overview.md index 6959e20ed35ff..47b2fdfc8b412 100644 --- a/adev/src/content/tools/cli/overview.md +++ b/adev/src/content/tools/cli/overview.md @@ -16,7 +16,7 @@ You don't need to set up your local environment until you're ready. Install Angular CLI to create and build your first app. - + Discover CLI commands to make you more productive with Angular. From 9dff1fe8917d497781b91fb60519ff2014eaded4 Mon Sep 17 00:00:00 2001 From: Sasidharan SD Date: Tue, 7 Nov 2023 21:32:48 +0530 Subject: [PATCH 09/43] docs: fix repetetive text (#52587) PR Close #52587 --- adev/src/content/guide/components/anatomy-of-components.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/adev/src/content/guide/components/anatomy-of-components.md b/adev/src/content/guide/components/anatomy-of-components.md index f4b4138168d7c..3f368f2b6d16c 100644 --- a/adev/src/content/guide/components/anatomy-of-components.md +++ b/adev/src/content/guide/components/anatomy-of-components.md @@ -8,9 +8,7 @@ Every component must have: * A TypeScript class with _behaviors_ such as handling user input and fetching data from a server * An HTML template that controls what renders into the DOM * A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors) that defines how the component is used in HTML -* A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors) that defines how the component is used in HTML -You provide Angular-specific information for a component by adding a `@Component` [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) on top of the TypeScript class: You provide Angular-specific information for a component by adding a `@Component` [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) on top of the TypeScript class: From 3d98a44db57fac14f0a8b71009cccc783693707d Mon Sep 17 00:00:00 2001 From: Sasidharan SD Date: Tue, 7 Nov 2023 21:26:16 +0530 Subject: [PATCH 10/43] docs: fix incorrect tag reference (#52586) PR Close #52586 --- .../content/introduction/essentials/conditionals-and-loops.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/introduction/essentials/conditionals-and-loops.md b/adev/src/content/introduction/essentials/conditionals-and-loops.md index 16e865da1e5c0..30ca5f4af0a3c 100644 --- a/adev/src/content/introduction/essentials/conditionals-and-loops.md +++ b/adev/src/content/introduction/essentials/conditionals-and-loops.md @@ -32,7 +32,7 @@ export class UserControls { } ``` -In this example, Angular only renders the `
` element if the `isAdmin` property is true. Otherwise, it does not appear page. +In this example, Angular only renders the ` @@ -188,7 +188,7 @@ By default, the placeholder will act as the hover element as long as it is a sin } ``` -Alternatively, you can specify a [template reference variable](guide/glossary#template-reference-variable) as the hover element. This variable is passed in as a parameter on the hover trigger. +Alternatively, you can specify a template reference variable as the hover element. This variable is passed in as a parameter on the hover trigger. ```html
Hello!
From 867161f2048bcff7873350ff76ce86540b41d4c2 Mon Sep 17 00:00:00 2001 From: Mikhail Filin Date: Thu, 9 Nov 2023 21:47:17 +0400 Subject: [PATCH 26/43] docs: remove broken link pill in dynamic component page (#52744) PR Close #52744 --- packages/core/src/linker/component_factory.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/linker/component_factory.ts b/packages/core/src/linker/component_factory.ts index 30700a126ad71..8d1f9f7eb2b5f 100644 --- a/packages/core/src/linker/component_factory.ts +++ b/packages/core/src/linker/component_factory.ts @@ -83,8 +83,6 @@ export abstract class ComponentRef { * Instantiate a factory for a given type of component with `resolveComponentFactory()`. * Use the resulting `ComponentFactory.create()` method to create a component of that type. * - * @see [Dynamic Components](guide/dynamic-component-loader) - * * @publicApi * * @deprecated Angular no longer requires Component factories. Please use other APIs where From 5935f91cf7ab21b46865cf7c7d7afabe53f69ed0 Mon Sep 17 00:00:00 2001 From: Balaji <88931771+Rajendran-Balaji@users.noreply.github.com> Date: Thu, 9 Nov 2023 09:20:38 +0530 Subject: [PATCH 27/43] docs: updating incorrect link in Class Binding page (#52715) PR Close #52715 --- adev/src/content/guide/templates/class-binding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/guide/templates/class-binding.md b/adev/src/content/guide/templates/class-binding.md index c367ef1423ff6..6290072cd4e12 100644 --- a/adev/src/content/guide/templates/class-binding.md +++ b/adev/src/content/guide/templates/class-binding.md @@ -88,6 +88,6 @@ A single HTML element can have its CSS class list and style values bound to mult ## What’s next - + From cb5c6fc53027327b5b0b78962dc9afb690c6b894 Mon Sep 17 00:00:00 2001 From: Patrick Hyatt Date: Wed, 8 Nov 2023 21:29:02 -0500 Subject: [PATCH 28/43] docs: correct typescript type inference link (#52714) PR Close #52714 --- .../steps/2-updating-the-component-class/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/tutorials/learn-angular/steps/2-updating-the-component-class/README.md b/adev/src/content/tutorials/learn-angular/steps/2-updating-the-component-class/README.md index c905e25d788c1..39b0086e66332 100644 --- a/adev/src/content/tutorials/learn-angular/steps/2-updating-the-component-class/README.md +++ b/adev/src/content/tutorials/learn-angular/steps/2-updating-the-component-class/README.md @@ -17,7 +17,7 @@ export class AppComponent { } ``` -The `city` property is of type `string` but you can omit the type because of [type inference in TypeScript](typescript.org/type-inference). The `city` property can be used in the `AppComponent` class and can be referenced in the component template. +The `city` property is of type `string` but you can omit the type because of [type inference in TypeScript](https://www.typescriptlang.org/docs/handbook/type-inference.html). The `city` property can be used in the `AppComponent` class and can be referenced in the component template.
From c5e44248108dd8dfe8a193c032bd09323b2afa34 Mon Sep 17 00:00:00 2001 From: Nelson Gutierrez Date: Wed, 8 Nov 2023 20:07:11 -0600 Subject: [PATCH 29/43] docs: add missing backtick on title (#52711) PR Close #52711 --- adev/src/content/guide/routing/common-router-tasks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/guide/routing/common-router-tasks.md b/adev/src/content/guide/routing/common-router-tasks.md index b7d581df6b413..e8ff7307c1f2f 100644 --- a/adev/src/content/guide/routing/common-router-tasks.md +++ b/adev/src/content/guide/routing/common-router-tasks.md @@ -144,7 +144,7 @@ To get information from a route: - + Add the `withComponentInputBinding` feature to the `provideRouter` method. From 71a4d6aaefe13fc5ce5b487ca449b345e067697a Mon Sep 17 00:00:00 2001 From: Mikita Himpel Date: Thu, 9 Nov 2023 13:27:53 +0100 Subject: [PATCH 30/43] docs: fix programmatic-rendering example component name (#52731) PR Close #52731 --- adev/src/content/guide/components/programmatic-rendering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adev/src/content/guide/components/programmatic-rendering.md b/adev/src/content/guide/components/programmatic-rendering.md index b4c09e7a6ba59..d96b09cf20091 100644 --- a/adev/src/content/guide/components/programmatic-rendering.md +++ b/adev/src/content/guide/components/programmatic-rendering.md @@ -28,7 +28,7 @@ export class CustomDialog { @Input() user: User; getBioComponent() { - return this.user.isAdmin ? AdminBio : StardardBio; + return this.user.isAdmin ? AdminBio : StandardBio; } } ``` From 154f286d554f7be8a3d8b34b9e33d6715df71016 Mon Sep 17 00:00:00 2001 From: Sasidharan SD Date: Thu, 9 Nov 2023 13:41:50 +0530 Subject: [PATCH 31/43] docs: add extended diagnostic NG8109 (#52721) PR Close #52721 --- adev/src/app/sub-navigation-data.ts | 5 ++ .../reference/extended-diagnostics/NG8109.md | 61 +++++++++++++++++++ .../extended-diagnostics/overview.md | 1 + 3 files changed, 67 insertions(+) create mode 100644 adev/src/content/reference/extended-diagnostics/NG8109.md diff --git a/adev/src/app/sub-navigation-data.ts b/adev/src/app/sub-navigation-data.ts index f90d43237bcb0..eddfd56a1c43e 100644 --- a/adev/src/app/sub-navigation-data.ts +++ b/adev/src/app/sub-navigation-data.ts @@ -1269,6 +1269,11 @@ const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [ path: 'extended-diagnostics/NG8108', contentPath: 'reference/extended-diagnostics/NG8108', }, + { + label: 'NG8109: Signals must be invoked in template interpolations', + path: 'extended-diagnostics/NG8109', + contentPath: 'reference/extended-diagnostics/NG8109', + }, ], }, { diff --git a/adev/src/content/reference/extended-diagnostics/NG8109.md b/adev/src/content/reference/extended-diagnostics/NG8109.md new file mode 100644 index 0000000000000..2f7cd126c9185 --- /dev/null +++ b/adev/src/content/reference/extended-diagnostics/NG8109.md @@ -0,0 +1,61 @@ +# Signals must be invoked in template interpolations. + +This diagnostic detects uninvoked signals in template interpolations. + + + +import {Component, signal, Signal} from '@angular/core'; + +@Component({ + template: `
{{ mySignal }}/div>`, +}) +class MyComponent { + mySignal: Signal = signal(0); +} + + + +## What's wrong with that? + +Angular Signals are zero-argument functions (`() => T`). When executed, they return the current value of the signal. +This means they are meant to be invoked when used in template interpolations to render their value. + +## What should I do instead? + +Ensure to invoke the signal when you use it within a template interpolation to render its value. + + + +import {Component, signal, Signal} from '@angular/core'; + +@Component({ + template: `
{{ mySignal() }}/div>`, +}) +class MyComponent { + mySignal: Signal = signal(0) +} + + + +## Configuration requirements + +[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. +`interpolatedSignalNotInvoked` 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: + + +{ + "angularCompilerOptions": { + "extendedDiagnostics": { + "checks": { + "interpolatedSignalNotInvoked": "suppress" + } + } + } +} + + +See [extended diagnostic configuration](extended-diagnostics#configuration) for more info. diff --git a/adev/src/content/reference/extended-diagnostics/overview.md b/adev/src/content/reference/extended-diagnostics/overview.md index 98162db20cca8..639ee928c2204 100644 --- a/adev/src/content/reference/extended-diagnostics/overview.md +++ b/adev/src/content/reference/extended-diagnostics/overview.md @@ -18,6 +18,7 @@ Currently, Angular supports the following extended diagnostics: | `NG8106` | [`suffixNotSupported`](extended-diagnostics/NG8106) | | `NG8107` | [`optionalChainNotNullable`](extended-diagnostics/NG8107) | | `NG8108` | [`skipHydrationNotStatic`](extended-diagnostics/NG8108) | +| `NG8109` | [`interpolatedSignalNotInvoked`](extended-diagnostics/NG8109) | ## Configuration From 6c8776ff7102d87e00ea2c28f1517654f7386dd7 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Thu, 9 Nov 2023 18:18:43 +0100 Subject: [PATCH 32/43] fix(core): limit rate of markers invocations (#52742) This PR assures that the performance markers are invoked only once for a given feature. Closes #52524 PR Close #52742 --- packages/common/http/src/transfer_cache.ts | 4 ++-- .../ng_optimized_image/ng_optimized_image.ts | 4 ++-- packages/core/src/core_private_export.ts | 2 +- packages/core/src/defer/instructions.ts | 4 ++-- packages/core/src/hydration/api.ts | 4 ++-- packages/core/src/render3/after_render_hooks.ts | 6 +++--- .../src/render3/features/standalone_feature.ts | 7 ++----- .../core/src/render3/instructions/control_flow.ts | 10 +++------- packages/core/src/util/performance.ts | 15 +++++++++------ .../bundle.golden_symbols.json | 6 +++--- .../bundling/defer/bundle.golden_symbols.json | 8 ++++---- .../bundling/hydration/bundle.golden_symbols.json | 8 ++++---- .../bundling/router/bundle.golden_symbols.json | 6 +++--- .../bundle.golden_symbols.json | 6 +++--- 14 files changed, 43 insertions(+), 47 deletions(-) diff --git a/packages/common/http/src/transfer_cache.ts b/packages/common/http/src/transfer_cache.ts index 7f7eb772e1ead..4310038157b21 100644 --- a/packages/common/http/src/transfer_cache.ts +++ b/packages/common/http/src/transfer_cache.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {APP_BOOTSTRAP_LISTENER, ApplicationRef, inject, InjectionToken, makeStateKey, Provider, StateKey, TransferState, ɵformatRuntimeError as formatRuntimeError, ɵperformanceMark as performanceMark, ɵtruncateMiddle as truncateMiddle, ɵwhenStable as whenStable} from '@angular/core'; +import {APP_BOOTSTRAP_LISTENER, ApplicationRef, inject, InjectionToken, makeStateKey, Provider, StateKey, TransferState, ɵformatRuntimeError as formatRuntimeError, ɵperformanceMarkFeature as performanceMarkFeature, ɵtruncateMiddle as truncateMiddle, ɵwhenStable as whenStable} from '@angular/core'; import {Observable, of} from 'rxjs'; import {tap} from 'rxjs/operators'; @@ -227,7 +227,7 @@ export function withHttpTransferCache(cacheOptions: HttpTransferCacheOptions): P { provide: CACHE_OPTIONS, useFactory: (): CacheOptions => { - performanceMark('mark_use_counter', {detail: {feature: 'NgHttpTransferCache'}}); + performanceMarkFeature('NgHttpTransferCache'); return {isCacheActive: true, ...cacheOptions}; } }, diff --git a/packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts b/packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts index 3ac4f538b64b5..1e8aaabc7f093 100644 --- a/packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts +++ b/packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {booleanAttribute, Directive, ElementRef, inject, Injector, Input, NgZone, numberAttribute, OnChanges, OnDestroy, OnInit, PLATFORM_ID, Renderer2, SimpleChanges, ɵformatRuntimeError as formatRuntimeError, ɵIMAGE_CONFIG as IMAGE_CONFIG, ɵIMAGE_CONFIG_DEFAULTS as IMAGE_CONFIG_DEFAULTS, ɵImageConfig as ImageConfig, ɵperformanceMark as performanceMark, ɵRuntimeError as RuntimeError, ɵSafeValue as SafeValue, ɵunwrapSafeValue as unwrapSafeValue} from '@angular/core'; +import {booleanAttribute, Directive, ElementRef, inject, Injector, Input, NgZone, numberAttribute, OnChanges, OnDestroy, OnInit, PLATFORM_ID, Renderer2, SimpleChanges, ɵformatRuntimeError as formatRuntimeError, ɵIMAGE_CONFIG as IMAGE_CONFIG, ɵIMAGE_CONFIG_DEFAULTS as IMAGE_CONFIG_DEFAULTS, ɵImageConfig as ImageConfig, ɵperformanceMarkFeature as performanceMarkFeature, ɵRuntimeError as RuntimeError, ɵSafeValue as SafeValue, ɵunwrapSafeValue as unwrapSafeValue} from '@angular/core'; import {RuntimeErrorCode} from '../../errors'; import {isPlatformServer} from '../../platform_id'; @@ -302,7 +302,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy { /** @nodoc */ ngOnInit() { - performanceMark('mark_use_counter', {'detail': {'feature': 'NgOptimizedImage'}}); + performanceMarkFeature('NgOptimizedImage'); if (ngDevMode) { const ngZone = this.injector.get(NgZone); diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index e02fe02af9e08..224bfba7cc5b9 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -41,6 +41,6 @@ export {booleanAttribute, numberAttribute} from './util/coercion'; export {devModeEqual as ɵdevModeEqual} from './util/comparison'; export {global as ɵglobal} from './util/global'; export {isPromise as ɵisPromise, isSubscribable as ɵisSubscribable} from './util/lang'; -export {performanceMark as ɵperformanceMark} from './util/performance'; +export {performanceMarkFeature as ɵperformanceMarkFeature} from './util/performance'; export {stringify as ɵstringify, truncateMiddle as ɵtruncateMiddle} from './util/stringify'; export {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from './view/provider_flags'; diff --git a/packages/core/src/defer/instructions.ts b/packages/core/src/defer/instructions.ts index 64f04c20ef332..bb31ab813881f 100644 --- a/packages/core/src/defer/instructions.ts +++ b/packages/core/src/defer/instructions.ts @@ -29,7 +29,7 @@ import {isPlatformBrowser} from '../render3/util/misc_utils'; import {getConstant, getTNode, removeLViewOnDestroy, storeLViewOnDestroy} from '../render3/util/view_utils'; import {addLViewToLContainer, createAndRenderEmbeddedLView, removeLViewFromLContainer, shouldAddViewToDom} from '../render3/view_manipulation'; import {assertDefined, throwError} from '../util/assert'; -import {performanceMark} from '../util/performance'; +import {performanceMarkFeature} from '../util/performance'; import {invokeAllTriggerCleanupFns, invokeTriggerCleanupFns, storeTriggerCleanupFn} from './cleanup'; import {onHover, onInteraction, onViewport, registerDomTrigger} from './dom_triggers'; @@ -130,7 +130,7 @@ export function ɵɵdefer( ɵɵtemplate(index, null, 0, 0); if (tView.firstCreatePass) { - performanceMark('mark_use_counter', {detail: {feature: 'NgDefer'}}); + performanceMarkFeature('NgDefer'); const tDetails: TDeferBlockDetails = { primaryTmplIndex, diff --git a/packages/core/src/hydration/api.ts b/packages/core/src/hydration/api.ts index cb38f59516ff7..d95b51cfb0196 100644 --- a/packages/core/src/hydration/api.ts +++ b/packages/core/src/hydration/api.ts @@ -20,7 +20,7 @@ import {enableLocateOrCreateTextNodeImpl} from '../render3/instructions/text'; import {getDocument} from '../render3/interfaces/document'; import {isPlatformBrowser} from '../render3/util/misc_utils'; import {TransferState} from '../transfer_state'; -import {performanceMark} from '../util/performance'; +import {performanceMarkFeature} from '../util/performance'; import {NgZone} from '../zone'; import {cleanupDehydratedViews} from './cleanup'; @@ -137,7 +137,7 @@ export function withDomHydration(): EnvironmentProviders { } } if (isEnabled) { - performanceMark('mark_use_counter', {detail: {feature: 'NgHydration'}}); + performanceMarkFeature('NgHydration'); } return isEnabled; }, diff --git a/packages/core/src/render3/after_render_hooks.ts b/packages/core/src/render3/after_render_hooks.ts index ec9ceb36a907b..58f45f8f6d93a 100644 --- a/packages/core/src/render3/after_render_hooks.ts +++ b/packages/core/src/render3/after_render_hooks.ts @@ -13,7 +13,7 @@ import {ErrorHandler} from '../error_handler'; import {RuntimeError, RuntimeErrorCode} from '../errors'; import {DestroyRef} from '../linker/destroy_ref'; import {assertGreaterThan} from '../util/assert'; -import {performanceMark} from '../util/performance'; +import {performanceMarkFeature} from '../util/performance'; import {NgZone} from '../zone'; import {isPlatformBrowser} from './util/misc_utils'; @@ -225,7 +225,7 @@ export function afterRender(callback: VoidFunction, options?: AfterRenderOptions return NOOP_AFTER_RENDER_REF; } - performanceMark('mark_use_counter', {detail: {feature: 'NgAfterRender'}}); + performanceMarkFeature('NgAfterRender'); const afterRenderEventManager = injector.get(AfterRenderEventManager); // Lazily initialize the handler implementation, if necessary. This is so that it can be @@ -301,7 +301,7 @@ export function afterNextRender( return NOOP_AFTER_RENDER_REF; } - performanceMark('mark_use_counter', {detail: {feature: 'NgAfterNextRender'}}); + performanceMarkFeature('NgAfterNextRender'); const afterRenderEventManager = injector.get(AfterRenderEventManager); // Lazily initialize the handler implementation, if necessary. This is so that it can be diff --git a/packages/core/src/render3/features/standalone_feature.ts b/packages/core/src/render3/features/standalone_feature.ts index 0ad4dfc035d17..1c2d813cdd274 100644 --- a/packages/core/src/render3/features/standalone_feature.ts +++ b/packages/core/src/render3/features/standalone_feature.ts @@ -10,7 +10,7 @@ import {ɵɵdefineInjectable as defineInjectable} from '../../di/interface/defs' import {internalImportProvidersFrom} from '../../di/provider_collection'; import {EnvironmentInjector} from '../../di/r3_injector'; import {OnDestroy} from '../../interface/lifecycle_hooks'; -import {performanceMark} from '../../util/performance'; +import {performanceMarkFeature} from '../../util/performance'; import {ComponentDef} from '../interfaces/definition'; import {createEnvironmentInjector} from '../ng_module_ref'; @@ -61,9 +61,6 @@ class StandaloneService implements OnDestroy { }); } -const PERF_MARK_STANDALONE = { - detail: {feature: 'NgStandalone'} -}; /** * A feature that acts as a setup code for the {@link StandaloneService}. @@ -76,7 +73,7 @@ const PERF_MARK_STANDALONE = { * @codeGenApi */ export function ɵɵStandaloneFeature(definition: ComponentDef) { - performanceMark('mark_use_counter', PERF_MARK_STANDALONE); + performanceMarkFeature('NgStandalone'); definition.getStandaloneInjector = (parentInjector: EnvironmentInjector) => { return parentInjector.get(StandaloneService).getOrCreateStandaloneInjector(definition); }; diff --git a/packages/core/src/render3/instructions/control_flow.ts b/packages/core/src/render3/instructions/control_flow.ts index 4240990e290c5..11acac874342c 100644 --- a/packages/core/src/render3/instructions/control_flow.ts +++ b/packages/core/src/render3/instructions/control_flow.ts @@ -12,7 +12,7 @@ import {TrackByFunction} from '../../change_detection'; import {DehydratedContainerView} from '../../hydration/interfaces'; import {findMatchingDehydratedView} from '../../hydration/views'; import {assertDefined} from '../../util/assert'; -import {performanceMark} from '../../util/performance'; +import {performanceMarkFeature} from '../../util/performance'; import {assertLContainer, assertLView, assertTNode} from '../assert'; import {bindingUpdated} from '../bindings'; import {CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; @@ -27,10 +27,6 @@ import {addLViewToLContainer, createAndRenderEmbeddedLView, getLViewFromLContain import {ɵɵtemplate} from './template'; -const PERF_MARK_CONTROL_FLOW = { - detail: {feature: 'NgControlFlow'} -}; - /** * The conditional instruction represents the basic building block on the runtime side to support * built-in "if" and "switch". On the high level this instruction is responsible for adding and @@ -43,7 +39,7 @@ const PERF_MARK_CONTROL_FLOW = { * @codeGenApi */ export function ɵɵconditional(containerIndex: number, matchingTemplateIndex: number, value?: T) { - performanceMark('mark_use_counter', PERF_MARK_CONTROL_FLOW); + performanceMarkFeature('NgControlFlow'); const hostLView = getLView(); const bindingIndex = nextBindingIndex(); @@ -149,7 +145,7 @@ export function ɵɵrepeaterCreate( tagName: string|null, attrsIndex: number|null, trackByFn: TrackByFunction, trackByUsesComponentInstance?: boolean, emptyTemplateFn?: ComponentTemplate, emptyDecls?: number, emptyVars?: number): void { - performanceMark('mark_use_counter', PERF_MARK_CONTROL_FLOW); + performanceMarkFeature('NgControlFlow'); const hasEmptyBlock = emptyTemplateFn !== undefined; const hostLView = getLView(); const boundTrackBy = trackByUsesComponentInstance ? diff --git a/packages/core/src/util/performance.ts b/packages/core/src/util/performance.ts index 4c3dcf2c53389..77b9b86176d53 100644 --- a/packages/core/src/util/performance.ts +++ b/packages/core/src/util/performance.ts @@ -6,17 +6,20 @@ * found in the LICENSE file at https://angular.io/license */ +const markedFeatures = new Set(); + // tslint:disable:ban /** - * A guarded `performance.mark`. + * A guarded `performance.mark` for feature marking. * * This method exists because while all supported browser and node.js version supported by Angular * support performance.mark API. This is not the case for other environments such as JSDOM and * Cloudflare workers. */ -export function performanceMark( - markName: string, - markOptions?: PerformanceMarkOptions|undefined, - ): PerformanceMark|undefined { - return performance?.mark?.(markName, markOptions); +export function performanceMarkFeature(feature: string): void { + if (markedFeatures.has(feature)) { + return; + } + markedFeatures.add(feature); + performance?.mark?.('mark_use_counter', {detail: {feature}}); } diff --git a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json index 0c37e3c53a985..4fd3a46c886ad 100644 --- a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json @@ -407,9 +407,6 @@ { "name": "PARAM_REGEX" }, - { - "name": "PERF_MARK_STANDALONE" - }, { "name": "PLATFORM_DESTROY_LISTENERS" }, @@ -1211,6 +1208,9 @@ { "name": "markViewForRefresh" }, + { + "name": "markedFeatures" + }, { "name": "maybeWrapInNotSelector" }, diff --git a/packages/core/test/bundling/defer/bundle.golden_symbols.json b/packages/core/test/bundling/defer/bundle.golden_symbols.json index 22cfd53707105..2caad4a19a0d1 100644 --- a/packages/core/test/bundling/defer/bundle.golden_symbols.json +++ b/packages/core/test/bundling/defer/bundle.golden_symbols.json @@ -374,9 +374,6 @@ { "name": "Observable" }, - { - "name": "PERF_MARK_STANDALONE" - }, { "name": "PLATFORM_DESTROY_LISTENERS" }, @@ -2144,6 +2141,9 @@ { "name": "markViewForRefresh" }, + { + "name": "markedFeatures" + }, { "name": "maybeWrapInNotSelector" }, @@ -2199,7 +2199,7 @@ "name": "onLeave" }, { - "name": "performanceMark" + "name": "performanceMarkFeature" }, { "name": "populateDehydratedViewsInLContainer" diff --git a/packages/core/test/bundling/hydration/bundle.golden_symbols.json b/packages/core/test/bundling/hydration/bundle.golden_symbols.json index 60a7798efc387..9ce67584be42e 100644 --- a/packages/core/test/bundling/hydration/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hydration/bundle.golden_symbols.json @@ -380,9 +380,6 @@ { "name": "Observable" }, - { - "name": "PERF_MARK_STANDALONE" - }, { "name": "PLATFORM_DESTROY_LISTENERS" }, @@ -1109,6 +1106,9 @@ { "name": "markViewForRefresh" }, + { + "name": "markedFeatures" + }, { "name": "maybeWrapInNotSelector" }, @@ -1173,7 +1173,7 @@ "name": "onLeave" }, { - "name": "performanceMark" + "name": "performanceMarkFeature" }, { "name": "populateDehydratedViewsInLContainerImpl" diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 3da6ad301f0bd..473795c544022 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -536,9 +536,6 @@ { "name": "OutletInjector" }, - { - "name": "PERF_MARK_STANDALONE" - }, { "name": "PLATFORM_DESTROY_LISTENERS" }, @@ -1709,6 +1706,9 @@ { "name": "markViewForRefresh" }, + { + "name": "markedFeatures" + }, { "name": "match" }, diff --git a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json index 0a87cc24fdd89..08acc73fd23ea 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -296,9 +296,6 @@ { "name": "Observable" }, - { - "name": "PERF_MARK_STANDALONE" - }, { "name": "PLATFORM_DESTROY_LISTENERS" }, @@ -890,6 +887,9 @@ { "name": "markViewForRefresh" }, + { + "name": "markedFeatures" + }, { "name": "maybeWrapInNotSelector" }, From f84cce09dfa80c724d8878fe88ff3f6a6b72d2fb Mon Sep 17 00:00:00 2001 From: Jessica Janiuk Date: Thu, 9 Nov 2023 15:20:18 -0500 Subject: [PATCH 33/43] docs: release notes for the v17.0.2 release --- CHANGELOG.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffcf8dc92c9d3..2e78124783414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ + +# 17.0.2 (2023-11-09) +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [7a95cccf50](https://github.com/angular/angular/commit/7a95cccf50c01a3733c6015551f8864e246d9239) | fix | add interpolatedSignalNotInvoked to diagnostics ([#52687](https://github.com/angular/angular/pull/52687)) | +| [a548c0333e](https://github.com/angular/angular/commit/a548c0333ecc993073ee7df054119a6fdde1d27b) | fix | incorrect inferred type of for loop implicit variables ([#52732](https://github.com/angular/angular/pull/52732)) | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [2cea80c6e2](https://github.com/angular/angular/commit/2cea80c6e21c113d12c38c4b3219c5f3f5944bd8) | fix | error code in image performance warning ([#52727](https://github.com/angular/angular/pull/52727)) | +| [b16fc2610a](https://github.com/angular/angular/commit/b16fc2610a37b7407713e1e0018d92372f1349e9) | fix | limit rate of markers invocations ([#52742](https://github.com/angular/angular/pull/52742)) | +| [44c48a4835](https://github.com/angular/angular/commit/44c48a48358c92c32301b578966a8e1ee9a867d8) | fix | properly update collection with repeated keys in `@for` ([#52697](https://github.com/angular/angular/pull/52697)) | + + + # 17.0.1 (2023-11-08) ### http @@ -10,7 +26,7 @@ | [70d30c28e0](https://github.com/angular/angular/commit/70d30c28e04f4ead51145e4e47df342492bfb336) | fix | Add support for ng-templates with i18n attributes ([#52597](https://github.com/angular/angular/pull/52597)) | | [4f125c5f9a](https://github.com/angular/angular/commit/4f125c5f9ae572a8216ec1fbb88f52e47b875e1e) | fix | Switches to multiple passes to fix several reported bugs ([#52592](https://github.com/angular/angular/pull/52592)) | -Web Frameworks: the internet frontier.
+Web Frameworks: the internet frontier.
These are the voyages of the framework Angular.
Its continuing mission:
To explore strange, new technologies.
From beb18fb01cf027cb1f375334071b18e90ba662cd Mon Sep 17 00:00:00 2001 From: Jessica Janiuk Date: Thu, 9 Nov 2023 15:51:10 -0500 Subject: [PATCH 34/43] refactor(migrations): code clean up and add comments on exported functions (#52755) This cleans up a bit of code to make maintenance easier. It also adds comments for all the exported methods so they are clear to anyone in the future. PR Close #52755 --- .../ng-generate/control-flow-migration/ifs.ts | 2 +- .../control-flow-migration/index.ts | 18 ++--------- .../control-flow-migration/migration.ts | 31 +++++++++++++++++++ .../control-flow-migration/types.ts | 3 ++ .../control-flow-migration/util.ts | 22 +++++++++++++ 5 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 packages/core/schematics/ng-generate/control-flow-migration/migration.ts diff --git a/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts b/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts index 8b5ffd27b3d00..49ed4af7b51a6 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts @@ -64,7 +64,7 @@ export function migrateIf(template: string): {migrated: string, errors: MigrateE return {migrated: result, errors}; } -export function migrateNgIf(etm: ElementToMigrate, tmpl: string, offset: number): Result { +function migrateNgIf(etm: ElementToMigrate, tmpl: string, offset: number): Result { const matchThen = etm.attr.value.match(/;\s*then/gm); const matchElse = etm.attr.value.match(/;\s*else/gm); diff --git a/packages/core/schematics/ng-generate/control-flow-migration/index.ts b/packages/core/schematics/ng-generate/control-flow-migration/index.ts index cf20669cb391f..f5664072185ba 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/index.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/index.ts @@ -13,11 +13,9 @@ import {normalizePath} from '../../utils/change_tracker'; import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host'; -import {migrateFor} from './fors'; -import {migrateIf} from './ifs'; -import {migrateSwitch} from './switches'; +import {migrateTemplate} from './migration'; import {AnalyzedFile, MigrateError} from './types'; -import {analyze, processNgTemplates} from './util'; +import {analyze} from './util'; interface Options { path: string; @@ -88,17 +86,7 @@ function runControlFlowMigration( const template = content.slice(start, end); const length = (end ?? content.length) - start; - const ifResult = migrateIf(template); - const forResult = migrateFor(ifResult.migrated); - const switchResult = migrateSwitch(forResult.migrated); - - const errors = [ - ...ifResult.errors, - ...forResult.errors, - ...switchResult.errors, - ]; - - const migrated = processNgTemplates(switchResult.migrated); + const {migrated, errors} = migrateTemplate(template); if (migrated !== null) { update.remove(start, length); diff --git a/packages/core/schematics/ng-generate/control-flow-migration/migration.ts b/packages/core/schematics/ng-generate/control-flow-migration/migration.ts new file mode 100644 index 0000000000000..3d8568015bde7 --- /dev/null +++ b/packages/core/schematics/ng-generate/control-flow-migration/migration.ts @@ -0,0 +1,31 @@ +/** + * @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 {migrateFor} from './fors'; +import {migrateIf} from './ifs'; +import {migrateSwitch} from './switches'; +import {MigrateError} from './types'; +import {processNgTemplates} from './util'; + +/** + * Actually migrates a given template to the new syntax + */ +export function migrateTemplate(template: string): {migrated: string, errors: MigrateError[]} { + const ifResult = migrateIf(template); + const forResult = migrateFor(ifResult.migrated); + const switchResult = migrateSwitch(forResult.migrated); + + const migrated = processNgTemplates(switchResult.migrated); + + const errors = [ + ...ifResult.errors, + ...forResult.errors, + ...switchResult.errors, + ]; + return {migrated, errors}; +} diff --git a/packages/core/schematics/ng-generate/control-flow-migration/types.ts b/packages/core/schematics/ng-generate/control-flow-migration/types.ts index adfaa76cb0f4d..2c9c01cad7f73 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/types.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/types.ts @@ -75,6 +75,9 @@ export class ElementToMigrate { } } +/** + * Represents an ng-template inside a template being migrated to new control flow + */ export class Template { el: Element; count: number = 0; diff --git a/packages/core/schematics/ng-generate/control-flow-migration/util.ts b/packages/core/schematics/ng-generate/control-flow-migration/util.ts index d612c43817b62..a8c1d2b42d4a9 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/util.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/util.ts @@ -82,6 +82,9 @@ function getNestedCount(etm: ElementToMigrate, aggregator: number[]) { } } +/** + * parses the template string into the Html AST + */ export function parseTemplate(template: string): ParseTreeResult|null { let parsed: ParseTreeResult; try { @@ -108,6 +111,9 @@ export function parseTemplate(template: string): ParseTreeResult|null { return parsed; } +/** + * calculates the level of nesting of the items in the collector + */ export function calculateNesting( visitor: ElementCollector|TemplateCollector, hasLineBreaks: boolean): void { // start from top of template @@ -133,10 +139,16 @@ function escapeRegExp(val: string) { return val.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } +/** + * determines if a given template string contains line breaks + */ export function hasLineBreaks(template: string): boolean { return /\r|\n/.test(template); } +/** + * properly adjusts template offsets based on current nesting levels + */ export function reduceNestingOffset( el: ElementToMigrate, nestLevel: number, offset: number, postOffsets: number[]): number { if (el.nestCount <= nestLevel) { @@ -178,6 +190,9 @@ function wrapIntoI18nContainer(i18nAttr: Attribute, content: string) { return `${content}`; } +/** + * Counts, replaces, and removes any necessary ng-templates post control flow migration + */ export function processNgTemplates(template: string): string { // count usage const templates = countTemplateUsage(template); @@ -201,6 +216,10 @@ export function processNgTemplates(template: string): string { return template; } +/** + * retrieves the original block of text in the template for length comparison during migration + * processing + */ export function getOriginals( etm: ElementToMigrate, tmpl: string, offset: number): {start: string, end: string} { // original opening block @@ -221,6 +240,9 @@ export function getOriginals( return {start, end: ''}; } +/** + * builds the proper contents of what goes inside a given control flow block after migration + */ export function getMainBlock(etm: ElementToMigrate, tmpl: string, offset: number): {start: string, middle: string, end: string} { const i18nAttr = etm.el.attrs.find(x => x.name === 'i18n'); From 94096c6ede67436a349ae07901f2bb418bf9f461 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 9 Nov 2023 11:53:01 +0100 Subject: [PATCH 35/43] feat(core): support TypeScript 5.3 (#52572) Updates the repo to support TypeScript 5.3 and resolve any issues. Fixes include: * Updating usages of TS compiler APIs to match their new signatures. * In TS 5.3 negative numbers are represented as `PrefixUnaryExpression` instead of `NumericExpression`. These changes update all usages to account for it since passing a negative number into the old APIs results in a runtime error. PR Close #52572 --- goldens/public-api/localize/tools/index.md | 4 + integration/typings_test_ts53/BUILD.bazel | 9 + integration/typings_test_ts53/include-all.ts | 69 ++ integration/typings_test_ts53/package.json | 29 + integration/typings_test_ts53/tsconfig.json | 26 + integration/typings_test_ts53/yarn.lock | 789 ++++++++++++++++++ package.json | 2 +- packages/bazel/package.json | 2 +- packages/compiler-cli/package.json | 2 +- .../compiler-cli/src/ngtsc/core/src/host.ts | 4 + .../src/ngtsc/docs/src/enum_extractor.ts | 7 +- .../partial_evaluator/test/evaluator_spec.ts | 4 + .../src/ts_create_program_driver.ts | 4 + .../src/ngtsc/translator/src/ts_util.ts | 23 + .../ngtsc/translator/src/type_translator.ts | 3 +- .../translator/src/typescript_ast_factory.ts | 3 +- .../test/typescript_ast_factory_spec.ts | 7 + .../src/ngtsc/typecheck/src/expression.ts | 4 +- .../src/ngtsc/typecheck/src/ts_util.ts | 14 + .../compiler-cli/src/typescript_support.ts | 2 +- packages/language-service/plugin-factory.ts | 4 +- packages/localize/package.json | 2 + yarn.lock | 5 + 23 files changed, 1007 insertions(+), 11 deletions(-) create mode 100644 integration/typings_test_ts53/BUILD.bazel create mode 100644 integration/typings_test_ts53/include-all.ts create mode 100644 integration/typings_test_ts53/package.json create mode 100644 integration/typings_test_ts53/tsconfig.json create mode 100644 integration/typings_test_ts53/yarn.lock create mode 100644 packages/compiler-cli/src/ngtsc/translator/src/ts_util.ts diff --git a/goldens/public-api/localize/tools/index.md b/goldens/public-api/localize/tools/index.md index 4ff6062cefa6c..7116d7448403d 100644 --- a/goldens/public-api/localize/tools/index.md +++ b/goldens/public-api/localize/tools/index.md @@ -4,6 +4,10 @@ ```ts +/// +/// +/// + import { AbsoluteFsPath } from '@angular/compiler-cli/private/localize'; import { Element as Element_2 } from '@angular/compiler'; import { Logger } from '@angular/compiler-cli/private/localize'; diff --git a/integration/typings_test_ts53/BUILD.bazel b/integration/typings_test_ts53/BUILD.bazel new file mode 100644 index 0000000000000..015e2928e71a6 --- /dev/null +++ b/integration/typings_test_ts53/BUILD.bazel @@ -0,0 +1,9 @@ +load("//integration:index.bzl", "ng_integration_test") + +ng_integration_test( + name = "test", + # Special case for `typings_test_ts53` test as we want to pin + # `typescript` at version 5.3.x for that test and not link to the + # root @npm//typescript package. + pinned_npm_packages = ["typescript"], +) diff --git a/integration/typings_test_ts53/include-all.ts b/integration/typings_test_ts53/include-all.ts new file mode 100644 index 0000000000000..196ed86c52012 --- /dev/null +++ b/integration/typings_test_ts53/include-all.ts @@ -0,0 +1,69 @@ +/** + * @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 * as animations from '@angular/animations'; +import * as animationsBrowser from '@angular/animations/browser'; +import * as animationsBrowserTesting from '@angular/animations/browser/testing'; +import * as common from '@angular/common'; +import * as commonHttp from '@angular/common/http'; +import * as commonTesting from '@angular/common/testing'; +import * as commonHttpTesting from '@angular/common/http/testing'; +import * as compiler from '@angular/compiler'; +import * as core from '@angular/core'; +import * as coreTesting from '@angular/core/testing'; +import * as elements from '@angular/elements'; +import * as forms from '@angular/forms'; +import * as localize from '@angular/localize'; +import * as platformBrowser from '@angular/platform-browser'; +import * as platformBrowserDynamic from '@angular/platform-browser-dynamic'; +import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynamic/testing'; +import * as platformBrowserAnimations from '@angular/platform-browser/animations'; +import * as platformBrowserTesting from '@angular/platform-browser/testing'; +import * as platformServer from '@angular/platform-server'; +import * as platformServerInit from '@angular/platform-server/init'; +import * as platformServerTesting from '@angular/platform-server/testing'; +import * as router from '@angular/router'; +import * as routerTesting from '@angular/router/testing'; +import * as routerUpgrade from '@angular/router/upgrade'; +import * as serviceWorker from '@angular/service-worker'; +import * as upgrade from '@angular/upgrade'; +import * as upgradeStatic from '@angular/upgrade/static'; +import * as upgradeTesting from '@angular/upgrade/static/testing'; + +export default { + animations, + animationsBrowser, + animationsBrowserTesting, + common, + commonTesting, + commonHttp, + commonHttpTesting, + compiler, + core, + coreTesting, + elements, + forms, + localize, + platformBrowser, + platformBrowserTesting, + platformBrowserDynamic, + platformBrowserDynamicTesting, + platformBrowserAnimations, + platformServer, + platformServerInit, + platformServerTesting, + router, + routerTesting, + routerUpgrade, + serviceWorker, + upgrade, + upgradeStatic, + upgradeTesting, +}; diff --git a/integration/typings_test_ts53/package.json b/integration/typings_test_ts53/package.json new file mode 100644 index 0000000000000..a06d293ef24f8 --- /dev/null +++ b/integration/typings_test_ts53/package.json @@ -0,0 +1,29 @@ +{ + "name": "angular-integration", + "description": "Assert that users with TypeScript 5.3 can type-check an Angular application", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@angular/animations": "file:../../dist/packages-dist/animations", + "@angular/common": "file:../../dist/packages-dist/common", + "@angular/compiler": "file:../../dist/packages-dist/compiler", + "@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli", + "@angular/core": "file:../../dist/packages-dist/core", + "@angular/elements": "file:../../dist/packages-dist/elements", + "@angular/forms": "file:../../dist/packages-dist/forms", + "@angular/localize": "file:../../dist/packages-dist/localize", + "@angular/platform-browser": "file:../../dist/packages-dist/platform-browser", + "@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic", + "@angular/platform-server": "file:../../dist/packages-dist/platform-server", + "@angular/router": "file:../../dist/packages-dist/router", + "@angular/service-worker": "file:../../dist/packages-dist/service-worker", + "@angular/upgrade": "file:../../dist/packages-dist/upgrade", + "@types/jasmine": "file:../../node_modules/@types/jasmine", + "rxjs": "file:../../node_modules/rxjs", + "typescript": "5.3.1-rc", + "zone.js": "file:../../dist/zone.js-dist/archive/zone.js.tgz" + }, + "scripts": { + "test": "tsc" + } +} diff --git a/integration/typings_test_ts53/tsconfig.json b/integration/typings_test_ts53/tsconfig.json new file mode 100644 index 0000000000000..30e25c2209734 --- /dev/null +++ b/integration/typings_test_ts53/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "experimentalDecorators": true, + "module": "commonjs", + "moduleResolution": "node", + "outDir": "./dist/out-tsc", + "rootDir": ".", + "target": "es5", + "lib": [ + "es5", + "dom", + "es2015.collection", + "es2015.iterable", + "es2015.promise" + ], + "types": [], + }, + "files": [ + "include-all.ts", + "node_modules/@types/jasmine/index.d.ts" + ] +} diff --git a/integration/typings_test_ts53/yarn.lock b/integration/typings_test_ts53/yarn.lock new file mode 100644 index 0000000000000..22508712a5f6a --- /dev/null +++ b/integration/typings_test_ts53/yarn.lock @@ -0,0 +1,789 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@angular/animations@file:../../dist/packages-dist/animations": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/common@file:../../dist/packages-dist/common": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli": + version "17.1.0-next.0" + dependencies: + "@babel/core" "7.23.2" + "@jridgewell/sourcemap-codec" "^1.4.14" + chokidar "^3.0.0" + convert-source-map "^1.5.1" + reflect-metadata "^0.1.2" + semver "^7.0.0" + tslib "^2.3.0" + yargs "^17.2.1" + +"@angular/compiler@file:../../dist/packages-dist/compiler": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/core@file:../../dist/packages-dist/core": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/elements@file:../../dist/packages-dist/elements": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/forms@file:../../dist/packages-dist/forms": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/localize@file:../../dist/packages-dist/localize": + version "17.1.0-next.0" + dependencies: + "@babel/core" "7.23.2" + fast-glob "3.3.1" + yargs "^17.2.1" + +"@angular/platform-browser-dynamic@file:../../dist/packages-dist/platform-browser-dynamic": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/platform-browser@file:../../dist/packages-dist/platform-browser": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/platform-server@file:../../dist/packages-dist/platform-server": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + xhr2 "^0.2.0" + +"@angular/router@file:../../dist/packages-dist/router": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/service-worker@file:../../dist/packages-dist/service-worker": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@angular/upgrade@file:../../dist/packages-dist/upgrade": + version "17.1.0-next.0" + dependencies: + tslib "^2.3.0" + +"@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + +"@babel/compat-data@^7.22.9": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.2.tgz#6a12ced93455827037bfb5ed8492820d60fc32cc" + integrity sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ== + +"@babel/core@7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94" + integrity sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helpers" "^7.23.2" + "@babel/parser" "^7.23.0" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== + dependencies: + "@babel/types" "^7.23.0" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.15" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" + integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== + +"@babel/helpers@^7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767" + integrity sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== + +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@types/jasmine@file:../../node_modules/@types/jasmine": + version "5.1.1" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.21.9: + version "4.22.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" + integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== + dependencies: + caniuse-lite "^1.0.30001541" + electron-to-chromium "^1.4.535" + node-releases "^2.0.13" + update-browserslist-db "^1.0.13" + +caniuse-lite@^1.0.30001541: + version "1.0.30001561" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz#752f21f56f96f1b1a52e97aae98c57c562d5d9da" + integrity sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chokidar@^3.0.0: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +convert-source-map@^1.5.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +electron-to-chromium@^1.4.535: + version "1.4.577" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.577.tgz#a732f11cf4532be96e5e3f1197dcda54c2cec7ad" + integrity sha512-/5xHPH6f00SxhHw6052r+5S1xO7gHNc89hV7tqlvnStvKbSrDqc/u6AlwPvVWWNj+s4/KL6T6y8ih+nOY0qYNA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +fast-glob@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reflect-metadata@^0.1.2: + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +"rxjs@file:../../node_modules/rxjs": + version "6.6.7" + dependencies: + tslib "^1.9.0" + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.0.0: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.3.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +typescript@5.3.1-rc: + version "5.3.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.1-rc.tgz#c307d4b69ea0c1c2cd17d4dd7700d30f7197f829" + integrity sha512-NVq/AufFc6KVjmVPcuVwdCkhTQlTcMEyRYJPvaGhPvj+X80MYUF+90qf0//uvINPb2ULg9m91/gbdIOhN2cZqA== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +xhr2@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.2.1.tgz#4e73adc4f9cfec9cbd2157f73efdce3a5f108a93" + integrity sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.2.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +"zone.js@file:../../dist/zone.js-dist/archive/zone.js.tgz": + version "0.14.2" + resolved "file:../../dist/zone.js-dist/archive/zone.js.tgz#60b949ae4663cb0a4e6296217d9d45be86875ae3" + dependencies: + tslib "^2.3.0" diff --git a/package.json b/package.json index f6f456fcc26f0..2bd82c813761d 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "todomvc-common": "^1.0.5", "tslib": "^2.3.0", "tslint": "6.1.3", - "typescript": "5.2.2", + "typescript": "5.3.1-rc", "webtreemap": "^2.0.1", "xhr2": "0.2.1", "yargs": "^17.2.1" diff --git a/packages/bazel/package.json b/packages/bazel/package.json index 954acd68433e7..8db22b03bef9c 100644 --- a/packages/bazel/package.json +++ b/packages/bazel/package.json @@ -35,7 +35,7 @@ "rollup": "^2.56.3", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.9.0", - "typescript": ">=5.2 <5.3" + "typescript": ">=5.2 <5.4" }, "peerDependenciesMeta": { "terser": { diff --git a/packages/compiler-cli/package.json b/packages/compiler-cli/package.json index 94f94fe533a64..4c9c4147f428f 100644 --- a/packages/compiler-cli/package.json +++ b/packages/compiler-cli/package.json @@ -54,7 +54,7 @@ }, "peerDependencies": { "@angular/compiler": "0.0.0-PLACEHOLDER", - "typescript": ">=5.2 <5.3" + "typescript": ">=5.2 <5.4" }, "repository": { "type": "git", diff --git a/packages/compiler-cli/src/ngtsc/core/src/host.ts b/packages/compiler-cli/src/ngtsc/core/src/host.ts index 0b1ad3b8fecd5..555d9dd936709 100644 --- a/packages/compiler-cli/src/ngtsc/core/src/host.ts +++ b/packages/compiler-cli/src/ngtsc/core/src/host.ts @@ -61,6 +61,7 @@ export class DelegatingCompilerHost implements hasInvalidatedResolutions; resolveModuleNameLiterals; resolveTypeReferenceDirectiveReferences; + jsDocParsingMode; constructor(protected delegate: ExtendedTsCompilerHost) { // Excluded are 'getSourceFile' and 'fileExists', which are actually implemented by @@ -96,6 +97,9 @@ export class DelegatingCompilerHost implements this.resolveModuleNameLiterals = this.delegateMethod('resolveModuleNameLiterals'); this.resolveTypeReferenceDirectiveReferences = this.delegateMethod('resolveTypeReferenceDirectiveReferences'); + // TODO(crisbeto): can be removed when we drop support for TS 5.2. + // @ts-ignore + this.jsDocParsingMode = this.delegateMethod('jsDocParsingMode'); } private delegateMethod(name: M): diff --git a/packages/compiler-cli/src/ngtsc/docs/src/enum_extractor.ts b/packages/compiler-cli/src/ngtsc/docs/src/enum_extractor.ts index e9b7d3171d6cb..426d73baf30af 100644 --- a/packages/compiler-cli/src/ngtsc/docs/src/enum_extractor.ts +++ b/packages/compiler-cli/src/ngtsc/docs/src/enum_extractor.ts @@ -43,7 +43,10 @@ function extractEnumMembers( function getEnumMemberValue(memberNode: ts.EnumMember): string { // If the enum member has a child number literal or string literal, // we use that literal as the "value" of the member. - const literal = - memberNode.getChildren().find(n => ts.isNumericLiteral(n) || ts.isStringLiteral(n)); + const literal = memberNode.getChildren().find(n => { + return ts.isNumericLiteral(n) || ts.isStringLiteral(n) || + (ts.isPrefixUnaryExpression(n) && n.operator === ts.SyntaxKind.MinusToken && + ts.isNumericLiteral(n.operand)); + }); return literal?.getText() ?? ''; } diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts index 07d84bb5831ba..23349cc0e43fa 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts @@ -198,6 +198,10 @@ runInEachFileSystem(() => { expect(evaluate('const a = null;', 'a')).toEqual(null); }); + it('supports negative numbers', () => { + expect(evaluate('const a = -123;', 'a')).toEqual(-123); + }); + it('supports destructuring array variable declarations', () => { const code = ` const [a, b, c, d] = [0, 1, 2, 3]; diff --git a/packages/compiler-cli/src/ngtsc/program_driver/src/ts_create_program_driver.ts b/packages/compiler-cli/src/ngtsc/program_driver/src/ts_create_program_driver.ts index 1a539faea9d74..5ce7dd44e07ff 100644 --- a/packages/compiler-cli/src/ngtsc/program_driver/src/ts_create_program_driver.ts +++ b/packages/compiler-cli/src/ngtsc/program_driver/src/ts_create_program_driver.ts @@ -46,6 +46,7 @@ export class DelegatingCompilerHost implements hasInvalidatedResolutions; resolveModuleNameLiterals; resolveTypeReferenceDirectiveReferences; + jsDocParsingMode; constructor(protected delegate: ts.CompilerHost) { // Excluded are 'getSourceFile', 'fileExists' and 'writeFile', which are actually implemented by @@ -75,6 +76,9 @@ export class DelegatingCompilerHost implements this.resolveModuleNameLiterals = this.delegateMethod('resolveModuleNameLiterals'); this.resolveTypeReferenceDirectiveReferences = this.delegateMethod('resolveTypeReferenceDirectiveReferences'); + // TODO(crisbeto): can be removed when we drop support for TS 5.2. + // @ts-ignore + this.jsDocParsingMode = this.delegateMethod('jsDocParsingMode'); } private delegateMethod(name: M): ts.CompilerHost[M] { diff --git a/packages/compiler-cli/src/ngtsc/translator/src/ts_util.ts b/packages/compiler-cli/src/ngtsc/translator/src/ts_util.ts new file mode 100644 index 0000000000000..d1603700625f5 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/translator/src/ts_util.ts @@ -0,0 +1,23 @@ +/*! + * @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 ts from 'typescript'; + +/** + * Creates a TypeScript node representing a numeric value. + */ +export function tsNumericExpression(value: number): ts.NumericLiteral|ts.PrefixUnaryExpression { + // As of TypeScript 5.3 negative numbers are represented as `prefixUnaryOperator` and passing a + // negative number (even as a string) into `createNumericLiteral` will result in an error. + if (value < 0) { + const operand = ts.factory.createNumericLiteral(Math.abs(value)); + return ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, operand); + } + + return ts.factory.createNumericLiteral(value); +} diff --git a/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts index c0bb3e8e84bfe..42653912341e6 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts @@ -14,6 +14,7 @@ import {ReflectionHost} from '../../reflection'; import {Context} from './context'; import {ImportManager} from './import_manager'; +import {tsNumericExpression} from './ts_util'; import {TypeEmitter} from './type_emitter'; @@ -133,7 +134,7 @@ class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor { return ts.factory.createLiteralTypeNode( ast.value ? ts.factory.createTrue() : ts.factory.createFalse()); } else if (typeof ast.value === 'number') { - return ts.factory.createLiteralTypeNode(ts.factory.createNumericLiteral(ast.value)); + return ts.factory.createLiteralTypeNode(tsNumericExpression(ast.value)); } else { return ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(ast.value)); } diff --git a/packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts b/packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts index 368ac0e6f848b..1d5edd6026453 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts @@ -8,6 +8,7 @@ import ts from 'typescript'; import {AstFactory, BinaryOperator, LeadingComment, ObjectLiteralProperty, SourceMapRange, TemplateLiteral, UnaryOperator, VariableDeclarationType} from './api/ast_factory'; +import {tsNumericExpression} from './ts_util'; /** * Different optimizers use different annotations on a function or method call to indicate its pure @@ -161,7 +162,7 @@ export class TypeScriptAstFactory implements AstFactory { expect(generate(literal)).toEqual('42'); }); + it('should create a negative number literal', () => { + const {generate} = setupStatements(); + const literal = factory.createLiteral(-42); + expect(ts.isPrefixUnaryExpression(literal)).toBe(true); + expect(generate(literal)).toEqual('-42'); + }); + it('should create a number literal for `NaN`', () => { const {generate} = setupStatements(); const literal = factory.createLiteral(NaN); diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts index a6eb52bf2db7f..ee39ae51c8942 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts @@ -12,7 +12,7 @@ import ts from 'typescript'; import {TypeCheckingConfig} from '../api'; import {addParseSpanInfo, wrapForDiagnostics, wrapForTypeChecker} from './diagnostics'; -import {tsCastToAny} from './ts_util'; +import {tsCastToAny, tsNumericExpression} from './ts_util'; export const NULL_AS_ANY = ts.factory.createAsExpression( ts.factory.createNull(), ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)); @@ -198,7 +198,7 @@ class AstTranslator implements AstVisitor { } else if (typeof ast.value === 'string') { node = ts.factory.createStringLiteral(ast.value); } else if (typeof ast.value === 'number') { - node = ts.factory.createNumericLiteral(ast.value); + node = tsNumericExpression(ast.value); } else if (typeof ast.value === 'boolean') { node = ast.value ? ts.factory.createTrue() : ts.factory.createFalse(); } else { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ts_util.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/ts_util.ts index fdbab237b8767..7b496f0f529b6 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ts_util.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/ts_util.ts @@ -146,3 +146,17 @@ export function isAccessExpression(node: ts.Node): node is ts.ElementAccessExpre ts.PropertyAccessExpression { return ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node); } + +/** + * Creates a TypeScript node representing a numeric value. + */ +export function tsNumericExpression(value: number): ts.NumericLiteral|ts.PrefixUnaryExpression { + // As of TypeScript 5.3 negative numbers are represented as `prefixUnaryOperator` and passing a + // negative number (even as a string) into `createNumericLiteral` will result in an error. + if (value < 0) { + const operand = ts.factory.createNumericLiteral(Math.abs(value)); + return ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, operand); + } + + return ts.factory.createNumericLiteral(value); +} diff --git a/packages/compiler-cli/src/typescript_support.ts b/packages/compiler-cli/src/typescript_support.ts index 7c1190ca69994..6df2ecec9c18c 100644 --- a/packages/compiler-cli/src/typescript_support.ts +++ b/packages/compiler-cli/src/typescript_support.ts @@ -26,7 +26,7 @@ const MIN_TS_VERSION = '5.2.0'; * Note: this check is disabled in g3, search for * `angularCompilerOptions.disableTypeScriptVersionCheck` config param value in g3. */ -const MAX_TS_VERSION = '5.3.0'; +const MAX_TS_VERSION = '5.4.0'; /** * The currently used version of TypeScript, which can be adjusted for testing purposes using diff --git a/packages/language-service/plugin-factory.ts b/packages/language-service/plugin-factory.ts index 58c5c51408f0a..d913d16cbd2f2 100644 --- a/packages/language-service/plugin-factory.ts +++ b/packages/language-service/plugin-factory.ts @@ -24,7 +24,9 @@ export const factory: ts.server.PluginModuleFactory = (tsModule): PluginModule = return plugin.create(info); }, getExternalFiles(project: ts.server.Project): string[] { - return plugin?.getExternalFiles?.(project) ?? []; + // TODO(crisbeto): hardcoded value `2` to be replaced with `ts.ProgramUpdateLevel.Full` + // after we drop support for TS 5.2. + return plugin?.getExternalFiles?.(project, 2) ?? []; }, onConfigurationChanged(config: PluginConfig): void { plugin?.onConfigurationChanged?.(config); diff --git a/packages/localize/package.json b/packages/localize/package.json index 69e12ae192df5..001aca6eca908 100644 --- a/packages/localize/package.json +++ b/packages/localize/package.json @@ -35,6 +35,8 @@ ], "dependencies": { "@babel/core": "7.23.2", + "@types/babel__core": "7.20.2", + "@types/babel__traverse": "7.20.2", "fast-glob": "3.3.1", "yargs": "^17.2.1" }, diff --git a/yarn.lock b/yarn.lock index b220f14ab30e3..4784f0b4d53ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14817,6 +14817,11 @@ typescript@5.2.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +typescript@5.3.1-rc: + version "5.3.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.1-rc.tgz#c307d4b69ea0c1c2cd17d4dd7700d30f7197f829" + integrity sha512-NVq/AufFc6KVjmVPcuVwdCkhTQlTcMEyRYJPvaGhPvj+X80MYUF+90qf0//uvINPb2ULg9m91/gbdIOhN2cZqA== + typescript@^3.9.10, typescript@^3.9.7: version "3.9.10" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" From 5ee935e7feb89d613506a335f72ff9d5d100a985 Mon Sep 17 00:00:00 2001 From: Doug Parker Date: Tue, 7 Nov 2023 14:41:23 -0800 Subject: [PATCH 36/43] release: bump DevTools version to 1.0.8 (#52759) PR Close #52759 --- .../projects/shell-browser/src/manifest/manifest.chrome.json | 4 ++-- .../projects/shell-browser/src/manifest/manifest.firefox.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devtools/projects/shell-browser/src/manifest/manifest.chrome.json b/devtools/projects/shell-browser/src/manifest/manifest.chrome.json index 1dc05c652cf39..68106d51a9b1a 100644 --- a/devtools/projects/shell-browser/src/manifest/manifest.chrome.json +++ b/devtools/projects/shell-browser/src/manifest/manifest.chrome.json @@ -3,8 +3,8 @@ "short_name": "Angular DevTools", "name": "Angular DevTools", "description": "Angular DevTools extends Chrome DevTools adding Angular specific debugging and profiling capabilities.", - "version": "1.0.7", - "version_name": "1.0.7", + "version": "1.0.8", + "version_name": "1.0.8", "minimum_chrome_version": "102", "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'" diff --git a/devtools/projects/shell-browser/src/manifest/manifest.firefox.json b/devtools/projects/shell-browser/src/manifest/manifest.firefox.json index f619f4dd6bef0..70bf635d3e301 100644 --- a/devtools/projects/shell-browser/src/manifest/manifest.firefox.json +++ b/devtools/projects/shell-browser/src/manifest/manifest.firefox.json @@ -3,7 +3,7 @@ "short_name": "Angular DevTools", "name": "Angular DevTools", "description": "Angular DevTools extends Firefox DevTools adding Angular specific debugging and profiling capabilities.", - "version": "1.0.7", + "version": "1.0.8", "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", "icons": { "16": "assets/icon16.png", From 50a06fa451f11cc5e9f42e4692b13c66c9355efb Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Mon, 6 Nov 2023 15:39:14 -0800 Subject: [PATCH 37/43] refactor(compiler): More consistent sorting of i18n params (#52698) Previously the template pipeline sorted i18n message params before adding the sub-message placeholders. Now its sorts after all placeholders are added. Both the template pipeline and TemplateDefinitionBuilder previously failed to sort the post-processing params. They both now sort these as well. This is safe to change in TemplateDefinitionBuilder, as it does not change anything about the functionality, it simply ensures that params map in the output has the keys ordered in a way that can be easily reproduced in the template pipeline. PR Close #52698 --- .../test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.js | 2 +- .../r3_view_compiler_i18n/icu_logic/custom_interpolation.js | 2 +- .../r3_view_compiler_i18n/icu_logic/expressions.js | 2 +- .../r3_view_compiler_i18n/icu_logic/html_content.js | 2 +- .../icu_logic/icu_with_interpolations.js | 4 ++-- .../r3_view_compiler_i18n/icu_logic/named_interpolations.js | 2 +- .../icu_logic/nested_icu_in_other_block.js | 2 +- packages/compiler/src/render3/view/template.ts | 4 +++- .../template/pipeline/src/phases/extract_i18n_messages.ts | 2 +- .../template/pipeline/src/phases/i18n_const_collection.ts | 6 ++++++ 10 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.js index 3e697e2ba6281..0aa9bceb8f158 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.js @@ -34,7 +34,7 @@ decls: 4, vars: 3, consts: () => { __i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) - __i18nIcuMsg__('{VAR_SELECT, select, 0 {no emails} 1 {one email} other {{INTERPOLATION} emails}}', [ ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`]], {}) + __i18nIcuMsg__('{VAR_SELECT, select, 0 {no emails} 1 {one email} other {{INTERPOLATION} emails}}', [['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) return [ $i18n_0$, ["title", "icu only", __AttributeMarker.Template__, "ngIf"], diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/custom_interpolation.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/custom_interpolation.js index 09d4f83e45a7c..b959cb3736cdf 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/custom_interpolation.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/custom_interpolation.js @@ -1,5 +1,5 @@ consts: () => { - __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {{INTERPOLATION}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`]], {}) + __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {{INTERPOLATION}}}', [['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) return [ $i18n_0$ ]; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/expressions.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/expressions.js index 20e8f1d00de90..cedd046b70c87 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/expressions.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/expressions.js @@ -1,7 +1,7 @@ decls: 2, vars: 2, consts: () => { - __i18nIcuMsg__('{VAR_SELECT, select, male {male of age: {INTERPOLATION}} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`]], {}) + __i18nIcuMsg__('{VAR_SELECT, select, male {male of age: {INTERPOLATION}} female {female} other {other}}', [['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) return [ $i18n_0$ ]; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/html_content.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/html_content.js index ba0b050456358..b797dbdde7543 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/html_content.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/html_content.js @@ -1,7 +1,7 @@ decls: 5, vars: 1, consts: () => { - __i18nIcuMsg__('{VAR_SELECT, select, male {male - {START_BOLD_TEXT}male{CLOSE_BOLD_TEXT}} female {female {START_BOLD_TEXT}female{CLOSE_BOLD_TEXT}} other {{START_TAG_DIV}{START_ITALIC_TEXT}other{CLOSE_ITALIC_TEXT}{CLOSE_TAG_DIV}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['START_BOLD_TEXT', ''], ['CLOSE_BOLD_TEXT', ''], ['START_ITALIC_TEXT', ''], ['CLOSE_ITALIC_TEXT', ''], ['START_TAG_DIV', '
'], ['CLOSE_TAG_DIV', '
']], {}) + __i18nIcuMsg__('{VAR_SELECT, select, male {male - {START_BOLD_TEXT}male{CLOSE_BOLD_TEXT}} female {female {START_BOLD_TEXT}female{CLOSE_BOLD_TEXT}} other {{START_TAG_DIV}{START_ITALIC_TEXT}other{CLOSE_ITALIC_TEXT}{CLOSE_TAG_DIV}}}', [['CLOSE_BOLD_TEXT', ''], ['CLOSE_ITALIC_TEXT', ''], ['CLOSE_TAG_DIV', '
'], ['START_BOLD_TEXT', ''], ['START_ITALIC_TEXT', ''], ['START_TAG_DIV', '
'], ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) __i18nMsg__(' {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}', [['closeBoldText', String.raw`\uFFFD/#2\uFFFD`], ['closeItalicText', String.raw`\uFFFD/#4\uFFFD`], ['closeTagDiv', String.raw`\uFFFD/#3\uFFFD`], ['icu', '$I18N_1$', '4731057199984078679'], ['startBoldText', String.raw`\uFFFD#2\uFFFD`], ['startItalicText', String.raw`\uFFFD#4\uFFFD`], ['startTagDiv', String.raw`\uFFFD#3\uFFFD`]], {original_code: {'closeBoldText': '', 'closeItalicText': '', 'closeTagDiv': '
', 'icu': '{gender, select, male {male - male} female {female female} other {
other
}}', 'startBoldText': '', 'startItalicText': '', 'startTagDiv': '
'}}, {}) return [ $i18n_1$, diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/icu_with_interpolations.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/icu_with_interpolations.js index e4c60035750a5..75b8c8bc9afcc 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/icu_with_interpolations.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/icu_with_interpolations.js @@ -15,8 +15,8 @@ function MyComponent_span_2_Template(rf, ctx) { decls: 3, vars: 4, consts: () => { - __i18nIcuMsg__('{VAR_SELECT, select, male {male {INTERPOLATION}} female {female {INTERPOLATION_1}} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], ['INTERPOLATION_1', String.raw`\uFFFD2\uFFFD`]], {}) - __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {INTERPOLATION}}}', [['VAR_SELECT', String.raw`\uFFFD0:1\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1:1\uFFFD`]], {}) + __i18nIcuMsg__('{VAR_SELECT, select, male {male {INTERPOLATION}} female {female {INTERPOLATION_1}} other {other}}', [['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], ['INTERPOLATION_1', String.raw`\uFFFD2\uFFFD`], ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) + __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {INTERPOLATION}}}', [['INTERPOLATION', String.raw`\uFFFD1:1\uFFFD`], ['VAR_SELECT', String.raw`\uFFFD0:1\uFFFD`]], {}) __i18nMsg__(' {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}', [['closeTagSpan', String.raw`\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD`], ['icu', '$i18n_0$', '567200399523107034'], ['icu_1', '$i18n_1$', '5762277079421427850'], ['startTagSpan', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`]], {original_code: {'closeTagSpan': '', 'icu': '{gender, select, male {male {{ weight }}} female {female {{ height }}} other {other}}', 'icu_1': '{age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {{ otherAge }}}}', 'startTagSpan': ''}}, {}) return [ $i18n_2$, diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/named_interpolations.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/named_interpolations.js index de0f5526adb1a..8b57015ff1b2c 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/named_interpolations.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/named_interpolations.js @@ -1,7 +1,7 @@ decls: 2, vars: 4, consts: () => { - __i18nIcuMsg__('{VAR_SELECT, select, male {male {PH_A}} female {female {PH_B}} other {other {PH_WITH_SPACES}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['PH_A', String.raw`\uFFFD1\uFFFD`], ['PH_B', String.raw`\uFFFD2\uFFFD`], ['PH_WITH_SPACES', String.raw`\uFFFD3\uFFFD`]], {}) + __i18nIcuMsg__('{VAR_SELECT, select, male {male {PH_A}} female {female {PH_B}} other {other {PH_WITH_SPACES}}}', [['PH_WITH_SPACES', String.raw`\uFFFD3\uFFFD`], ['PH_A', String.raw`\uFFFD1\uFFFD`], ['PH_B', String.raw`\uFFFD2\uFFFD`], ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) return [ $i18n_0$ ]; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/nested_icu_in_other_block.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/nested_icu_in_other_block.js index 74a2a7e5f914a..67d3cfc1e8013 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/nested_icu_in_other_block.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/nested_icu_in_other_block.js @@ -1,7 +1,7 @@ decls: 2, vars: 3, consts: () => { - __i18nIcuMsg__('{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, cat {cats} dog {dogs} other {animals}} !} other {other - {INTERPOLATION}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['VAR_PLURAL', String.raw`\uFFFD1\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD2\uFFFD`]], {}) + __i18nIcuMsg__('{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, cat {cats} dog {dogs} other {animals}} !} other {other - {INTERPOLATION}}}', [['INTERPOLATION', String.raw`\uFFFD2\uFFFD`], ['VAR_PLURAL', String.raw`\uFFFD1\uFFFD`], ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) return [ $i18n_0$ ]; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 8906de8b49bfd..2a89c7ddd7343 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -1118,7 +1118,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // inside ICUs) // - all ICU vars (such as `VAR_SELECT` or `VAR_PLURAL`) are replaced with correct values const transformFn = (raw: o.ReadVarExpr) => { - const params = {...vars, ...placeholders}; + // Sort the map entries in the compiled output. This makes it easy to acheive identical output + // in the template pipeline compiler. + const params = Object.fromEntries(Object.entries({...vars, ...placeholders}).sort()); const formatted = formatI18nPlaceholderNamesInMap(params, /* useCamelCase */ false); return invokeInstruction(null, R3.i18nPostprocess, [raw, mapLiteral(formatted, true)]); }; diff --git a/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts b/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts index 4f08a4c7fa26f..3b722a711bdf5 100644 --- a/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts +++ b/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts @@ -130,7 +130,7 @@ function createI18nMessage( */ function formatParams(params: Map): Map { const result = new Map(); - for (const [placeholder, placeholderValues] of [...params].sort()) { + for (const [placeholder, placeholderValues] of params) { const serializedValues = formatParamValues(placeholderValues); if (serializedValues !== null) { result.set(placeholder, o.literal(formatParamValues(placeholderValues))); diff --git a/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts b/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts index b5a2664e564ee..43606ed3fa389 100644 --- a/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts +++ b/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts @@ -83,6 +83,9 @@ function collectMessage( messageOp.params.set(subMessage.messagePlaceholder!, subMessageVar); } + // Sort the params for consistency with TemaplateDefinitionBuilder output. + messageOp.params = new Map([...messageOp.params.entries()].sort()); + // Check that the message has all of its parameters filled out. assertAllParamsResolved(messageOp); @@ -97,6 +100,9 @@ function collectMessage( // If nescessary, add a post-processing step and resolve any placeholder params that are // set in post-processing. if (messageOp.needsPostprocessing) { + // Sort the post-processing params for consistency with TemaplateDefinitionBuilder output. + messageOp.postprocessingParams = new Map([...messageOp.postprocessingParams.entries()].sort()); + const extraTransformFnParams: o.Expression[] = []; if (messageOp.postprocessingParams.size > 0) { extraTransformFnParams.push(o.literalMap( From ef6999f2f6e55c0e56dc506a7dac881c2f7f7fa4 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 7 Nov 2023 13:26:29 -0800 Subject: [PATCH 38/43] refactor(compiler): Support element tags inside ICUs (#52698) ICUs that contain element tags need extra parameters for the i18n message. These are in addition to the element slot params that are already added to the parent i18n block's params. In this commit we add a new phase to fill in these placeholders. PR Close #52698 --- .../icu_logic/TEST_CASES.json | 3 +- .../src/template/pipeline/ir/src/enums.ts | 9 ++- .../src/template/pipeline/src/emit.ts | 2 + .../src/phases/extract_i18n_messages.ts | 5 ++ .../resolve_i18n_element_placeholders.ts | 2 +- .../resolve_i18n_expression_placeholders.ts | 7 +- .../phases/resolve_i18n_icu_placeholders.ts | 74 +++++++++++++++++++ 7 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json index b4281ce99373d..8f5fb9f591f64 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json @@ -85,8 +85,7 @@ "verifyUniqueConsts" ] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should handle icus with expressions", diff --git a/packages/compiler/src/template/pipeline/ir/src/enums.ts b/packages/compiler/src/template/pipeline/ir/src/enums.ts index e0f1c0c0e298f..f1f8331834bf9 100644 --- a/packages/compiler/src/template/pipeline/ir/src/enums.ts +++ b/packages/compiler/src/template/pipeline/ir/src/enums.ts @@ -500,12 +500,12 @@ export enum I18nParamValueFlags { /** * This value represtents an element tag. */ - ElementTag = 0b001, + ElementTag = 0b1, /** * This value represents a template tag. */ - TemplateTag = 0b0010, + TemplateTag = 0b10, /** * This value represents the opening of a tag. @@ -516,6 +516,11 @@ export enum I18nParamValueFlags { * This value represents the closing of a tag. */ CloseTag = 0b1000, + + /** + * This value represents an i18n expression index. + */ + ExpressionIndex = 0b10000 } /** diff --git a/packages/compiler/src/template/pipeline/src/emit.ts b/packages/compiler/src/template/pipeline/src/emit.ts index 0ded17a42098e..6d95af2ea880e 100644 --- a/packages/compiler/src/template/pipeline/src/emit.ts +++ b/packages/compiler/src/template/pipeline/src/emit.ts @@ -61,6 +61,7 @@ import {resolveContexts} from './phases/resolve_contexts'; import {resolveDollarEvent} from './phases/resolve_dollar_event'; import {resolveI18nElementPlaceholders} from './phases/resolve_i18n_element_placeholders'; import {resolveI18nExpressionPlaceholders} from './phases/resolve_i18n_expression_placeholders'; +import {resolveI18nIcuPlaceholders} from './phases/resolve_i18n_icu_placeholders'; import {resolveNames} from './phases/resolve_names'; import {resolveSanitizers} from './phases/resolve_sanitizers'; import {saveAndRestoreView} from './phases/save_restore_view'; @@ -127,6 +128,7 @@ const phases: Phase[] = [ {kind: Kind.Tmpl, fn: createDeferDepsFns}, {kind: Kind.Tmpl, fn: resolveI18nElementPlaceholders}, {kind: Kind.Tmpl, fn: resolveI18nExpressionPlaceholders}, + {kind: Kind.Tmpl, fn: resolveI18nIcuPlaceholders}, {kind: Kind.Tmpl, fn: mergeI18nContexts}, {kind: Kind.Tmpl, fn: extractI18nMessages}, {kind: Kind.Tmpl, fn: generateTrackFns}, diff --git a/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts b/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts index 3b722a711bdf5..a01dbec48ec76 100644 --- a/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts +++ b/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts @@ -156,6 +156,11 @@ function formatParamValues(values: ir.I18nParamValue[]): string|null { * Formats a single `I18nParamValue` into a string */ function formatValue(value: ir.I18nParamValue): string { + // If there are no special flags, just return the raw value. + if (value.flags === ir.I18nParamValueFlags.None) { + return `${value.value}`; + } + let tagMarker = ''; let closeMarker = ''; if (value.flags & ir.I18nParamValueFlags.ElementTag) { diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_element_placeholders.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_element_placeholders.ts index 736056622f10c..90e9b0f290985 100644 --- a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_element_placeholders.ts +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_element_placeholders.ts @@ -122,7 +122,7 @@ function getSubTemplateIndexForTemplateTag( /** Add a param value to the given params map. */ function addParam( params: Map, placeholder: string, value: string|number, - subTemplateIndex: number|null, flags = ir.I18nParamValueFlags.None) { + subTemplateIndex: number|null, flags: ir.I18nParamValueFlags) { const values = params.get(placeholder) ?? []; values.push({value, subTemplateIndex, flags}); params.set(placeholder, values); diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.ts index f99955e673955..f92673a283c91 100644 --- a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.ts +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.ts @@ -43,8 +43,11 @@ export function resolveI18nExpressionPlaceholders(job: ComponentCompilationJob) i18nContext.params : i18nContext.postprocessingParams; const values = params.get(op.i18nPlaceholder) || []; - values.push( - {value: index, subTemplateIndex: subTemplateIndex, flags: ir.I18nParamValueFlags.None}); + values.push({ + value: index, + subTemplateIndex: subTemplateIndex, + flags: ir.I18nParamValueFlags.ExpressionIndex + }); params.set(op.i18nPlaceholder, values); expressionIndices.set(op.context, index + 1); diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts new file mode 100644 index 0000000000000..da6977e7bc600 --- /dev/null +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts @@ -0,0 +1,74 @@ +/** + * @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 * as i18n from '../../../../i18n/i18n_ast'; +import * as ir from '../../ir'; +import {CompilationJob} from '../compilation'; + +/** + * Resolves placeholders for element tags inside of an ICU. + */ +export function resolveI18nIcuPlaceholders(job: CompilationJob) { + const contextOps = new Map(); + for (const unit of job.units) { + for (const op of unit.create) { + switch (op.kind) { + case ir.OpKind.I18nContext: + contextOps.set(op.xref, op); + break; + } + } + } + + for (const unit of job.units) { + for (const op of unit.create) { + switch (op.kind) { + case ir.OpKind.Icu: + if (op.context === null) { + throw Error('Icu should have its i18n context set.'); + } + const i18nContext = contextOps.get(op.context)!; + op.icu.visit(new ResolveIcuPlaceholdersVisitor(i18nContext.postprocessingParams)); + break; + } + } + } +} + +/** + * Visitor for i18n AST that resolves ICU params into the given map. + */ +class ResolveIcuPlaceholdersVisitor extends i18n.RecurseVisitor { + constructor(readonly params: Map) { + super(); + } + + override visitTagPlaceholder(placeholder: i18n.TagPlaceholder) { + super.visitTagPlaceholder(placeholder); + + // Add the start and end source span for tag placeholders. These need to be recorded for + // elements inside ICUs. The slots for the elements were recorded separately under the i18n + // block's context as part of the `resolveI18nElementPlaceholders` phase. + if (placeholder.startName && placeholder.startSourceSpan && + !this.params.has(placeholder.startName)) { + this.params.set(placeholder.startName, [{ + value: placeholder.startSourceSpan?.toString(), + subTemplateIndex: null, + flags: ir.I18nParamValueFlags.None + }]); + } + if (placeholder.closeName && placeholder.endSourceSpan && + !this.params.has(placeholder.closeName)) { + this.params.set(placeholder.closeName, [{ + value: placeholder.endSourceSpan?.toString(), + subTemplateIndex: null, + flags: ir.I18nParamValueFlags.None + }]); + } + } +} From 3a0ac32dcbed859a1b3bc08eb9fe3e140aaf36cd Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 7 Nov 2023 14:28:47 -0800 Subject: [PATCH 39/43] refactor(compiler): Support expressions inside ICUs (#52698) Previously ICUs were assumed to only generate a single i18n expression per ICU. However, it is possible for ICUs to contain text interpolations which requires additional expressions. This commit adds support for multiple expressions per ICU. PR Close #52698 --- .../icu_logic/TEST_CASES.json | 13 ++-- .../icu_logic/bare_icu.pipeline.js | 64 +++++++++++++++++++ .../{bare_icu.js => bare_icu.template.js} | 0 .../src/phases/create_i18n_icu_expressions.ts | 36 +++++++++-- .../phases/resolve_i18n_icu_placeholders.ts | 2 +- 5 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.pipeline.js rename packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/{bare_icu.js => bare_icu.template.js} (100%) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json index 8f5fb9f591f64..d9e44581284c4 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json @@ -50,13 +50,19 @@ ], "expectations": [ { + "files": [ + { + "generated": "bare_icu.js", + "expected": "bare_icu.template.js", + "templatePipelineExpected": "bare_icu.pipeline.js" + } + ], "extraChecks": [ "verifyPlaceholdersIntegrity", "verifyUniqueConsts" ] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should support interpolation with custom interpolation config", @@ -70,8 +76,7 @@ "verifyUniqueConsts" ] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should handle icus with html", diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.pipeline.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.pipeline.js new file mode 100644 index 0000000000000..5949574631e5a --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.pipeline.js @@ -0,0 +1,64 @@ +function $MyComponent_div_2_Template$(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelementStart(0, "div", 5); + $r3$.ɵɵtext(1, " "); + $r3$.ɵɵi18n(2, 1); + $r3$.ɵɵtext(3, " "); + $r3$.ɵɵelementEnd(); + } + if (rf & 2) { + const $ctx_r0$ = $r3$.ɵɵnextContext(); + $r3$.ɵɵadvance(2); + $r3$.ɵɵi18nExp($ctx_r0$.age); + $r3$.ɵɵi18nApply(2); + } +} +… +function $MyComponent_div_3_Template$(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelementStart(0, "div", 6); + $r3$.ɵɵtext(1, " You have "); + $r3$.ɵɵi18n(2, 2); + $r3$.ɵɵtext(3, ". "); + $r3$.ɵɵelementEnd(); + } + if (rf & 2) { + const $ctx_r1$ = $r3$.ɵɵnextContext(); + $r3$.ɵɵadvance(2); + $r3$.ɵɵi18nExp($ctx_r1$.count)($ctx_r1$.count); + $r3$.ɵɵi18nApply(2); + } +} +… +decls: 4, +vars: 3, +consts: () => { + __i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) + __i18nIcuMsg__('{VAR_SELECT, select, 0 {no emails} 1 {one email} other {{INTERPOLATION} emails}}', [['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) + return [ + $i18n_0$, + $i18n_1$, + $i18n_2$, + ["title", "icu only", __AttributeMarker.Template__, "ngIf"], + ["title", "icu and text", __AttributeMarker.Template__, "ngIf"], + ["title", "icu only"], + ["title", "icu and text"] + ]; +}, +template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelementStart(0, "div"); + $r3$.ɵɵi18n(1, 0); + $r3$.ɵɵelementEnd(); + $r3$.ɵɵtemplate(2, $MyComponent_div_2_Template$, 4, 1, "div", 3)(3, $MyComponent_div_3_Template$, 4, 2, "div", 4); + } + if (rf & 2) { + $r3$.ɵɵadvance(1); + $r3$.ɵɵi18nExp(ctx.gender); + $r3$.ɵɵi18nApply(1); + $r3$.ɵɵadvance(1); + $r3$.ɵɵproperty("ngIf", ctx.visible); + $r3$.ɵɵadvance(1); + $r3$.ɵɵproperty("ngIf", ctx.available); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.template.js similarity index 100% rename from packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.js rename to packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/bare_icu.template.js diff --git a/packages/compiler/src/template/pipeline/src/phases/create_i18n_icu_expressions.ts b/packages/compiler/src/template/pipeline/src/phases/create_i18n_icu_expressions.ts index cc62b17ab0b60..b4a1fb2dc1cac 100644 --- a/packages/compiler/src/template/pipeline/src/phases/create_i18n_icu_expressions.ts +++ b/packages/compiler/src/template/pipeline/src/phases/create_i18n_icu_expressions.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import * as i18n from '../../../../i18n/i18n_ast'; import * as ir from '../../ir'; import {CompilationJob} from '../compilation'; @@ -46,15 +47,36 @@ export function createI18nIcuExpressions(job: CompilationJob) { } const i18nContext = i18nContexts.get(icuOp.context)!; const i18nBlock = i18nBlocks.get(i18nContext.i18nBlock)!; - ir.OpList.replace( - op, - ir.createI18nExpressionOp( - i18nContext.xref, i18nBlock.xref, i18nBlock.handle, - new ir.LexicalReadExpr(icuOp.icu.expression), icuOp.icu.expressionPlaceholder, - // ICU-based i18n Expressions are resolved during post-processing. - ir.I18nParamResolutionTime.Postproccessing, null!)); + const expressionOp = ir.createI18nExpressionOp( + i18nContext.xref, i18nBlock.xref, i18nBlock.handle, + new ir.LexicalReadExpr(icuOp.icu.expression), icuOp.icu.expressionPlaceholder, + // ICU-based i18n Expressions are resolved during post-processing. + ir.I18nParamResolutionTime.Postproccessing, null!); + ir.OpList.replace(op, expressionOp); + icuOp.icu.visit(new AddIcuExpressionVisitor( + i18nContext.xref, i18nBlock.xref, i18nBlock.handle, expressionOp)); break; } } } } + +/** + * Visitor for i18n AST that adds i18nExpression ops for expressions nested in an ICU. + */ +class AddIcuExpressionVisitor extends i18n.RecurseVisitor { + constructor( + private contextId: ir.XrefId, private i18nBlockId: ir.XrefId, + private i18nBlockHandle: ir.SlotHandle, private insertAfterOp: ir.UpdateOp) { + super(); + } + + override visitPlaceholder(placeholder: i18n.Placeholder) { + const expressionOp = ir.createI18nExpressionOp( + this.contextId, this.i18nBlockId, this.i18nBlockHandle, + new ir.LexicalReadExpr(placeholder.value), placeholder.name, + ir.I18nParamResolutionTime.Postproccessing, null!); + ir.OpList.insertAfter(expressionOp, this.insertAfterOp); + super.visitPlaceholder(placeholder); + } +} diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts index da6977e7bc600..af4930bb9c1d4 100644 --- a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts @@ -44,7 +44,7 @@ export function resolveI18nIcuPlaceholders(job: CompilationJob) { * Visitor for i18n AST that resolves ICU params into the given map. */ class ResolveIcuPlaceholdersVisitor extends i18n.RecurseVisitor { - constructor(readonly params: Map) { + constructor(private readonly params: Map) { super(); } From 0864dbe571b4c4c7426d83cd3af48ca38bbf62d0 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 7 Nov 2023 17:27:12 -0800 Subject: [PATCH 40/43] refactor(compiler): Change how ICUs are ingested (#52698) The previous commit added support for interpolated text in ICUs, but it made the assumption that the interpolation would be a single variable read expression. To properly support all kinds of interpolation expressions, this commit refactors how ICUs are ingested to allow us to re-use the same logic we use for bound text outside of ICUs. To accomplish this, the `IcuOp` creation op has been removed in favor of a pair of ops: `IcuStartOp` and `IcuEndOp`, that mark the beginning and end of the ICU. Now, instead of inserting an `IcuUpdateOp` in the update IR, we call `ingestBoundText` and use the presence of the surrounding `IcuStartOp` and `IcuEndOp` to match the interpolation with the ICU. PR Close #52698 --- .../icu_logic/TEST_CASES.json | 3 +- .../src/template/pipeline/ir/src/enums.ts | 4 +- .../template/pipeline/ir/src/expression.ts | 4 +- .../template/pipeline/ir/src/ops/create.ts | 47 +++++++---- .../template/pipeline/ir/src/ops/update.ts | 33 +------- .../src/template/pipeline/src/emit.ts | 2 - .../src/template/pipeline/src/ingest.ts | 36 ++++++-- .../src/phases/create_i18n_contexts.ts | 2 +- .../src/phases/create_i18n_icu_expressions.ts | 82 ------------------- .../src/phases/extract_i18n_messages.ts | 29 ++++--- .../src/phases/i18n_text_extraction.ts | 21 ++++- .../phases/resolve_i18n_icu_placeholders.ts | 6 +- .../template/pipeline/src/phases/wrap_icus.ts | 14 +++- 13 files changed, 118 insertions(+), 165 deletions(-) delete mode 100644 packages/compiler/src/template/pipeline/src/phases/create_i18n_icu_expressions.ts diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json index d9e44581284c4..83fde4ec32e41 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json @@ -104,8 +104,7 @@ "verifyUniqueConsts" ] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should handle multiple icus in one block", diff --git a/packages/compiler/src/template/pipeline/ir/src/enums.ts b/packages/compiler/src/template/pipeline/ir/src/enums.ts index f1f8331834bf9..d5436126eacf9 100644 --- a/packages/compiler/src/template/pipeline/ir/src/enums.ts +++ b/packages/compiler/src/template/pipeline/ir/src/enums.ts @@ -223,12 +223,12 @@ export enum OpKind { /** * An instruction to create an ICU expression. */ - Icu, + IcuStart, /** * An instruction to update an ICU expression. */ - IcuUpdate, + IcuEnd, /** * An i18n context containing information needed to generate an i18n message. diff --git a/packages/compiler/src/template/pipeline/ir/src/expression.ts b/packages/compiler/src/template/pipeline/ir/src/expression.ts index 43268414b3c2b..50540946072ed 100644 --- a/packages/compiler/src/template/pipeline/ir/src/expression.ts +++ b/packages/compiler/src/template/pipeline/ir/src/expression.ts @@ -1014,8 +1014,8 @@ export function transformExpressionsInOp( case OpKind.I18nContext: case OpKind.I18nEnd: case OpKind.I18nStart: - case OpKind.Icu: - case OpKind.IcuUpdate: + case OpKind.IcuEnd: + case OpKind.IcuStart: case OpKind.Namespace: case OpKind.Pipe: case OpKind.Projection: diff --git a/packages/compiler/src/template/pipeline/ir/src/ops/create.ts b/packages/compiler/src/template/pipeline/ir/src/ops/create.ts index 98c887fa10c04..5e9661b6a2a1c 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/create.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/create.ts @@ -26,7 +26,7 @@ export type CreateOp = ListEndOp|StatementOp|ElementOp|Eleme ElementEndOp|ContainerOp|ContainerStartOp|ContainerEndOp|TemplateOp|EnableBindingsOp| DisableBindingsOp|TextOp|ListenerOp|PipeOp|VariableOp|NamespaceOp|ProjectionDefOp| ProjectionOp|ExtractedAttributeOp|DeferOp|DeferOnOp|RepeaterCreateOp|I18nMessageOp|I18nOp| - I18nStartOp|I18nEndOp|IcuOp|I18nContextOp; + I18nStartOp|I18nEndOp|IcuStartOp|IcuEndOp|I18nContextOp; /** * An operation representing the creation of an element or container. @@ -1001,10 +1001,10 @@ export function createI18nEndOp(xref: XrefId): I18nEndOp { } /** - * An op that represents an ICU expression. + * An op that represents the start of an ICU expression. */ -export interface IcuOp extends Op { - kind: OpKind.Icu; +export interface IcuStartOp extends Op { + kind: OpKind.IcuStart; /** * The ID of the ICU. @@ -1016,11 +1016,6 @@ export interface IcuOp extends Op { */ message: i18n.Message; - /** - * The ICU associated with this op. - */ - icu: i18n.Icu; - /** * Placeholder used to reference this ICU in other i18n messages. */ @@ -1035,16 +1030,15 @@ export interface IcuOp extends Op { } /** - * Creates an op to create an ICU expression. + * Creates an ICU start op. */ -export function createIcuOp( - xref: XrefId, message: i18n.Message, icu: i18n.Icu, messagePlaceholder: string, - sourceSpan: ParseSourceSpan): IcuOp { +export function createIcuStartOp( + xref: XrefId, message: i18n.Message, messagePlaceholder: string, + sourceSpan: ParseSourceSpan): IcuStartOp { return { - kind: OpKind.Icu, + kind: OpKind.IcuStart, xref, message, - icu, messagePlaceholder, context: null, sourceSpan, @@ -1052,6 +1046,29 @@ export function createIcuOp( }; } +/** + * An op that represents the end of an ICU expression. + */ +export interface IcuEndOp extends Op { + kind: OpKind.IcuEnd; + + /** + * The ID of the corresponding IcuStartOp. + */ + xref: XrefId; +} + +/** + * Creates an ICU end op. + */ +export function createIcuEndOp(xref: XrefId): IcuEndOp { + return { + kind: OpKind.IcuEnd, + xref, + ...NEW_OP, + }; +} + /** * An i18n context that is used to generate snippets of a full translated message. * A separate context is created in a few different scenarios: diff --git a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts index dd46b10bc2b0b..b06533c05894b 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts @@ -7,7 +7,6 @@ */ import {SecurityContext} from '../../../../../core'; -import * as i18n from '../../../../../i18n/i18n_ast'; import * as o from '../../../../../output/output_ast'; import {ParseSourceSpan} from '../../../../../parse_util'; import {BindingKind, I18nParamResolutionTime, OpKind} from '../enums'; @@ -24,7 +23,7 @@ import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared'; */ export type UpdateOp = ListEndOp|StatementOp|PropertyOp|AttributeOp|StylePropOp| ClassPropOp|StyleMapOp|ClassMapOp|InterpolateTextOp|AdvanceOp|VariableOp|BindingOp| - HostPropertyOp|ConditionalOp|I18nExpressionOp|I18nApplyOp|IcuUpdateOp|RepeaterOp|DeferWhenOp; + HostPropertyOp|ConditionalOp|I18nExpressionOp|I18nApplyOp|RepeaterOp|DeferWhenOp; /** * A logical operation to perform string interpolation on a text node. @@ -49,7 +48,7 @@ export interface InterpolateTextOp extends Op, ConsumesVarsTrait { /** * The i18n placeholders associated with this interpolation. */ - i18nPlaceholders: i18n.Placeholder[]; + i18nPlaceholders: string[]; sourceSpan: ParseSourceSpan; } @@ -58,7 +57,7 @@ export interface InterpolateTextOp extends Op, ConsumesVarsTrait { * Create an `InterpolationTextOp`. */ export function createInterpolateTextOp( - xref: XrefId, interpolation: Interpolation, i18nPlaceholders: i18n.Placeholder[], + xref: XrefId, interpolation: Interpolation, i18nPlaceholders: string[], sourceSpan: ParseSourceSpan): InterpolateTextOp { return { kind: OpKind.InterpolateText, @@ -697,29 +696,3 @@ export function createI18nApplyOp( ...NEW_OP, }; } - -/** - * An op that represents updating an ICU expression. - */ -export interface IcuUpdateOp extends Op { - kind: OpKind.IcuUpdate; - - /** - * The ID of the ICU being updated. - */ - xref: XrefId; - - sourceSpan: ParseSourceSpan; -} - -/** - * Creates an op to update an ICU expression. - */ -export function createIcuUpdateOp(xref: XrefId, sourceSpan: ParseSourceSpan): IcuUpdateOp { - return { - kind: OpKind.IcuUpdate, - xref, - sourceSpan, - ...NEW_OP, - }; -} diff --git a/packages/compiler/src/template/pipeline/src/emit.ts b/packages/compiler/src/template/pipeline/src/emit.ts index 6d95af2ea880e..77a29b6dfade2 100644 --- a/packages/compiler/src/template/pipeline/src/emit.ts +++ b/packages/compiler/src/template/pipeline/src/emit.ts @@ -24,7 +24,6 @@ import {generateConditionalExpressions} from './phases/conditionals'; import {collectElementConsts} from './phases/const_collection'; import {createDeferDepsFns} from './phases/create_defer_deps_fns'; import {createI18nContexts} from './phases/create_i18n_contexts'; -import {createI18nIcuExpressions} from './phases/create_i18n_icu_expressions'; import {configureDeferInstructions} from './phases/defer_configs'; import {resolveDeferTargetNames} from './phases/defer_resolve_targets'; import {collapseEmptyInstructions} from './phases/empty_elements'; @@ -104,7 +103,6 @@ const phases: Phase[] = [ {kind: Kind.Tmpl, fn: createPipes}, {kind: Kind.Tmpl, fn: configureDeferInstructions}, {kind: Kind.Tmpl, fn: extractI18nText}, - {kind: Kind.Tmpl, fn: createI18nIcuExpressions}, {kind: Kind.Tmpl, fn: applyI18nExpressions}, {kind: Kind.Tmpl, fn: createVariadicPipes}, {kind: Kind.Both, fn: generatePureLiteralStructures}, diff --git a/packages/compiler/src/template/pipeline/src/ingest.ts b/packages/compiler/src/template/pipeline/src/ingest.ts index 10344db9dd330..78caea7c08449 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -251,7 +251,8 @@ function ingestText(unit: ViewCompilationUnit, text: t.Text): void { /** * Ingest an interpolated text node from the AST into the given `ViewCompilation`. */ -function ingestBoundText(unit: ViewCompilationUnit, text: t.BoundText): void { +function ingestBoundText( + unit: ViewCompilationUnit, text: t.BoundText, i18nPlaceholders?: string[]): void { let value = text.value; if (value instanceof e.ASTWithSource) { value = value.ast; @@ -265,10 +266,17 @@ function ingestBoundText(unit: ViewCompilationUnit, text: t.BoundText): void { `Unhandled i18n metadata type for text interpolation: ${text.i18n?.constructor.name}`); } - const i18nPlaceholders = text.i18n instanceof i18n.Container ? - text.i18n.children.filter( - (node): node is i18n.Placeholder => node instanceof i18n.Placeholder) : - []; + if (i18nPlaceholders === undefined) { + i18nPlaceholders = text.i18n instanceof i18n.Container ? + text.i18n.children + .filter((node): node is i18n.Placeholder => node instanceof i18n.Placeholder) + .map(placeholder => placeholder.name) : + []; + } + if (i18nPlaceholders.length > 0 && i18nPlaceholders.length !== value.expressions.length) { + throw Error(`Unexpected number of i18n placeholders (${ + value.expressions.length}) for BoundText with ${value.expressions.length} expressions`); + } const textXref = unit.job.allocateXrefId(); unit.create.push(ir.createTextOp(textXref, '', text.sourceSpan)); @@ -482,9 +490,21 @@ function ingestDeferBlock(unit: ViewCompilationUnit, deferBlock: t.DeferredBlock function ingestIcu(unit: ViewCompilationUnit, icu: t.Icu) { if (icu.i18n instanceof i18n.Message && isSingleI18nIcu(icu.i18n)) { const xref = unit.job.allocateXrefId(); - unit.create.push(ir.createIcuOp( - xref, icu.i18n, icu.i18n.nodes[0], icuFromI18nMessage(icu.i18n).name, null!)); - unit.update.push(ir.createIcuUpdateOp(xref, null!)); + const icuNode = icu.i18n.nodes[0]; + unit.create.push(ir.createIcuStartOp(xref, icu.i18n, icuFromI18nMessage(icu.i18n).name, null!)); + const {expressionPlaceholder} = icuNode; + if (expressionPlaceholder === undefined || icu.vars[expressionPlaceholder] === undefined) { + throw Error('ICU should have a text binding'); + } + ingestBoundText(unit, icu.vars[expressionPlaceholder], [expressionPlaceholder]); + for (const [placeholder, text] of Object.entries(icu.placeholders)) { + if (text instanceof t.BoundText) { + ingestBoundText(unit, text, [placeholder]); + } else { + ingestText(unit, text); + } + } + unit.create.push(ir.createIcuEndOp(xref)); } else { throw Error(`Unhandled i18n metadata type for ICU: ${icu.i18n?.constructor.name}`); } diff --git a/packages/compiler/src/template/pipeline/src/phases/create_i18n_contexts.ts b/packages/compiler/src/template/pipeline/src/phases/create_i18n_contexts.ts index 15b6bcf130d6b..3a562bfffed81 100644 --- a/packages/compiler/src/template/pipeline/src/phases/create_i18n_contexts.ts +++ b/packages/compiler/src/template/pipeline/src/phases/create_i18n_contexts.ts @@ -35,7 +35,7 @@ export function createI18nContexts(job: CompilationJob) { case ir.OpKind.I18nEnd: currentI18nOp = null; break; - case ir.OpKind.Icu: + case ir.OpKind.IcuStart: // If an ICU represents a different message than its containing block, we give it its own // i18n context. if (currentI18nOp === null) { diff --git a/packages/compiler/src/template/pipeline/src/phases/create_i18n_icu_expressions.ts b/packages/compiler/src/template/pipeline/src/phases/create_i18n_icu_expressions.ts deleted file mode 100644 index b4a1fb2dc1cac..0000000000000 --- a/packages/compiler/src/template/pipeline/src/phases/create_i18n_icu_expressions.ts +++ /dev/null @@ -1,82 +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 * as i18n from '../../../../i18n/i18n_ast'; -import * as ir from '../../ir'; -import {CompilationJob} from '../compilation'; - -/** - * Replace the ICU update ops with i18n expression ops. - */ -export function createI18nIcuExpressions(job: CompilationJob) { - const icus = new Map(); - const i18nContexts = new Map(); - const i18nBlocks = new Map(); - - // Collect maps of ops that need to be referenced to create the I18nExpressionOps. - for (const unit of job.units) { - for (const op of unit.create) { - switch (op.kind) { - case ir.OpKind.Icu: - icus.set(op.xref, op); - break; - case ir.OpKind.I18nContext: - i18nContexts.set(op.xref, op); - break; - case ir.OpKind.I18nStart: - i18nBlocks.set(op.xref, op); - break; - } - } - - // Replace each IcuUpdateOp with an I18nExpressionOp. - for (const op of unit.update) { - switch (op.kind) { - case ir.OpKind.IcuUpdate: - const icuOp = icus.get(op.xref); - if (icuOp?.icu.expressionPlaceholder === undefined) { - throw Error('ICU should have an i18n placeholder'); - } - if (icuOp.context === null) { - throw Error('ICU should have its i18n context set'); - } - const i18nContext = i18nContexts.get(icuOp.context)!; - const i18nBlock = i18nBlocks.get(i18nContext.i18nBlock)!; - const expressionOp = ir.createI18nExpressionOp( - i18nContext.xref, i18nBlock.xref, i18nBlock.handle, - new ir.LexicalReadExpr(icuOp.icu.expression), icuOp.icu.expressionPlaceholder, - // ICU-based i18n Expressions are resolved during post-processing. - ir.I18nParamResolutionTime.Postproccessing, null!); - ir.OpList.replace(op, expressionOp); - icuOp.icu.visit(new AddIcuExpressionVisitor( - i18nContext.xref, i18nBlock.xref, i18nBlock.handle, expressionOp)); - break; - } - } - } -} - -/** - * Visitor for i18n AST that adds i18nExpression ops for expressions nested in an ICU. - */ -class AddIcuExpressionVisitor extends i18n.RecurseVisitor { - constructor( - private contextId: ir.XrefId, private i18nBlockId: ir.XrefId, - private i18nBlockHandle: ir.SlotHandle, private insertAfterOp: ir.UpdateOp) { - super(); - } - - override visitPlaceholder(placeholder: i18n.Placeholder) { - const expressionOp = ir.createI18nExpressionOp( - this.contextId, this.i18nBlockId, this.i18nBlockHandle, - new ir.LexicalReadExpr(placeholder.value), placeholder.name, - ir.I18nParamResolutionTime.Postproccessing, null!); - ir.OpList.insertAfter(expressionOp, this.insertAfterOp); - super.visitPlaceholder(placeholder); - } -} diff --git a/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts b/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts index a01dbec48ec76..13dce2a490590 100644 --- a/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts +++ b/packages/compiler/src/template/pipeline/src/phases/extract_i18n_messages.ts @@ -91,18 +91,23 @@ export function extractI18nMessages(job: CompilationJob): void { // Extract messages from ICUs with their own sub-context. for (const unit of job.units) { for (const op of unit.create) { - if (op.kind === ir.OpKind.Icu) { - if (!op.context) { - throw Error('ICU op should have its context set.'); - } - if (!i18nBlockContexts.has(op.context)) { - const i18nContext = i18nContexts.get(op.context)!; - const subMessage = createI18nMessage(job, i18nContext, op.messagePlaceholder); - unit.create.push(subMessage); - const parentMessage = i18nBlockMessages.get(i18nContext.i18nBlock); - parentMessage?.subMessages.push(subMessage.xref); - } - ir.OpList.remove(op); + switch (op.kind) { + case ir.OpKind.IcuStart: + if (!op.context) { + throw Error('ICU op should have its context set.'); + } + if (!i18nBlockContexts.has(op.context)) { + const i18nContext = i18nContexts.get(op.context)!; + const subMessage = createI18nMessage(job, i18nContext, op.messagePlaceholder); + unit.create.push(subMessage); + const parentMessage = i18nBlockMessages.get(i18nContext.i18nBlock); + parentMessage?.subMessages.push(subMessage.xref); + } + ir.OpList.remove(op); + break; + case ir.OpKind.IcuEnd: + ir.OpList.remove(op); + break; } } } diff --git a/packages/compiler/src/template/pipeline/src/phases/i18n_text_extraction.ts b/packages/compiler/src/template/pipeline/src/phases/i18n_text_extraction.ts index 439b8df35b308..207bafc75ba81 100644 --- a/packages/compiler/src/template/pipeline/src/phases/i18n_text_extraction.ts +++ b/packages/compiler/src/template/pipeline/src/phases/i18n_text_extraction.ts @@ -17,7 +17,9 @@ export function extractI18nText(job: CompilationJob): void { // Remove all text nodes within i18n blocks, their content is already captured in the i18n // message. let currentI18n: ir.I18nStartOp|null = null; + let currentIcu: ir.IcuStartOp|null = null; const textNodeI18nBlocks = new Map(); + const textNodeIcus = new Map(); for (const op of unit.create) { switch (op.kind) { case ir.OpKind.I18nStart: @@ -29,9 +31,19 @@ export function extractI18nText(job: CompilationJob): void { case ir.OpKind.I18nEnd: currentI18n = null; break; + case ir.OpKind.IcuStart: + if (op.context === null) { + throw Error('Icu op should have its context set.'); + } + currentIcu = op; + break; + case ir.OpKind.IcuEnd: + currentIcu = null; + break; case ir.OpKind.Text: if (currentI18n !== null) { textNodeI18nBlocks.set(op.xref, currentI18n); + textNodeIcus.set(op.xref, currentIcu); ir.OpList.remove(op); } break; @@ -48,15 +60,18 @@ export function extractI18nText(job: CompilationJob): void { } const i18nOp = textNodeI18nBlocks.get(op.target)!; + const icuOp = textNodeIcus.get(op.target); + const contextId = icuOp ? icuOp.context : i18nOp.context; + const resolutionTime = icuOp ? ir.I18nParamResolutionTime.Postproccessing : + ir.I18nParamResolutionTime.Creation; const ops: ir.UpdateOp[] = []; for (let i = 0; i < op.interpolation.expressions.length; i++) { const expr = op.interpolation.expressions[i]; - const placeholder = op.i18nPlaceholders[i]; // For now, this i18nExpression depends on the slot context of the enclosing i18n block. // Later, we will modify this, and advance to a different point. ops.push(ir.createI18nExpressionOp( - i18nOp.context!, i18nOp.xref, i18nOp.handle, expr, placeholder.name, - ir.I18nParamResolutionTime.Creation, expr.sourceSpan ?? op.sourceSpan)); + contextId!, i18nOp.xref, i18nOp.handle, expr, op.i18nPlaceholders[i], + resolutionTime, expr.sourceSpan ?? op.sourceSpan)); } ir.OpList.replaceWithMany(op as ir.UpdateOp, ops); break; diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts index af4930bb9c1d4..595660908f3e9 100644 --- a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts @@ -28,12 +28,14 @@ export function resolveI18nIcuPlaceholders(job: CompilationJob) { for (const unit of job.units) { for (const op of unit.create) { switch (op.kind) { - case ir.OpKind.Icu: + case ir.OpKind.IcuStart: if (op.context === null) { throw Error('Icu should have its i18n context set.'); } const i18nContext = contextOps.get(op.context)!; - op.icu.visit(new ResolveIcuPlaceholdersVisitor(i18nContext.postprocessingParams)); + for (const node of op.message.nodes) { + node.visit(new ResolveIcuPlaceholdersVisitor(i18nContext.postprocessingParams)); + } break; } } diff --git a/packages/compiler/src/template/pipeline/src/phases/wrap_icus.ts b/packages/compiler/src/template/pipeline/src/phases/wrap_icus.ts index ff7bc08eef106..ca119ba157100 100644 --- a/packages/compiler/src/template/pipeline/src/phases/wrap_icus.ts +++ b/packages/compiler/src/template/pipeline/src/phases/wrap_icus.ts @@ -15,6 +15,7 @@ import {CompilationJob} from '../compilation'; export function wrapI18nIcus(job: CompilationJob): void { for (const unit of job.units) { let currentI18nOp: ir.I18nStartOp|null = null; + let addedI18nId: ir.XrefId|null = null; for (const op of unit.create) { switch (op.kind) { case ir.OpKind.I18nStart: @@ -23,11 +24,16 @@ export function wrapI18nIcus(job: CompilationJob): void { case ir.OpKind.I18nEnd: currentI18nOp = null; break; - case ir.OpKind.Icu: + case ir.OpKind.IcuStart: if (currentI18nOp === null) { - const id = job.allocateXrefId(); - ir.OpList.insertBefore(ir.createI18nStartOp(id, op.message), op); - ir.OpList.insertAfter(ir.createI18nEndOp(id), op); + addedI18nId = job.allocateXrefId(); + ir.OpList.insertBefore(ir.createI18nStartOp(addedI18nId, op.message), op); + } + break; + case ir.OpKind.IcuEnd: + if (addedI18nId !== null) { + ir.OpList.insertAfter(ir.createI18nEndOp(addedI18nId), op); + addedI18nId = null; } break; } From 2c3b6c6e2752e0ab5aeef80e12ac9bd688076576 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 7 Nov 2023 21:46:56 -0800 Subject: [PATCH 41/43] refactor(compiler): Fix some issues with i18n expressions in ICUs (#52698) We were previously counting the i18n expression index and deciding when to apply i18n expressions based on the i18n context. These should be done based on the i18n block instead. PR Close #52698 --- .../icu_logic/TEST_CASES.json | 3 +-- .../src/phases/apply_i18n_expressions.ts | 19 +++++++++++++++---- .../resolve_i18n_expression_placeholders.ts | 6 +++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json index 83fde4ec32e41..c4e48e6e789f5 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json @@ -118,8 +118,7 @@ "verifyUniqueConsts" ] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should handle multiple icus that share same placeholder", diff --git a/packages/compiler/src/template/pipeline/src/phases/apply_i18n_expressions.ts b/packages/compiler/src/template/pipeline/src/phases/apply_i18n_expressions.ts index 23e4700080ca5..6528585129a27 100644 --- a/packages/compiler/src/template/pipeline/src/phases/apply_i18n_expressions.ts +++ b/packages/compiler/src/template/pipeline/src/phases/apply_i18n_expressions.ts @@ -13,10 +13,19 @@ import {CompilationJob} from '../compilation'; * Adds apply operations after i18n expressions. */ export function applyI18nExpressions(job: CompilationJob): void { + const i18nContexts = new Map(); + for (const unit of job.units) { + for (const op of unit.create) { + if (op.kind === ir.OpKind.I18nContext) { + i18nContexts.set(op.xref, op); + } + } + } + for (const unit of job.units) { for (const op of unit.update) { // Only add apply after expressions that are not followed by more expressions. - if (op.kind === ir.OpKind.I18nExpression && needsApplication(op)) { + if (op.kind === ir.OpKind.I18nExpression && needsApplication(i18nContexts, op)) { // TODO: what should be the source span for the apply op? ir.OpList.insertAfter(ir.createI18nApplyOp(op.target, op.handle, null!), op); } @@ -27,13 +36,15 @@ export function applyI18nExpressions(job: CompilationJob): void { /** * Checks whether the given expression op needs to be followed with an apply op. */ -function needsApplication(op: ir.I18nExpressionOp) { +function needsApplication(i18nContexts: Map, op: ir.I18nExpressionOp) { // If the next op is not another expression, we need to apply. if (op.next?.kind !== ir.OpKind.I18nExpression) { return true; } - // If the next op is an expression targeting a different i18n context, we need to apply. - if (op.next.context !== op.context) { + // If the next op is an expression targeting a different i18n block, we need to apply. + const context = i18nContexts.get(op.context)!; + const nextContext = i18nContexts.get(op.next.context)!; + if (context.i18nBlock !== nextContext.i18nBlock) { return true; } return false; diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.ts index f92673a283c91..b6bc11512753a 100644 --- a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.ts +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.ts @@ -29,14 +29,14 @@ export function resolveI18nExpressionPlaceholders(job: ComponentCompilationJob) } } - // Keep track of the next available expression index per i18n context. + // Keep track of the next available expression index per i18n block. const expressionIndices = new Map(); for (const unit of job.units) { for (const op of unit.update) { if (op.kind === ir.OpKind.I18nExpression) { - const index = expressionIndices.get(op.context) || 0; const i18nContext = i18nContexts.get(op.context)!; + const index = expressionIndices.get(i18nContext.i18nBlock) || 0; const subTemplateIndex = subTemplateIndicies.get(i18nContext.i18nBlock)!; // Add the expression index in the appropriate params map. const params = op.resolutionTime === ir.I18nParamResolutionTime.Creation ? @@ -50,7 +50,7 @@ export function resolveI18nExpressionPlaceholders(job: ComponentCompilationJob) }); params.set(op.i18nPlaceholder, values); - expressionIndices.set(op.context, index + 1); + expressionIndices.set(i18nContext.i18nBlock, index + 1); } } } From 70fe5e60b36a10be7684c27bff138ec1801e7943 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 8 Nov 2023 10:50:12 -0800 Subject: [PATCH 42/43] refactor(compiler): Handle trailing spaces in ICU placeholders (#52698) In some cases ICU expression placeholders may have trailing spaces that need to be trimmed when matching the placeholder to its corresponding text binding. PR Close #52698 --- .../r3_view_compiler_i18n/icu_logic/TEST_CASES.json | 6 ++---- packages/compiler/src/template/pipeline/src/ingest.ts | 2 +- .../template/pipeline/src/phases/i18n_const_collection.ts | 6 ++++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json index c4e48e6e789f5..595a262081e57 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/TEST_CASES.json @@ -222,8 +222,7 @@ "verifyUniqueConsts" ] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should produce proper messages when `select` or `plural` keywords have spaces after them", @@ -237,8 +236,7 @@ "verifyUniqueConsts" ] } - ], - "skipForTemplatePipeline": true + ] } ] } diff --git a/packages/compiler/src/template/pipeline/src/ingest.ts b/packages/compiler/src/template/pipeline/src/ingest.ts index 78caea7c08449..fd5a2bfd5ddf2 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -492,7 +492,7 @@ function ingestIcu(unit: ViewCompilationUnit, icu: t.Icu) { const xref = unit.job.allocateXrefId(); const icuNode = icu.i18n.nodes[0]; unit.create.push(ir.createIcuStartOp(xref, icu.i18n, icuFromI18nMessage(icu.i18n).name, null!)); - const {expressionPlaceholder} = icuNode; + const expressionPlaceholder = icuNode.expressionPlaceholder?.trimEnd(); if (expressionPlaceholder === undefined || icu.vars[expressionPlaceholder] === undefined) { throw Error('ICU should have a text binding'); } diff --git a/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts b/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts index 43606ed3fa389..a6677033907e3 100644 --- a/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts +++ b/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts @@ -204,12 +204,14 @@ function i18nGenerateClosureVar( * Asserts that all of the message's placeholders have values. */ function assertAllParamsResolved(op: ir.I18nMessageOp): asserts op is ir.I18nMessageOp { - for (const placeholder in op.message.placeholders) { + for (let placeholder in op.message.placeholders) { + placeholder = placeholder.trimEnd(); if (!op.params.has(placeholder) && !op.postprocessingParams.has(placeholder)) { throw Error(`Failed to resolve i18n placeholder: ${placeholder}`); } } - for (const placeholder in op.message.placeholderToMessage) { + for (let placeholder in op.message.placeholderToMessage) { + placeholder = placeholder.trimEnd(); if (!op.params.has(placeholder) && !op.postprocessingParams.has(placeholder)) { throw Error(`Failed to resolve i18n message placeholder: ${placeholder}`); } From 3cf18bb6f2798020cbe8633fae79f920cf98816b Mon Sep 17 00:00:00 2001 From: AleksanderBodurri Date: Fri, 10 Nov 2023 12:23:19 -0500 Subject: [PATCH 43/43] fix(devtools): check for all new DI debug APIs before trying to determine resolution path providers (#52791) Previously, some versions of Angular 16.1.x that had 3/4 of the new DI debug APIs would enter a code path that required them to have access to the 4th. Now DevTools checks for the existence of all 4 explicitly before going down this code path. PR Close #52791 --- .../src/lib/component-tree.ts | 93 ++++++++++--------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/devtools/projects/ng-devtools-backend/src/lib/component-tree.ts b/devtools/projects/ng-devtools-backend/src/lib/component-tree.ts index 53610e058b6f0..7dcd9167dbd4c 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/component-tree.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/component-tree.ts @@ -88,54 +88,57 @@ export function getDirectivesFromElement(element: HTMLElement): export const getLatestComponentState = (query: ComponentExplorerViewQuery, directiveForest?: ComponentTreeNode[]): - {directiveProperties: DirectivesProperties;}|undefined => { - // if a directive forest is passed in we don't have to build the forest again. - directiveForest = directiveForest ?? buildDirectiveForest(); - - const node = queryDirectiveForest(query.selectedElement, directiveForest); - if (!node) { - return; - } - - const directiveProperties: DirectivesProperties = {}; - - const injector = ngDebug().getInjector(node.nativeElement); - - const resolutionPathWithProviders = getInjectorResolutionPath(injector).map( - injector => ({injector, providers: getInjectorProviders(injector)})); - - - const populateResultSet = (dir: DirectiveInstanceType|ComponentInstanceType) => { - const {instance, name} = dir; - const metadata = getDirectiveMetadata(instance); - metadata.dependencies = getDependenciesForDirective( - injector, resolutionPathWithProviders, instance.constructor); - - if (query.propertyQuery.type === PropertyQueryTypes.All) { - directiveProperties[dir.name] = { - props: serializeDirectiveState(instance), - metadata, - }; - } - - if (query.propertyQuery.type === PropertyQueryTypes.Specified) { - directiveProperties[name] = { - props: deeplySerializeSelectedProperties( - instance, query.propertyQuery.properties[name] || []), - metadata, - }; - } - }; + {directiveProperties: DirectivesProperties;}| + undefined => { + // if a directive forest is passed in we don't have to build the forest again. + directiveForest = directiveForest ?? buildDirectiveForest(); + + const node = queryDirectiveForest(query.selectedElement, directiveForest); + if (!node) { + return; + } + + const directiveProperties: DirectivesProperties = {}; - node.directives.forEach((dir) => populateResultSet(dir)); - if (node.component) { - populateResultSet(node.component); - } + const injector = ngDebug().getInjector(node.nativeElement); + + let resolutionPathWithProviders: {injector: Injector; providers: ProviderRecord[];}[] = []; + if (hasDiDebugAPIs()) { + resolutionPathWithProviders = getInjectorResolutionPath(injector).map( + injector => ({injector, providers: getInjectorProviders(injector)})); + } + + const populateResultSet = (dir: DirectiveInstanceType|ComponentInstanceType) => { + const {instance, name} = dir; + const metadata = getDirectiveMetadata(instance); + metadata.dependencies = getDependenciesForDirective( + injector, resolutionPathWithProviders, instance.constructor); + + if (query.propertyQuery.type === PropertyQueryTypes.All) { + directiveProperties[dir.name] = { + props: serializeDirectiveState(instance), + metadata, + }; + } - return { - directiveProperties, + if (query.propertyQuery.type === PropertyQueryTypes.Specified) { + directiveProperties[name] = { + props: deeplySerializeSelectedProperties( + instance, query.propertyQuery.properties[name] || []), + metadata, }; - }; + } + }; + + node.directives.forEach((dir) => populateResultSet(dir)); + if (node.component) { + populateResultSet(node.component); + } + + return { + directiveProperties, + }; + }; export function serializeElementInjectorWithId(injector: Injector): SerializedInjector|null { let id: string;