@angular/core 21.0.0-next.0 → 21.0.0-next.10

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 (106) hide show
  1. package/fesm2022/_attribute-chunk.mjs +12 -0
  2. package/fesm2022/_attribute-chunk.mjs.map +1 -0
  3. package/fesm2022/_debug_node-chunk.mjs +18469 -0
  4. package/fesm2022/_debug_node-chunk.mjs.map +1 -0
  5. package/fesm2022/_effect-chunk.mjs +423 -0
  6. package/fesm2022/_effect-chunk.mjs.map +1 -0
  7. package/fesm2022/_effect-chunk2.mjs +2951 -0
  8. package/fesm2022/_effect-chunk2.mjs.map +1 -0
  9. package/fesm2022/_not_found-chunk.mjs +39 -0
  10. package/fesm2022/_not_found-chunk.mjs.map +1 -0
  11. package/fesm2022/_resource-chunk.mjs +378 -0
  12. package/fesm2022/_resource-chunk.mjs.map +1 -0
  13. package/fesm2022/_untracked-chunk.mjs +96 -0
  14. package/fesm2022/_untracked-chunk.mjs.map +1 -0
  15. package/fesm2022/_weak_ref-chunk.mjs +10 -0
  16. package/fesm2022/_weak_ref-chunk.mjs.map +1 -0
  17. package/fesm2022/core.mjs +2499 -4185
  18. package/fesm2022/core.mjs.map +1 -1
  19. package/fesm2022/primitives-di.mjs +23 -0
  20. package/fesm2022/primitives-di.mjs.map +1 -0
  21. package/fesm2022/primitives-event-dispatch.mjs +788 -0
  22. package/fesm2022/primitives-event-dispatch.mjs.map +1 -0
  23. package/fesm2022/primitives-signals.mjs +187 -0
  24. package/fesm2022/primitives-signals.mjs.map +1 -0
  25. package/fesm2022/rxjs-interop.mjs +210 -308
  26. package/fesm2022/rxjs-interop.mjs.map +1 -1
  27. package/fesm2022/testing.mjs +2309 -3170
  28. package/fesm2022/testing.mjs.map +1 -1
  29. package/package.json +18 -12
  30. package/resources/best-practices.md +56 -0
  31. package/schematics/bundles/add-bootstrap-context-to-server-main.cjs +117 -0
  32. package/schematics/bundles/application-config-core.cjs +84 -0
  33. package/schematics/bundles/{apply_import_manager-DR9xXCle.cjs → apply_import_manager-1Zs_gpB6.cjs} +4 -5
  34. package/schematics/bundles/bootstrap-options-migration.cjs +598 -0
  35. package/schematics/bundles/cleanup-unused-imports.cjs +9 -13
  36. package/schematics/bundles/common-to-standalone-migration.cjs +381 -0
  37. package/schematics/bundles/{compiler_host-BXBP7CE2.cjs → compiler_host-DBwYMlTo.cjs} +10 -11
  38. package/schematics/bundles/control-flow-migration.cjs +122 -119
  39. package/schematics/bundles/{imports-CIX-JgAN.cjs → imports-DP72APSx.cjs} +6 -1
  40. package/schematics/bundles/{index-CfTQUOiz.cjs → index-B7I9sIUx.cjs} +36 -39
  41. package/schematics/bundles/inject-migration.cjs +148 -70
  42. package/schematics/bundles/leading_space-D9nQ8UQC.cjs +1 -1
  43. package/schematics/bundles/{migrate_ts_type_references-6NtAj-Wk.cjs → migrate_ts_type_references-UGIUl7En.cjs} +500 -24
  44. package/schematics/bundles/ng_component_template-Dsuq1Lw7.cjs +185 -0
  45. package/schematics/bundles/{ng_decorators-B5HCqr20.cjs → ng_decorators-DSFlWYQY.cjs} +2 -2
  46. package/schematics/bundles/ngclass-to-class-migration.cjs +542 -0
  47. package/schematics/bundles/ngstyle-to-style-migration.cjs +487 -0
  48. package/schematics/bundles/nodes-B16H9JUd.cjs +1 -1
  49. package/schematics/bundles/output-migration.cjs +16 -19
  50. package/schematics/bundles/parse_html-8VLCL37B.cjs +132 -0
  51. package/schematics/bundles/{project_paths-DcaODbky.cjs → project_paths-DvD50ouC.cjs} +14 -247
  52. package/schematics/bundles/project_tsconfig_paths-CDVxT6Ov.cjs +90 -0
  53. package/schematics/bundles/property_name-BBwFuqMe.cjs +1 -1
  54. package/schematics/bundles/route-lazy-loading.cjs +54 -26
  55. package/schematics/bundles/router-current-navigation.cjs +7 -18
  56. package/schematics/bundles/router-last-successful-navigation.cjs +7 -18
  57. package/schematics/bundles/router-testing-module-migration.cjs +502 -0
  58. package/schematics/bundles/self-closing-tags-migration.cjs +17 -216
  59. package/schematics/bundles/signal-input-migration.cjs +93 -29
  60. package/schematics/bundles/signal-queries-migration.cjs +22 -25
  61. package/schematics/bundles/signals.cjs +10 -13
  62. package/schematics/bundles/standalone-migration.cjs +135 -102
  63. package/schematics/bundles/{symbol-VPWguRxr.cjs → symbol-BObKoqes.cjs} +3 -2
  64. package/schematics/collection.json +23 -0
  65. package/schematics/migrations/common-to-standalone-migration/schema.json +14 -0
  66. package/schematics/migrations/ngclass-to-class-migration/schema.json +20 -0
  67. package/schematics/migrations/ngstyle-to-style-migration/schema.json +20 -0
  68. package/schematics/migrations/router-testing-module-migration/schema.json +14 -0
  69. package/schematics/migrations.json +16 -2
  70. package/{api.d.d.ts → types/_api-chunk.d.ts} +9 -6
  71. package/{chrome_dev_tools_performance.d.d.ts → types/_chrome_dev_tools_performance-chunk.d.ts} +26 -31
  72. package/{discovery.d.d.ts → types/_discovery-chunk.d.ts} +135 -98
  73. package/{signal.d.d.ts → types/_effect-chunk.d.ts} +14 -5
  74. package/{event_dispatcher.d.d.ts → types/_event_dispatcher-chunk.d.ts} +2 -2
  75. package/{graph.d.d.ts → types/_formatter-chunk.d.ts} +40 -7
  76. package/{weak_ref.d.d.ts → types/_weak_ref-chunk.d.ts} +2 -2
  77. package/{index.d.ts → types/core.d.ts} +233 -305
  78. package/{primitives/di/index.d.ts → types/primitives-di.d.ts} +2 -2
  79. package/{primitives/event-dispatch/index.d.ts → types/primitives-event-dispatch.d.ts} +4 -4
  80. package/{primitives/signals/index.d.ts → types/primitives-signals.d.ts} +7 -8
  81. package/{rxjs-interop/index.d.ts → types/rxjs-interop.d.ts} +8 -6
  82. package/{testing/index.d.ts → types/testing.d.ts} +7 -7
  83. package/fesm2022/attribute.mjs +0 -24
  84. package/fesm2022/attribute.mjs.map +0 -1
  85. package/fesm2022/debug_node.mjs +0 -31833
  86. package/fesm2022/debug_node.mjs.map +0 -1
  87. package/fesm2022/not_found.mjs +0 -56
  88. package/fesm2022/not_found.mjs.map +0 -1
  89. package/fesm2022/primitives/di.mjs +0 -23
  90. package/fesm2022/primitives/di.mjs.map +0 -1
  91. package/fesm2022/primitives/event-dispatch.mjs +0 -1622
  92. package/fesm2022/primitives/event-dispatch.mjs.map +0 -1
  93. package/fesm2022/primitives/signals.mjs +0 -89
  94. package/fesm2022/primitives/signals.mjs.map +0 -1
  95. package/fesm2022/resource.mjs +0 -633
  96. package/fesm2022/resource.mjs.map +0 -1
  97. package/fesm2022/root_effect_scheduler.mjs +0 -4007
  98. package/fesm2022/root_effect_scheduler.mjs.map +0 -1
  99. package/fesm2022/signal.mjs +0 -560
  100. package/fesm2022/signal.mjs.map +0 -1
  101. package/fesm2022/untracked.mjs +0 -117
  102. package/fesm2022/untracked.mjs.map +0 -1
  103. package/fesm2022/weak_ref.mjs +0 -12
  104. package/fesm2022/weak_ref.mjs.map +0 -1
  105. package/schematics/bundles/index-esqfDjNB.cjs +0 -22074
  106. package/schematics/bundles/project_tsconfig_paths-CS-eSeHC.cjs +0 -51062
