@angular/core 21.0.0-next.2 → 21.0.0-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/api.d.d.ts +1 -1
  2. package/chrome_dev_tools_performance.d.d.ts +1 -1
  3. package/discovery.d.d.ts +44 -18
  4. package/effect.d.d.ts +1 -1
  5. package/event_dispatcher.d.d.ts +1 -1
  6. package/fesm2022/attribute.mjs +1 -1
  7. package/fesm2022/attribute.mjs.map +1 -1
  8. package/fesm2022/core.mjs +253 -87
  9. package/fesm2022/core.mjs.map +1 -1
  10. package/fesm2022/debug_node.mjs +516 -690
  11. package/fesm2022/debug_node.mjs.map +1 -1
  12. package/fesm2022/effect.mjs +1 -1
  13. package/fesm2022/effect.mjs.map +1 -1
  14. package/fesm2022/not_found.mjs +1 -1
  15. package/fesm2022/not_found.mjs.map +1 -1
  16. package/fesm2022/primitives/di.mjs +1 -1
  17. package/fesm2022/primitives/di.mjs.map +1 -1
  18. package/fesm2022/primitives/event-dispatch.mjs +1 -1
  19. package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
  20. package/fesm2022/primitives/signals.mjs +2 -2
  21. package/fesm2022/primitives/signals.mjs.map +1 -1
  22. package/fesm2022/resource.mjs +1 -1
  23. package/fesm2022/resource.mjs.map +1 -1
  24. package/fesm2022/root_effect_scheduler.mjs +55 -39
  25. package/fesm2022/root_effect_scheduler.mjs.map +1 -1
  26. package/fesm2022/rxjs-interop.mjs +3 -1
  27. package/fesm2022/rxjs-interop.mjs.map +1 -1
  28. package/fesm2022/signal.mjs +32 -11
  29. package/fesm2022/signal.mjs.map +1 -1
  30. package/fesm2022/testing.mjs +4 -4
  31. package/fesm2022/testing.mjs.map +1 -1
  32. package/fesm2022/weak_ref.mjs +1 -1
  33. package/fesm2022/weak_ref.mjs.map +1 -1
  34. package/graph.d.d.ts +24 -4
  35. package/index.d.ts +16 -135
  36. package/package.json +2 -2
  37. package/primitives/di/index.d.ts +1 -1
  38. package/primitives/event-dispatch/index.d.ts +1 -1
  39. package/primitives/signals/index.d.ts +2 -2
  40. package/rxjs-interop/index.d.ts +3 -1
  41. package/schematics/bundles/add-bootstrap-context-to-server-main.cjs +135 -0
  42. package/schematics/bundles/application-config-core.cjs +6 -6
  43. package/schematics/bundles/{apply_import_manager-B3czqUhF.cjs → apply_import_manager-DroqamMP.cjs} +3 -3
  44. package/schematics/bundles/bootstrap-options-migration.cjs +715 -0
  45. package/schematics/bundles/cleanup-unused-imports.cjs +5 -5
  46. package/schematics/bundles/{compiler_host-B9qvCnmC.cjs → compiler_host-aKaS4KRz.cjs} +2 -2
  47. package/schematics/bundles/control-flow-migration.cjs +4 -4
  48. package/schematics/bundles/{imports-26VeX8i-.cjs → imports-DwPXlGFl.cjs} +27 -1
  49. package/schematics/bundles/{index-B6-f9bil.cjs → index-BI97t1U8.cjs} +130 -34
  50. package/schematics/bundles/{index-DN8W1c8n.cjs → index-DaB-z4lP.cjs} +4 -4
  51. package/schematics/bundles/inject-migration.cjs +5 -5
  52. package/schematics/bundles/leading_space-D9nQ8UQC.cjs +1 -1
  53. package/schematics/bundles/{migrate_ts_type_references-DawXRJzI.cjs → migrate_ts_type_references-DPuwhGod.cjs} +5 -5
  54. package/schematics/bundles/{ng_component_template-DUAg-x1h.cjs → ng_component_template-CytqBs-q.cjs} +3 -3
  55. package/schematics/bundles/{ng_decorators-CtYwz9Lw.cjs → ng_decorators-BI0uV7KI.cjs} +2 -2
  56. package/schematics/bundles/ngclass-to-class-migration.cjs +9 -9
  57. package/schematics/bundles/ngstyle-to-style-migration.cjs +490 -0
  58. package/schematics/bundles/nodes-B16H9JUd.cjs +1 -1
  59. package/schematics/bundles/output-migration.cjs +6 -6
  60. package/schematics/bundles/parse_html-CeQjkdOK.cjs +132 -0
  61. package/schematics/bundles/{project_paths-D64fJzoa.cjs → project_paths-Cz4x-QiT.cjs} +3 -3
  62. package/schematics/bundles/{project_tsconfig_paths-DZ17BWwk.cjs → project_tsconfig_paths-Clg7WX1w.cjs} +170 -198
  63. package/schematics/bundles/property_name-BBwFuqMe.cjs +1 -1
  64. package/schematics/bundles/route-lazy-loading.cjs +48 -4
  65. package/schematics/bundles/router-current-navigation.cjs +6 -6
  66. package/schematics/bundles/router-last-successful-navigation.cjs +6 -6
  67. package/schematics/bundles/self-closing-tags-migration.cjs +8 -8
  68. package/schematics/bundles/signal-input-migration.cjs +17 -9
  69. package/schematics/bundles/signal-queries-migration.cjs +7 -7
  70. package/schematics/bundles/signals.cjs +7 -7
  71. package/schematics/bundles/standalone-migration.cjs +7 -7
  72. package/schematics/bundles/{symbol-VPWguRxr.cjs → symbol-BObKoqes.cjs} +3 -2
  73. package/schematics/collection.json +6 -0
  74. package/schematics/migrations/ngclass-to-class-migration/schema.json +1 -1
  75. package/schematics/migrations/ngstyle-to-style-migration/schema.json +20 -0
  76. package/schematics/migrations.json +10 -0
  77. package/testing/index.d.ts +1 -1
  78. package/weak_ref.d.d.ts +1 -1
  79. package/schematics/bundles/parse_html-C8TYlOyu.cjs +0 -41
