@angular/core 20.2.3 → 21.0.0-next.1

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