@@ -0,0 +1,542 @@
1
+ 'use strict';
2
+ /**
3
+ * @license Angular v21.0.0-next.10
4
+ * (c) 2010-2025 Google LLC. https://angular.io/
5
+ * License: MIT
6
+ */
7
+ 'use strict';
8
+
9
+ var ts = require('typescript');
10
+ require('@angular/compiler-cli');
11
+ var migrations = require('@angular/compiler-cli/private/migrations');
12
+ require('node:path');
13
+ var project_paths = require('./project_paths-DvD50ouC.cjs');
14
+ var compiler = require('@angular/compiler');
15
+ var apply_import_manager = require('./apply_import_manager-1Zs_gpB6.cjs');
16
+ var imports = require('./imports-DP72APSx.cjs');
17
+ var parse_html = require('./parse_html-8VLCL37B.cjs');
18
+ var ng_component_template = require('./ng_component_template-Dsuq1Lw7.cjs');
19
+ require('@angular-devkit/core');
20
+ require('node:path/posix');
21
+ require('@angular-devkit/schematics');
22
+ require('./project_tsconfig_paths-CDVxT6Ov.cjs');
23
+ require('./ng_decorators-DSFlWYQY.cjs');
24
+ require('./property_name-BBwFuqMe.cjs');
25
+
26
+ const ngClassStr = 'NgClass';
27
+ const commonModuleStr = '@angular/common';
28
+ const commonModuleImportsStr = 'CommonModule';
29
+ function migrateNgClassBindings(template, config, componentNode, typeChecker) {
30
+ const parsed = parse_html.parseTemplate(template);
31
+ if (!parsed.tree || !parsed.tree.rootNodes.length) {
32
+ return { migrated: template, changed: false, replacementCount: 0, canRemoveCommonModule: false };
33
+ }
34
+ const visitor = new NgClassCollector(template, componentNode, typeChecker);
35
+ compiler.visitAll(visitor, parsed.tree.rootNodes, config);
36
+ let newTemplate = template;
37
+ let changedOffset = 0;
38
+ let replacementCount = 0;
39
+ for (const { start, end, replacement } of visitor.replacements) {
40
+ const currentLength = newTemplate.length;
41
+ newTemplate = replaceTemplate(newTemplate, replacement, start, end, changedOffset);
42
+ changedOffset += newTemplate.length - currentLength;
43
+ replacementCount++;
44
+ }
45
+ const changed = newTemplate !== template;
46
+ return {
47
+ migrated: newTemplate,
48
+ changed,
49
+ replacementCount,
50
+ canRemoveCommonModule: changed ? parse_html.canRemoveCommonModule(newTemplate) : false,
51
+ };
52
+ }
53
+ /**
54
+ * Creates a Replacement to remove `NgClass` from a component's `imports` array.
55
+ * Uses ReflectionHost + PartialEvaluator for robust AST analysis.
56
+ */
57
+ function createNgClassImportsArrayRemoval(classNode, file, typeChecker, removeCommonModule) {
58
+ const reflector = new migrations.TypeScriptReflectionHost(typeChecker);
59
+ const evaluator = new migrations.PartialEvaluator(reflector, typeChecker, null);
60
+ // Use ReflectionHost to get decorators instead of manual AST traversal
61
+ const decorators = reflector.getDecoratorsOfDeclaration(classNode);
62
+ if (!decorators) {
63
+ return null;
64
+ }
65
+ // Find @Component decorator using ReflectionHost
66
+ const componentDecorator = decorators.find((decorator) => decorator.name === 'Component');
67
+ if (!componentDecorator || !componentDecorator.args || componentDecorator.args.length === 0) {
68
+ return null;
69
+ }
70
+ // Use PartialEvaluator to evaluate the decorator metadata
71
+ const decoratorMetadata = evaluator.evaluate(componentDecorator.args[0]);
72
+ if (!decoratorMetadata || typeof decoratorMetadata !== 'object') {
73
+ return null;
74
+ }
75
+ // Get the actual AST node for the imports property
76
+ const componentDecoratorNode = componentDecorator.node;
77
+ if (!ts.isDecorator(componentDecoratorNode) ||
78
+ !ts.isCallExpression(componentDecoratorNode.expression) ||
79
+ !ts.isObjectLiteralExpression(componentDecoratorNode.expression.arguments[0])) {
80
+ return null;
81
+ }
82
+ const objLiteral = componentDecoratorNode.expression.arguments[0];
83
+ const importsProperty = objLiteral.properties.find((p) => ts.isPropertyAssignment(p) &&
84
+ p.name.getText() === 'imports' &&
85
+ ts.isArrayLiteralExpression(p.initializer));
86
+ if (!importsProperty || !ts.isArrayLiteralExpression(importsProperty.initializer)) {
87
+ return null;
88
+ }
89
+ const importsArray = importsProperty.initializer;
90
+ const elementsToRemove = new Set([ngClassStr]);
91
+ if (removeCommonModule) {
92
+ elementsToRemove.add(commonModuleImportsStr);
93
+ }
94
+ const originalElements = importsArray.elements;
95
+ const filteredElements = originalElements.filter((el) => !ts.isIdentifier(el) || !elementsToRemove.has(el.text));
96
+ if (filteredElements.length === originalElements.length) {
97
+ return null; // No changes needed.
98
+ }
99
+ // If the array becomes empty, remove the entire `imports` property.
100
+ if (filteredElements.length === 0) {
101
+ const removalRange = getPropertyRemovalRange(importsProperty);
102
+ return new project_paths.Replacement(file, new project_paths.TextUpdate({
103
+ position: removalRange.start,
104
+ end: removalRange.end,
105
+ toInsert: '',
106
+ }));
107
+ }
108
+ const printer = ts.createPrinter();
109
+ const newArray = ts.factory.updateArrayLiteralExpression(importsArray, filteredElements);
110
+ const newText = printer.printNode(ts.EmitHint.Unspecified, newArray, classNode.getSourceFile());
111
+ return new project_paths.Replacement(file, new project_paths.TextUpdate({
112
+ position: importsArray.getStart(),
113
+ end: importsArray.getEnd(),
114
+ toInsert: newText,
115
+ }));
116
+ }
117
+ function getPropertyRemovalRange(property) {
118
+ const parent = property.parent;
119
+ if (!ts.isObjectLiteralExpression(parent)) {
120
+ return { start: property.getStart(), end: property.getEnd() };
121
+ }
122
+ const properties = parent.properties;
123
+ const propertyIndex = properties.indexOf(property);
124
+ const end = property.getEnd();
125
+ if (propertyIndex < properties.length - 1) {
126
+ const nextProperty = properties[propertyIndex + 1];
127
+ return { start: property.getStart(), end: nextProperty.getStart() };
128
+ }
129
+ return { start: property.getStart(), end };
130
+ }
131
+ function calculateImportReplacements(info, sourceFiles, filesToRemoveCommonModule) {
132
+ const importReplacements = {};
133
+ const importManager = new migrations.ImportManager();
134
+ for (const sf of sourceFiles) {
135
+ const file = project_paths.projectFile(sf, info);
136
+ // Always remove NgClass if it's imported directly.
137
+ importManager.removeImport(sf, ngClassStr, commonModuleStr);
138
+ // Conditionally remove CommonModule if it's no longer needed.
139
+ if (filesToRemoveCommonModule.has(file.id)) {
140
+ importManager.removeImport(sf, commonModuleImportsStr, commonModuleStr);
141
+ }
142
+ const addRemove = [];
143
+ apply_import_manager.applyImportManagerChanges(importManager, addRemove, [sf], info);
144
+ if (addRemove.length > 0) {
145
+ importReplacements[file.id] = {
146
+ add: [],
147
+ addAndRemove: addRemove,
148
+ };
149
+ }
150
+ }
151
+ return importReplacements;
152
+ }
153
+ function replaceTemplate(template, replaceValue, start, end, offset) {
154
+ return template.slice(0, start + offset) + replaceValue + template.slice(end + offset);
155
+ }
156
+ /**
157
+ * Visitor class that scans Angular templates and collects replacements
158
+ * for [ngClass] bindings that use static object literals.
159
+ */
160
+ class NgClassCollector extends compiler.RecursiveVisitor {
161
+ componentNode;
162
+ typeChecker;
163
+ replacements = [];
164
+ originalTemplate;
165
+ isNgClassImported = true; // Default to true (permissive)
166
+ constructor(template, componentNode, typeChecker) {
167
+ super();
168
+ this.componentNode = componentNode;
169
+ this.typeChecker = typeChecker;
170
+ this.originalTemplate = template;
171
+ // If we have enough information, check if NgClass is actually imported.
172
+ // If not, we can confidently disable the migration for this component.
173
+ if (componentNode && typeChecker) {
174
+ const imports$1 = imports.getImportSpecifiers(componentNode.getSourceFile(), commonModuleStr, [
175
+ ngClassStr,
176
+ commonModuleImportsStr,
177
+ ]);
178
+ if (imports$1.length === 0) {
179
+ this.isNgClassImported = false;
180
+ }
181
+ }
182
+ }
183
+ visitElement(element, config) {
184
+ // If NgClass is not imported, do not attempt to migrate.
185
+ if (!this.isNgClassImported) {
186
+ return;
187
+ }
188
+ for (const attr of element.attrs) {
189
+ if (attr.name === '[ngClass]' && attr.valueSpan) {
190
+ const expr = this.originalTemplate.slice(attr.valueSpan.start.offset, attr.valueSpan.end.offset);
191
+ const staticMatch = tryParseStaticObjectLiteral(expr);
192
+ if (staticMatch === null) {
193
+ continue;
194
+ }
195
+ let replacement;
196
+ if (staticMatch.length === 0) {
197
+ replacement = '[class]=""';
198
+ }
199
+ else if (staticMatch.length === 1) {
200
+ const { key, value } = staticMatch[0];
201
+ // Special case: If the key is an empty string, use [class]=""
202
+ if (key === '') {
203
+ replacement = '[class]=""';
204
+ }
205
+ else {
206
+ // Normal single condition: use [class.className]="condition"
207
+ replacement = `[class.${key}]="${value}"`;
208
+ }
209
+ }
210
+ else {
211
+ // Check if all entries have the same value (condition)
212
+ const allSameValue = staticMatch.every((entry) => entry.value === staticMatch[0].value);
213
+ if (allSameValue &&
214
+ staticMatch.length > 1 &&
215
+ // Check if this was originally a single key with multiple classes
216
+ expr.includes('{') &&
217
+ expr.includes('}') &&
218
+ expr.split(':').length === 2) {
219
+ // Multiple classes with the same condition: use [class.class1]="condition" [class.class2]="condition"
220
+ if (config.migrateSpaceSeparatedKey) {
221
+ replacement = staticMatch
222
+ .map(({ key, value }) => `[class.${key}]="${value}"`)
223
+ .join(' ');
224
+ }
225
+ else {
226
+ continue;
227
+ }
228
+ }
229
+ else {
230
+ // Multiple conditions with different values: use [class]="{'class1': condition1, 'class2': condition2}"
231
+ replacement = `[class]="${expr}"`;
232
+ }
233
+ }
234
+ this.replacements.push({
235
+ start: attr.sourceSpan.start.offset,
236
+ end: attr.sourceSpan.end.offset,
237
+ replacement,
238
+ });
239
+ continue;
240
+ }
241
+ if (attr.name === 'ngClass' && attr.value) {
242
+ this.replacements.push({
243
+ start: attr.sourceSpan.start.offset,
244
+ end: attr.sourceSpan.end.offset,
245
+ replacement: `class="${attr.value}"`,
246
+ });
247
+ }
248
+ }
249
+ return super.visitElement(element, config);
250
+ }
251
+ }
252
+ function tryParseStaticObjectLiteral(expr) {
253
+ const trimmedExpr = expr.trim();
254
+ if (trimmedExpr === '{}' || trimmedExpr === '[]') {
255
+ return [];
256
+ }
257
+ if (!isObjectLiteralSyntax(trimmedExpr)) {
258
+ return null;
259
+ }
260
+ try {
261
+ const objectLiteral = parseAsObjectLiteral(trimmedExpr);
262
+ if (!objectLiteral) {
263
+ return null;
264
+ }
265
+ return extractClassBindings(objectLiteral);
266
+ }
267
+ catch {
268
+ return null;
269
+ }
270
+ }
271
+ /**
272
+ * Validates basic object literal syntax
273
+ */
274
+ function isObjectLiteralSyntax(expr) {
275
+ return expr.startsWith('{') && expr.endsWith('}');
276
+ }
277
+ /**
278
+ * Parses expression as TypeScript object literal
279
+ */
280
+ function parseAsObjectLiteral(expr) {
281
+ try {
282
+ const sourceFile = ts.createSourceFile('temp.ts', `const obj = ${expr}`, ts.ScriptTarget.Latest, true);
283
+ const variableStatement = sourceFile.statements[0];
284
+ if (!ts.isVariableStatement(variableStatement)) {
285
+ return null;
286
+ }
287
+ const declaration = variableStatement.declarationList.declarations[0];
288
+ if (!declaration.initializer || !ts.isObjectLiteralExpression(declaration.initializer)) {
289
+ return null;
290
+ }
291
+ // Manage invalid syntax ngClass="{class1 class2}"
292
+ const content = expr.slice(1, -1).trim();
293
+ if (content && !content.includes(':') && content.includes(' ') && !content.includes(',')) {
294
+ return null;
295
+ }
296
+ return declaration.initializer;
297
+ }
298
+ catch (error) {
299
+ return null;
300
+ }
301
+ }
302
+ /**
303
+ * Extracts class bindings from object literal properties
304
+ */
305
+ function extractClassBindings(objectLiteral) {
306
+ const result = [];
307
+ for (const property of objectLiteral.properties) {
308
+ if (ts.isShorthandPropertyAssignment(property)) {
309
+ const key = property.name.getText();
310
+ if (key.includes(' ')) {
311
+ return null;
312
+ }
313
+ result.push({ key, value: key });
314
+ }
315
+ else if (ts.isPropertyAssignment(property)) {
316
+ const keyText = extractPropertyKey(property.name);
317
+ const valueText = extractPropertyValue(property.initializer);
318
+ if (keyText === '' && valueText) {
319
+ result.push({ key: '', value: valueText });
320
+ }
321
+ else {
322
+ if (!keyText || !valueText) {
323
+ return null;
324
+ }
325
+ // Handle multiple CSS classes in single key (e.g., 'class1 class2': condition)
326
+ const classNames = keyText.split(/\s+/).filter(Boolean);
327
+ for (const className of classNames) {
328
+ result.push({ key: className, value: valueText });
329
+ }
330
+ }
331
+ }
332
+ else {
333
+ return null;
334
+ }
335
+ }
336
+ return result;
337
+ }
338
+ /**
339
+ * Extracts text from property key (name)
340
+ */
341
+ function extractPropertyKey(name) {
342
+ if (ts.isIdentifier(name)) {
343
+ return name.text;
344
+ }
345
+ if (ts.isStringLiteral(name)) {
346
+ return name.text;
347
+ }
348
+ if (ts.isNumericLiteral(name)) {
349
+ return name.text;
350
+ }
351
+ return null;
352
+ }
353
+ /**
354
+ * Extracts text from property value
355
+ */
356
+ function extractPropertyValue(initializer) {
357
+ // String literals: 'value' or "value"
358
+ if (ts.isStringLiteral(initializer)) {
359
+ return `'${initializer.text}'`;
360
+ }
361
+ // Numeric literals: 42, 3.14
362
+ if (ts.isNumericLiteral(initializer)) {
363
+ return initializer.text;
364
+ }
365
+ // Boolean and null keywords
366
+ if (initializer.kind === ts.SyntaxKind.TrueKeyword) {
367
+ return 'true';
368
+ }
369
+ if (initializer.kind === ts.SyntaxKind.FalseKeyword) {
370
+ return 'false';
371
+ }
372
+ if (initializer.kind === ts.SyntaxKind.NullKeyword) {
373
+ return 'null';
374
+ }
375
+ // Identifiers: isActive, condition, etc.
376
+ if (ts.isIdentifier(initializer)) {
377
+ return initializer.text;
378
+ }
379
+ return initializer.getText();
380
+ }
381
+
382
+ class NgClassMigration extends project_paths.TsurgeFunnelMigration {
383
+ config;
384
+ constructor(config = {}) {
385
+ super();
386
+ this.config = config;
387
+ }
388
+ processTemplate(template, node, file, info, typeChecker) {
389
+ const { migrated, changed, replacementCount, canRemoveCommonModule } = migrateNgClassBindings(template.content, this.config, node, typeChecker);
390
+ if (!changed) {
391
+ return null;
392
+ }
393
+ const fileToMigrate = template.inline
394
+ ? file
395
+ : project_paths.projectFile(template.filePath, info);
396
+ const end = template.start + template.content.length;
397
+ return {
398
+ replacements: [prepareTextReplacement(fileToMigrate, migrated, template.start, end)],
399
+ replacementCount,
400
+ canRemoveCommonModule,
401
+ };
402
+ }
403
+ async analyze(info) {
404
+ const { sourceFiles, program } = info;
405
+ const typeChecker = program.getTypeChecker();
406
+ const ngClassReplacements = [];
407
+ const filesWithNgClassDeclarations = new Set();
408
+ const filesToRemoveCommonModule = new Set();
409
+ for (const sf of sourceFiles) {
410
+ ts.forEachChild(sf, (node) => {
411
+ if (!ts.isClassDeclaration(node)) {
412
+ return;
413
+ }
414
+ const file = project_paths.projectFile(sf, info);
415
+ if (this.config.shouldMigrate && !this.config.shouldMigrate(file)) {
416
+ return;
417
+ }
418
+ const templateVisitor = new ng_component_template.NgComponentTemplateVisitor(typeChecker);
419
+ templateVisitor.visitNode(node);
420
+ const replacementsForClass = [];
421
+ let replacementCountForClass = 0;
422
+ let canRemoveCommonModuleForFile = true;
423
+ for (const template of templateVisitor.resolvedTemplates) {
424
+ const result = this.processTemplate(template, node, file, info, typeChecker);
425
+ if (result) {
426
+ replacementsForClass.push(...result.replacements);
427
+ replacementCountForClass += result.replacementCount;
428
+ if (!result.canRemoveCommonModule) {
429
+ canRemoveCommonModuleForFile = false;
430
+ }
431
+ }
432
+ }
433
+ if (replacementsForClass.length > 0) {
434
+ if (canRemoveCommonModuleForFile) {
435
+ filesToRemoveCommonModule.add(file.id);
436
+ }
437
+ // Handle the `@Component({ imports: [...] })` array.
438
+ const importsRemoval = createNgClassImportsArrayRemoval(node, file, typeChecker, canRemoveCommonModuleForFile);
439
+ if (importsRemoval) {
440
+ replacementsForClass.push(importsRemoval);
441
+ }
442
+ ngClassReplacements.push({
443
+ file,
444
+ replacementCount: replacementCountForClass,
445
+ replacements: replacementsForClass,
446
+ });
447
+ filesWithNgClassDeclarations.add(sf);
448
+ }
449
+ });
450
+ }
451
+ const importReplacements = calculateImportReplacements(info, filesWithNgClassDeclarations, filesToRemoveCommonModule);
452
+ return project_paths.confirmAsSerializable({
453
+ ngClassReplacements,
454
+ importReplacements,
455
+ });
456
+ }
457
+ async combine(unitA, unitB) {
458
+ const importReplacements = {};
459
+ for (const unit of [unitA, unitB]) {
460
+ for (const fileIDStr of Object.keys(unit.importReplacements)) {
461
+ const fileID = fileIDStr;
462
+ importReplacements[fileID] = unit.importReplacements[fileID];
463
+ }
464
+ }
465
+ return project_paths.confirmAsSerializable({
466
+ ngClassReplacements: [...unitA.ngClassReplacements, ...unitB.ngClassReplacements],
467
+ importReplacements,
468
+ });
469
+ }
470
+ async globalMeta(combinedData) {
471
+ return project_paths.confirmAsSerializable({
472
+ ngClassReplacements: combinedData.ngClassReplacements,
473
+ importReplacements: combinedData.importReplacements,
474
+ });
475
+ }
476
+ async stats(globalMetadata) {
477
+ const touchedFilesCount = globalMetadata.ngClassReplacements.length;
478
+ const replacementCount = globalMetadata.ngClassReplacements.reduce((acc, cur) => acc + cur.replacementCount, 0);
479
+ return project_paths.confirmAsSerializable({
480
+ touchedFilesCount,
481
+ replacementCount,
482
+ });
483
+ }
484
+ async migrate(globalData) {
485
+ const replacements = [];
486
+ replacements.push(...globalData.ngClassReplacements.flatMap(({ replacements }) => replacements));
487
+ for (const fileIDStr of Object.keys(globalData.importReplacements)) {
488
+ const fileID = fileIDStr;
489
+ const importReplacements = globalData.importReplacements[fileID];
490
+ replacements.push(...importReplacements.addAndRemove);
491
+ }
492
+ return { replacements };
493
+ }
494
+ }
495
+ function prepareTextReplacement(file, replacement, start, end) {
496
+ return new project_paths.Replacement(file, new project_paths.TextUpdate({
497
+ position: start,
498
+ end: end,
499
+ toInsert: replacement,
500
+ }));
501
+ }
502
+
503
+ function migrate(options) {
504
+ return async (tree, context) => {
505
+ await project_paths.runMigrationInDevkit({
506
+ tree,
507
+ getMigration: (fs) => new NgClassMigration({
508
+ migrateSpaceSeparatedKey: options.migrateSpaceSeparatedKey,
509
+ shouldMigrate: (file) => {
510
+ return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
511
+ !/(^|\/)node_modules\//.test(file.rootRelativePath));
512
+ },
513
+ }),
514
+ beforeProgramCreation: (tsconfigPath, stage) => {
515
+ if (stage === project_paths.MigrationStage.Analysis) {
516
+ context.logger.info(`Preparing analysis for: ${tsconfigPath}...`);
517
+ }
518
+ else {
519
+ context.logger.info(`Running migration for: ${tsconfigPath}...`);
520
+ }
521
+ },
522
+ beforeUnitAnalysis: (tsconfigPath) => {
523
+ context.logger.info(`Scanning for ngClass bindings: ${tsconfigPath}...`);
524
+ },
525
+ afterAllAnalyzed: () => {
526
+ context.logger.info(``);
527
+ context.logger.info(`Processing analysis data between targets...`);
528
+ context.logger.info(``);
529
+ },
530
+ afterAnalysisFailure: () => {
531
+ context.logger.error('Migration failed unexpectedly with no analysis data');
532
+ },
533
+ whenDone: ({ touchedFilesCount, replacementCount, }) => {
534
+ context.logger.info('');
535
+ context.logger.info(`Successfully migrated to class from ngClass 🎉`);
536
+ context.logger.info(` -> Migrated ${replacementCount} ngClass to class in ${touchedFilesCount} files.`);
537
+ },
538
+ });
539
+ };
540
+ }
541
+
542
+ exports.migrate = migrate;