@@ -0,0 +1,715 @@
1
+ 'use strict';
2
+ /**
3
+ * @license Angular v21.0.0-next.4
4
+ * (c) 2010-2025 Google LLC. https://angular.io/
5
+ * License: MIT
6
+ */
7
+ 'use strict';
8
+
9
+ require('@angular-devkit/core');
10
+ require('node:path/posix');
11
+ var project_paths = require('./project_paths-Cz4x-QiT.cjs');
12
+ require('os');
13
+ var ts = require('typescript');
14
+ var project_tsconfig_paths = require('./project_tsconfig_paths-Clg7WX1w.cjs');
15
+ var index = require('./index-BI97t1U8.cjs');
16
+ require('path');
17
+ require('node:path');
18
+ var apply_import_manager = require('./apply_import_manager-DroqamMP.cjs');
19
+ var property_name = require('./property_name-BBwFuqMe.cjs');
20
+ var imports = require('./imports-DwPXlGFl.cjs');
21
+ var symbol = require('./symbol-BObKoqes.cjs');
22
+ require('@angular-devkit/schematics');
23
+ require('fs');
24
+ require('module');
25
+ require('url');
26
+
27
+ const CORE_PACKAGE = '@angular/core';
28
+ const PROVIDE_ZONE_CHANGE_DETECTION = 'provideZoneChangeDetection';
29
+ const ZONE_CD_PROVIDER = `${PROVIDE_ZONE_CHANGE_DETECTION}()`;
30
+ const NoopNgZone = `
31
+ // TODO ADD WARNING MESSAGE
32
+ export class NoopNgZone implements NgZone {
33
+ readonly hasPendingMicrotasks = false;
34
+ readonly hasPendingMacrotasks = false;
35
+ readonly isStable = true;
36
+ readonly onUnstable = new EventEmitter<any>();
37
+ readonly onMicrotaskEmpty = new EventEmitter<any>();
38
+ readonly onStable = new EventEmitter<any>();
39
+ readonly onError = new EventEmitter<any>();
40
+
41
+ run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any): T {
42
+ return fn.apply(applyThis, applyArgs);
43
+ }
44
+
45
+ runGuarded<T>(fn: (...args: any[]) => any, applyThis?: any, applyArgs?: any): T {
46
+ return fn.apply(applyThis, applyArgs);
47
+ }
48
+
49
+ runOutsideAngular<T>(fn: (...args: any[]) => T): T {
50
+ return fn();
51
+ }
52
+
53
+ runTask<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any, name?: string): T {
54
+ return fn.apply(applyThis, applyArgs);
55
+ }
56
+ }
57
+ `;
58
+ class BootstrapOptionsMigration extends project_paths.TsurgeFunnelMigration {
59
+ async analyze(info) {
60
+ let replacements = [];
61
+ const importManager = new project_tsconfig_paths.ImportManager();
62
+ for (const sourceFile of info.sourceFiles) {
63
+ // We need to migration either
64
+ // * `bootstrapApplication(App)
65
+ // * `platformBrowser().bootstrapModule(AppModule)`
66
+ // * `platformBrowserDynamic().bootstrapModule(AppModule)`
67
+ // * `TestBed.initTestEnvironment([AppModule], platformBrowserTesting())`
68
+ const specifiers = getSpecifiers(sourceFile);
69
+ // If none of the imports related to bootstraping are present, we can skip the file.
70
+ if (specifiers === null)
71
+ continue;
72
+ const { bootstrapAppSpecifier, platformBrowserDynamicSpecifier, platformBrowserSpecifier, testBedSpecifier, createApplicationSpecifier, } = specifiers;
73
+ const typeChecker = info.program.getTypeChecker();
74
+ const isCreateApplicationNode = (node) => {
75
+ return (ts.isCallExpression(node) &&
76
+ createApplicationSpecifier !== null &&
77
+ symbol.isReferenceToImport(typeChecker, node.expression, createApplicationSpecifier));
78
+ };
79
+ const isBootstrapAppNode = (node) => {
80
+ return (ts.isCallExpression(node) &&
81
+ bootstrapAppSpecifier !== null &&
82
+ symbol.isReferenceToImport(typeChecker, node.expression, bootstrapAppSpecifier));
83
+ };
84
+ const isBootstrapModuleNode = (node) => {
85
+ return (ts.isCallExpression(node) &&
86
+ ts.isPropertyAccessExpression(node.expression) &&
87
+ node.expression.name.text === 'bootstrapModule' &&
88
+ ts.isCallExpression(node.expression.expression) &&
89
+ (symbol.isReferenceToImport(typeChecker, node.expression.expression.expression, platformBrowserSpecifier) ||
90
+ symbol.isReferenceToImport(typeChecker, node.expression.expression.expression, platformBrowserDynamicSpecifier)));
91
+ };
92
+ const isTestBedInitEnvironmentNode = (node) => {
93
+ return (ts.isCallExpression(node) &&
94
+ ts.isPropertyAccessExpression(node.expression) &&
95
+ node.expression.name.text === 'initTestEnvironment' &&
96
+ symbol.isReferenceToImport(typeChecker, node.expression.expression, testBedSpecifier));
97
+ };
98
+ const NgModuleWithBootstrapPropMetadataLiteral = (node) => {
99
+ const moduleClass = node;
100
+ const ngModule = findNgModule(moduleClass, reflector);
101
+ if (!ngModule)
102
+ return;
103
+ const ngModuleMetadata = evaluator.evaluate(ngModule);
104
+ if (!(ngModuleMetadata instanceof Map)) {
105
+ return;
106
+ }
107
+ if (ngModuleMetadata.has('bootstrap') &&
108
+ Array.isArray(ngModuleMetadata.get('bootstrap')) &&
109
+ ngModuleMetadata.get('bootstrap').length > 0) {
110
+ return ngModule;
111
+ }
112
+ return;
113
+ };
114
+ const reflector = new project_tsconfig_paths.TypeScriptReflectionHost(typeChecker);
115
+ const evaluator = new index.PartialEvaluator(reflector, typeChecker, null);
116
+ const walk = (node) => {
117
+ if (isBootstrapAppNode(node)) {
118
+ this.analyzeBootstrapApplication(node, sourceFile, info, typeChecker, importManager, replacements);
119
+ }
120
+ else if (isCreateApplicationNode(node)) {
121
+ this.analyzeCreateApplication(node, sourceFile, info, typeChecker, importManager, replacements);
122
+ }
123
+ else if (isBootstrapModuleNode(node)) {
124
+ this.analyzeBootstrapModule(node, sourceFile, reflector, evaluator, info, typeChecker, importManager, replacements);
125
+ }
126
+ else if (isTestBedInitEnvironmentNode(node)) {
127
+ this.analyzeTestBedInitEnvironment(node, sourceFile, info, typeChecker, importManager, replacements);
128
+ }
129
+ else if (ts.isClassDeclaration(node)) {
130
+ // This case is specific for handling G3 where the NgModule metadata might not be inspectable when it's in a different build target.
131
+ const ngModuleLiteral = NgModuleWithBootstrapPropMetadataLiteral(node);
132
+ if (ngModuleLiteral) {
133
+ this.analyzeModuleWithBootstrapProp(ngModuleLiteral, sourceFile, info, typeChecker, importManager, replacements);
134
+ }
135
+ }
136
+ node.forEachChild(walk);
137
+ };
138
+ sourceFile.forEachChild(walk);
139
+ }
140
+ // The combine method might not run when there is a single target.
141
+ // So we deduplicate here
142
+ replacements = deduplicateReplacements(replacements);
143
+ apply_import_manager.applyImportManagerChanges(importManager, replacements, info.sourceFiles, info);
144
+ return project_paths.confirmAsSerializable({ replacements });
145
+ }
146
+ async combine(unitA, unitB) {
147
+ const combined = [...unitA.replacements, ...unitB.replacements];
148
+ return project_paths.confirmAsSerializable({ replacements: deduplicateReplacements(combined) });
149
+ }
150
+ async globalMeta(data) {
151
+ return project_paths.confirmAsSerializable(data);
152
+ }
153
+ async stats(data) {
154
+ return project_paths.confirmAsSerializable({});
155
+ }
156
+ async migrate(data) {
157
+ return { replacements: data.replacements };
158
+ }
159
+ analyzeBootstrapApplication(node, sourceFile, info, typeChecker, importManager, replacements) {
160
+ const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(node, typeChecker);
161
+ if (hasExistingChangeDetectionProvider)
162
+ return;
163
+ const providerFn = 'provideZoneChangeDetection()';
164
+ const optionsNode = node.arguments[1];
165
+ const currentProjectFile = project_paths.projectFile(sourceFile, info);
166
+ if (optionsNode) {
167
+ let optionProjectFile = currentProjectFile;
168
+ let optionLiteral;
169
+ if (ts.isObjectLiteralExpression(optionsNode)) {
170
+ optionLiteral = optionsNode;
171
+ addProvidersToBootstrapOption(optionProjectFile, optionLiteral, providerFn, replacements);
172
+ }
173
+ else if (ts.isIdentifier(optionsNode)) {
174
+ // This case handled both `bootstrapApplication(App, appConfig)` and the server () => bootstrapApplication(App, appConfig)
175
+ // where appConfig is the result of a `mergeApplicationConfig` call.
176
+ // This is tricky case to handle, in G3 we're might not be able to resolve the identifier's value
177
+ // Our best alternative is to assume there is not CD providers set and add the ZoneChangeDetection provider
178
+ // In the cases where it is, we'll just override the zone provider we just set by re-used inthe appConfig providers
179
+ // TODO: Should we insert a TODO to clean this up ?
180
+ const text = `{...${optionsNode.getText()}, providers: [${providerFn}, ...${optionsNode.getText()}.providers]}`;
181
+ replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({
182
+ position: optionsNode.getStart(),
183
+ end: optionsNode.getEnd(),
184
+ toInsert: text,
185
+ })));
186
+ }
187
+ else {
188
+ throw new Error('unsupported optionsNode: ' + optionsNode.getText());
189
+ }
190
+ }
191
+ else {
192
+ // No options object, add it.
193
+ const text = `, {providers: [${providerFn}]}`;
194
+ const component = node.arguments[0];
195
+ replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({ position: component.getEnd(), end: component.getEnd(), toInsert: text })));
196
+ }
197
+ importManager.addImport({
198
+ exportModuleSpecifier: CORE_PACKAGE,
199
+ exportSymbolName: 'provideZoneChangeDetection',
200
+ requestedFile: sourceFile,
201
+ });
202
+ }
203
+ analyzeCreateApplication(node, sourceFile, info, typeChecker, importManager, replacements) {
204
+ const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(node, typeChecker);
205
+ if (hasExistingChangeDetectionProvider)
206
+ return;
207
+ const providerFn = 'provideZoneChangeDetection()';
208
+ const optionsNode = node.arguments[0];
209
+ const currentProjectFile = project_paths.projectFile(sourceFile, info);
210
+ if (optionsNode) {
211
+ let optionProjectFile = currentProjectFile;
212
+ let optionLiteral;
213
+ if (ts.isObjectLiteralExpression(optionsNode)) {
214
+ optionLiteral = optionsNode;
215
+ addProvidersToBootstrapOption(optionProjectFile, optionLiteral, providerFn, replacements);
216
+ }
217
+ else if (ts.isIdentifier(optionsNode) ||
218
+ ts.isCallExpression(optionsNode) ||
219
+ ts.isPropertyAccessExpression(optionsNode)) {
220
+ // This is tricky case to handle, in G3 we're might not be able to resolve the identifier's value
221
+ // Our best alternative is to assume there is no CD providers set and add the ZoneChangeDetection provider
222
+ // In the cases where it is, we'll just override the zone provider we just set by re-used inthe appConfig providers
223
+ // TODO: Should we insert a TODO to clean this up ?
224
+ const text = `{...${optionsNode.getText()}, providers: [${providerFn}, ...${optionsNode.getText()}.providers]}`;
225
+ replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({
226
+ position: optionsNode.getStart(),
227
+ end: optionsNode.getEnd(),
228
+ toInsert: text,
229
+ })));
230
+ }
231
+ else {
232
+ throw new Error('unsupported optionsNode: ' + optionsNode.getText());
233
+ }
234
+ }
235
+ else {
236
+ // No options object, add it.
237
+ const text = `{providers: [${providerFn}]}`;
238
+ replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({
239
+ position: node.expression.getEnd() + 1,
240
+ end: node.expression.getEnd() + 1,
241
+ toInsert: text,
242
+ })));
243
+ }
244
+ importManager.addImport({
245
+ exportModuleSpecifier: CORE_PACKAGE,
246
+ exportSymbolName: 'provideZoneChangeDetection',
247
+ requestedFile: sourceFile,
248
+ });
249
+ }
250
+ analyzeBootstrapModule(node, sourceFile, reflector, evaluator, info, typeChecker, importManager, replacements) {
251
+ const moduleIdentifier = node.arguments[0];
252
+ const moduleType = evaluator.evaluate(moduleIdentifier);
253
+ if (!(moduleType instanceof project_tsconfig_paths.Reference) || !ts.isClassDeclaration(moduleType.node)) {
254
+ return;
255
+ }
256
+ const moduleClass = moduleType.node;
257
+ const ngModule = findNgModule(moduleClass, reflector);
258
+ if (!ngModule) {
259
+ return;
260
+ }
261
+ const moduleSourceFile = moduleClass.getSourceFile();
262
+ const moduleProjectFile = project_paths.projectFile(moduleSourceFile, info);
263
+ if (moduleSourceFile.getText().includes('ZoneChangeDetectionModule')) {
264
+ // If the file already contains the ZoneChangeDetectionModule, we can skip it.
265
+ return;
266
+ }
267
+ // Always remove the options argument
268
+ replacements.push(new project_paths.Replacement(project_paths.projectFile(sourceFile, info), new project_paths.TextUpdate({ position: moduleIdentifier.getEnd(), end: node.getEnd() - 1, toInsert: '' })));
269
+ const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(ngModule, typeChecker);
270
+ if (hasExistingChangeDetectionProvider) {
271
+ return;
272
+ }
273
+ // Let's try to understand the bootstrap options.
274
+ const optionsNode = node.arguments[1];
275
+ const options = optionsNode && ts.isObjectLiteralExpression(optionsNode)
276
+ ? evaluator.evaluate(optionsNode)
277
+ : null;
278
+ let zoneCdProvider = ZONE_CD_PROVIDER;
279
+ let zoneInstanceProvider = null;
280
+ if (options instanceof Map) {
281
+ const ngZoneOption = options.get('ngZone');
282
+ if (options.has('ngZoneRunCoalescing') || options.has('ngZoneEventCoalescing')) {
283
+ const config = [];
284
+ if (options.get('ngZoneRunCoalescing')) {
285
+ config.push('runCoalescing: true');
286
+ }
287
+ if (options.get('ngZoneEventCoalescing')) {
288
+ config.push('eventCoalescing: true');
289
+ }
290
+ zoneCdProvider = `${PROVIDE_ZONE_CHANGE_DETECTION}(${config.length > 0 ? `{ ${config.join(', ')} }` : ''})`;
291
+ }
292
+ if (ngZoneOption instanceof project_tsconfig_paths.Reference) {
293
+ importManager.addImport({
294
+ exportModuleSpecifier: CORE_PACKAGE,
295
+ exportSymbolName: 'NgZone',
296
+ requestedFile: moduleSourceFile,
297
+ });
298
+ const clazz = ngZoneOption.node;
299
+ if (ts.isClassDeclaration(clazz) && clazz.name) {
300
+ const customZoneSourceFile = clazz.getSourceFile();
301
+ const exportModuleSpecifier = ngZoneOption.bestGuessOwningModule?.specifier ??
302
+ imports.getRelativePath(moduleSourceFile.fileName, customZoneSourceFile.fileName);
303
+ importManager.addImport({
304
+ exportModuleSpecifier,
305
+ exportSymbolName: clazz.name.text,
306
+ requestedFile: moduleSourceFile,
307
+ });
308
+ zoneInstanceProvider = `{provide: NgZone, useClass: ${clazz.name.text}}`;
309
+ }
310
+ }
311
+ else if (typeof ngZoneOption === 'string' && ngZoneOption === 'noop') {
312
+ importManager.addImport({
313
+ exportModuleSpecifier: CORE_PACKAGE,
314
+ exportSymbolName: 'NgZone',
315
+ requestedFile: moduleSourceFile,
316
+ });
317
+ // The migration specifically relies on this being longer than ZoneChangeDetectionModule
318
+ // As we take the "longest" change in case of duplicate replacements.
319
+ const moduleName = 'ZonelessChangeDetectionModule';
320
+ replacements.push(new project_paths.Replacement(moduleProjectFile, new project_paths.TextUpdate({
321
+ position: moduleClass.getStart() - 1,
322
+ end: moduleClass.getStart() - 1,
323
+ toInsert: `${NoopNgZone}\n@NgModule({providers: [{provide: NgZone, useClass: NoopNgZone}]})\nexport class ${moduleName} {}\n\n`,
324
+ })));
325
+ const importsNode = property_name.findLiteralProperty(ngModule, 'imports');
326
+ if (importsNode && ts.isPropertyAssignment(importsNode)) {
327
+ insertZoneCDModule(importsNode.initializer, moduleProjectFile, replacements, moduleName);
328
+ }
329
+ return;
330
+ }
331
+ else if (ngZoneOption && typeof ngZoneOption !== 'string') {
332
+ // This is a case where we're not able to migrate automatically
333
+ // The migration fails gracefully, keeps the ngZone option and adds a TODO.
334
+ let ngZoneValue;
335
+ optionsNode.properties.forEach((p) => {
336
+ if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'ngZone') {
337
+ ngZoneValue = p.initializer.getText();
338
+ }
339
+ else if (ts.isShorthandPropertyAssignment(p) && p.name.text === 'ngZone') {
340
+ ngZoneValue = p.name.text;
341
+ }
342
+ });
343
+ if (ngZoneValue) {
344
+ // We re-add the ngZone option
345
+ replacements.push(new project_paths.Replacement(project_paths.projectFile(sourceFile, info), new project_paths.TextUpdate({
346
+ position: moduleIdentifier.getEnd(),
347
+ end: node.getEnd() - 1,
348
+ toInsert: `, {ngZone: ${ngZoneValue}}`,
349
+ })));
350
+ }
351
+ // And add the TODO
352
+ replacements.push(new project_paths.Replacement(project_paths.projectFile(sourceFile, info), new project_paths.TextUpdate({
353
+ position: node.getStart() - 1,
354
+ end: node.getStart() - 1,
355
+ toInsert: '// TODO: BootstrapOptions are deprecated & ignored. Configure NgZone in the providers array of the application module instead.',
356
+ })));
357
+ }
358
+ }
359
+ const providers = [zoneCdProvider];
360
+ if (zoneInstanceProvider) {
361
+ providers.push(zoneInstanceProvider);
362
+ }
363
+ if (providers.length > 0) {
364
+ importManager.addImport({
365
+ exportModuleSpecifier: CORE_PACKAGE,
366
+ exportSymbolName: PROVIDE_ZONE_CHANGE_DETECTION,
367
+ requestedFile: moduleSourceFile,
368
+ });
369
+ addProvidersToNgModule(moduleProjectFile, moduleSourceFile, ngModule, providers.join(',\n'), replacements);
370
+ }
371
+ }
372
+ analyzeTestBedInitEnvironment(callExpr, sourceFile, info, typeChecker, importManager, replacements) {
373
+ const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(callExpr, typeChecker);
374
+ if (hasExistingChangeDetectionProvider)
375
+ return;
376
+ const ngModules = callExpr.arguments[0];
377
+ const moduleProjectFile = project_paths.projectFile(sourceFile, info);
378
+ importManager.addImport({
379
+ exportModuleSpecifier: CORE_PACKAGE,
380
+ exportSymbolName: PROVIDE_ZONE_CHANGE_DETECTION,
381
+ requestedFile: sourceFile,
382
+ });
383
+ let tmpNode = callExpr;
384
+ let insertPosition = 0;
385
+ while (tmpNode.parent.kind !== ts.SyntaxKind.SourceFile) {
386
+ insertPosition = tmpNode.parent.getStart(sourceFile, true) - 1;
387
+ tmpNode = tmpNode.parent;
388
+ }
389
+ importManager.addImport({
390
+ exportModuleSpecifier: CORE_PACKAGE,
391
+ exportSymbolName: 'NgModule',
392
+ requestedFile: sourceFile,
393
+ });
394
+ addZoneCDModule(ZONE_CD_PROVIDER, moduleProjectFile, insertPosition, replacements);
395
+ insertZoneCDModule(ngModules, moduleProjectFile, replacements, 'ZoneChangeDetectionModule');
396
+ }
397
+ analyzeModuleWithBootstrapProp(ngModuleProps, sourceFile, info, typeChecker, importManager, replacements) {
398
+ if (sourceFile.getText().includes('ZoneChangeDetectionModule')) {
399
+ // If the file already contains the ZoneChangeDetectionModule, we can skip it.
400
+ return;
401
+ }
402
+ const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(ngModuleProps, typeChecker);
403
+ if (hasExistingChangeDetectionProvider)
404
+ return;
405
+ importManager.addImport({
406
+ exportModuleSpecifier: CORE_PACKAGE,
407
+ exportSymbolName: PROVIDE_ZONE_CHANGE_DETECTION,
408
+ requestedFile: sourceFile,
409
+ });
410
+ addProvidersToNgModule(project_paths.projectFile(sourceFile, info), sourceFile, ngModuleProps, ZONE_CD_PROVIDER, replacements);
411
+ }
412
+ }
413
+ function addProvidersToNgModule(projectFile, moduleSourceFile, ngModule, providersText, replacements) {
414
+ // ObjLiteral => callExp => Decorator => ClassExpression
415
+ const moduleClassDeclaration = ngModule.parent.parent.parent;
416
+ const insertPosition = moduleClassDeclaration.getStart(moduleSourceFile, true) - 1;
417
+ addZoneCDModule(providersText, projectFile, insertPosition, replacements);
418
+ const importsNode = property_name.findLiteralProperty(ngModule, 'imports');
419
+ if (importsNode && ts.isPropertyAssignment(importsNode)) {
420
+ insertZoneCDModule(importsNode.initializer, projectFile, replacements, 'ZoneChangeDetectionModule');
421
+ }
422
+ else {
423
+ const text = `imports: [ZoneChangeDetectionModule]`;
424
+ const toInsert = `${text},\n`;
425
+ let position = ngModule.getStart() + 1;
426
+ if (ngModule.properties.length > 0) {
427
+ const firstProperty = ngModule.properties[0];
428
+ position = firstProperty.getStart();
429
+ }
430
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
431
+ position,
432
+ end: position,
433
+ toInsert,
434
+ })));
435
+ }
436
+ }
437
+ function addZoneCDModule(providersText, projectFile, location, replacements) {
438
+ const newModuleText = `\n@NgModule({ providers: [ ${providersText} ] })
439
+ export class ZoneChangeDetectionModule {}\n\n`;
440
+ if (replacementsHaveZoneCdModule(projectFile.rootRelativePath, replacements, newModuleText)) {
441
+ return;
442
+ }
443
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
444
+ position: location,
445
+ end: location,
446
+ toInsert: newModuleText,
447
+ })));
448
+ }
449
+ function insertZoneCDModule(node, projectFile, replacements, importedModule) {
450
+ if (ts.isArrayLiteralExpression(node)) {
451
+ const literal = node;
452
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
453
+ position: literal.elements[0]?.getStart() ?? literal.getEnd() - 1,
454
+ end: literal.elements[0]?.getStart() ?? literal.getEnd() - 1,
455
+ toInsert: importedModule + ',',
456
+ })));
457
+ }
458
+ else if (ts.isIdentifier(node)) {
459
+ // This should be a good enough heuristic to determine if the identifier is not array
460
+ let isArray = !node.text.endsWith('Module');
461
+ // Because if it's an array, we need to spread it
462
+ const newImports = `[${importedModule}, ${isArray ? '...' : ''}${node.text}]`;
463
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
464
+ position: node.getStart(),
465
+ end: node.getEnd(),
466
+ toInsert: newImports,
467
+ })));
468
+ }
469
+ else {
470
+ throw new Error('unsupported importsNode: ' + node.getText());
471
+ }
472
+ }
473
+ function addProvidersToBootstrapOption(projectFile, optionsNode, providersText, replacements) {
474
+ const providersProp = property_name.findLiteralProperty(optionsNode, 'providers');
475
+ if (providersProp && ts.isPropertyAssignment(providersProp)) {
476
+ // Can be bootstrap(App, {providers: [...]}), bootstrap(App, {providers}), bootstrap(App, {...appConfig, providers}) etc.
477
+ if (ts.isArrayLiteralExpression(providersProp.initializer)) {
478
+ const initializer = providersProp.initializer;
479
+ const text = `${providersText},`;
480
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
481
+ position: initializer.elements[0]?.getStart() ?? initializer.getEnd() - 1,
482
+ end: initializer.elements[0]?.getStart() ?? initializer.getEnd() - 1,
483
+ toInsert: text,
484
+ })));
485
+ }
486
+ else if (ts.isIdentifier(providersProp.initializer)) {
487
+ const newProviders = `[${providersText}, ...${providersProp.initializer.text}]`;
488
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
489
+ position: providersProp.initializer.getStart(),
490
+ end: providersProp.initializer.getEnd(),
491
+ toInsert: newProviders,
492
+ })));
493
+ }
494
+ else {
495
+ const newProviders = `[${providersText}, ...`;
496
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
497
+ position: providersProp.initializer.getStart(),
498
+ end: providersProp.initializer.getStart(),
499
+ toInsert: newProviders,
500
+ })));
501
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
502
+ position: providersProp.initializer.getEnd(),
503
+ end: providersProp.initializer.getEnd(),
504
+ toInsert: ']',
505
+ })));
506
+ }
507
+ }
508
+ else if (providersProp && ts.isShorthandPropertyAssignment(providersProp)) {
509
+ const newProviders = `providers: [${providersText}, ...${providersProp.name.text}]`;
510
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
511
+ position: providersProp.getStart(),
512
+ end: providersProp.getEnd(),
513
+ toInsert: newProviders,
514
+ })));
515
+ }
516
+ else if (optionsNode.properties.length === 1 &&
517
+ ts.isSpreadAssignment(optionsNode.properties[0])) {
518
+ const spread = optionsNode.properties[0];
519
+ const newProviders = `, providers: [${providersText}, ...${spread.expression.getText()}.providers]`;
520
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
521
+ position: spread.getEnd(),
522
+ end: spread.getEnd(),
523
+ toInsert: newProviders,
524
+ })));
525
+ }
526
+ else {
527
+ const text = `providers: [${providersText}]`;
528
+ let toInsert;
529
+ let position;
530
+ if (optionsNode.properties.length > 0) {
531
+ const lastProperty = optionsNode.properties[optionsNode.properties.length - 1];
532
+ toInsert = `,\n ${text}`;
533
+ position = lastProperty.getEnd();
534
+ }
535
+ else {
536
+ toInsert = `\n ${text}\n`;
537
+ position = optionsNode.getStart() + 1;
538
+ }
539
+ replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
540
+ position,
541
+ end: position,
542
+ toInsert,
543
+ })));
544
+ }
545
+ }
546
+ function findNgModule(node, reflector) {
547
+ const decorators = reflector.getDecoratorsOfDeclaration(node);
548
+ if (decorators) {
549
+ const ngModuleDecorator = project_tsconfig_paths.getAngularDecorators(decorators, ['NgModule'], true)[0];
550
+ if (ngModuleDecorator &&
551
+ ngModuleDecorator.args &&
552
+ ngModuleDecorator.args.length > 0 &&
553
+ ts.isObjectLiteralExpression(ngModuleDecorator.args[0])) {
554
+ return ngModuleDecorator.args[0];
555
+ }
556
+ }
557
+ return null;
558
+ }
559
+ function hasChangeDetectionProvider(expression, // either the bootstrapApplication or platformBrowserDynamic().bootstrapModule()
560
+ typeChecker) {
561
+ let literal;
562
+ if (ts.isCallExpression(expression)) {
563
+ let optionsNode = expression.arguments[1];
564
+ if (!optionsNode &&
565
+ symbol.isReferenceToImport(typeChecker, expression.expression, imports.getImportSpecifier(expression.getSourceFile(), '@angular/core', 'createApplication'))) {
566
+ optionsNode = expression.arguments[0];
567
+ }
568
+ if (!optionsNode)
569
+ return false;
570
+ if (ts.isIdentifier(optionsNode)) {
571
+ literal = getObjectLiteralFromIdentifier(optionsNode, typeChecker);
572
+ }
573
+ else {
574
+ literal = optionsNode;
575
+ }
576
+ }
577
+ else {
578
+ literal = expression;
579
+ }
580
+ if (!literal) {
581
+ return false;
582
+ }
583
+ const provideZoneCdSpecifier = imports.getImportSpecifier(literal.getSourceFile(), '@angular/core', 'provideZoneChangeDetection');
584
+ const provideZonelessCdSpecifier = imports.getImportSpecifier(literal.getSourceFile(), '@angular/core', 'provideZonelessChangeDetection');
585
+ if (provideZoneCdSpecifier === null && provideZonelessCdSpecifier === null) {
586
+ return false;
587
+ }
588
+ const found = ts.forEachChild(literal, function walk(node) {
589
+ if (ts.isCallExpression(node)) {
590
+ if (provideZonelessCdSpecifier &&
591
+ node.getText().includes(provideZonelessCdSpecifier.getText())) {
592
+ return true;
593
+ }
594
+ if (provideZoneCdSpecifier && node.getText().includes(provideZoneCdSpecifier.getText())) {
595
+ return true;
596
+ }
597
+ }
598
+ return ts.forEachChild(node, walk);
599
+ });
600
+ return !!found;
601
+ }
602
+ function getObjectLiteralFromIdentifier(identifier, typeChecker) {
603
+ let symbol = typeChecker.getSymbolAtLocation(identifier);
604
+ if (!symbol)
605
+ return;
606
+ // Follow aliases (for imported symbols)
607
+ if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
608
+ symbol = typeChecker.getAliasedSymbol(symbol);
609
+ }
610
+ const declarations = symbol.getDeclarations();
611
+ if (!declarations)
612
+ return;
613
+ for (const decl of declarations) {
614
+ if (ts.isVariableDeclaration(decl) &&
615
+ decl.initializer &&
616
+ ts.isObjectLiteralExpression(decl.initializer)) {
617
+ return decl.initializer;
618
+ }
619
+ }
620
+ return;
621
+ }
622
+ /**
623
+ * Extracts the import specifiers related to bootstraping from the source file.
624
+ * Returns null if no relevant specifiers are found.
625
+ */
626
+ function getSpecifiers(sourceFile) {
627
+ const createApplicationSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core', 'createApplication');
628
+ const bootstrapAppSpecifier = imports.getImportSpecifier(sourceFile, '@angular/platform-browser', 'bootstrapApplication');
629
+ const platformBrowserDynamicSpecifier = imports.getImportSpecifier(sourceFile, '@angular/platform-browser-dynamic', 'platformBrowserDynamic');
630
+ const platformBrowserSpecifier = imports.getImportSpecifier(sourceFile, '@angular/platform-browser', 'platformBrowser');
631
+ const testBedSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core/testing', 'TestBed');
632
+ const ngModuleSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core', 'NgModule');
633
+ if (!createApplicationSpecifier &&
634
+ !bootstrapAppSpecifier &&
635
+ !platformBrowserDynamicSpecifier &&
636
+ !platformBrowserSpecifier &&
637
+ !testBedSpecifier &&
638
+ !ngModuleSpecifier) {
639
+ return null;
640
+ }
641
+ return {
642
+ createApplicationSpecifier,
643
+ bootstrapAppSpecifier,
644
+ platformBrowserDynamicSpecifier,
645
+ platformBrowserSpecifier,
646
+ testBedSpecifier,
647
+ ngModuleSpecifier,
648
+ };
649
+ }
650
+ /**
651
+ * Removes duplicate replacements and for replacements at the same position, takes the longest one.
652
+ */
653
+ function deduplicateReplacements(replacements) {
654
+ if (replacements.length <= 1) {
655
+ return replacements;
656
+ }
657
+ // Group replacements by file and position
658
+ const groupedByFileAndPosition = new Map();
659
+ for (const replacement of replacements) {
660
+ const fileKey = replacement.projectFile.id;
661
+ const position = replacement.update.data.position;
662
+ if (!groupedByFileAndPosition.has(fileKey)) {
663
+ groupedByFileAndPosition.set(fileKey, new Map());
664
+ }
665
+ const fileReplacements = groupedByFileAndPosition.get(fileKey);
666
+ if (!fileReplacements.has(position)) {
667
+ fileReplacements.set(position, []);
668
+ }
669
+ fileReplacements.get(position).push(replacement);
670
+ }
671
+ const result = [];
672
+ for (const fileReplacements of groupedByFileAndPosition.values()) {
673
+ for (const positionReplacements of fileReplacements.values()) {
674
+ if (positionReplacements.length === 1) {
675
+ result.push(positionReplacements[0]);
676
+ }
677
+ else {
678
+ // For multiple replacements at the same position, take the one with the longest content
679
+ const longestReplacement = positionReplacements.reduce((longest, current) => {
680
+ const longestLength = longest.update.data.toInsert.length;
681
+ const currentLength = current.update.data.toInsert.length;
682
+ return currentLength > longestLength ? current : longest;
683
+ });
684
+ result.push(longestReplacement);
685
+ }
686
+ }
687
+ }
688
+ return result;
689
+ }
690
+ /**
691
+ * In the case we're looking to insert a new ZoneChangeDetectionModule, we need to check if we already inserted one.
692
+ *
693
+ * This function also checks if the existing one has fewer options (shorter text length), which means the previous migration strategy inserted one
694
+ * but the following one is more complete and we should still add it (the dedup function will take care of the cleanup).
695
+ */
696
+ function replacementsHaveZoneCdModule(rootRelativePath, replacements, text) {
697
+ return replacements.some((replacement) => {
698
+ const exisitingText = replacement.update.data.toInsert;
699
+ const isSameFile = replacement.projectFile.rootRelativePath === rootRelativePath;
700
+ return (isSameFile &&
701
+ text.includes('ZoneChangeDetectionModule') &&
702
+ exisitingText.length >= text.length);
703
+ });
704
+ }
705
+
706
+ function migrate() {
707
+ return async (tree) => {
708
+ await project_paths.runMigrationInDevkit({
709
+ tree,
710
+ getMigration: () => new BootstrapOptionsMigration(),
711
+ });
712
+ };
713
+ }
714
+
715
+ exports.migrate = migrate;