@angular/core 21.0.0-next.1 → 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.
- package/fesm2022/_attribute-chunk.mjs +12 -0
- package/fesm2022/_attribute-chunk.mjs.map +1 -0
- package/fesm2022/_debug_node-chunk.mjs +18469 -0
- package/fesm2022/_debug_node-chunk.mjs.map +1 -0
- package/fesm2022/_effect-chunk.mjs +423 -0
- package/fesm2022/_effect-chunk.mjs.map +1 -0
- package/fesm2022/_effect-chunk2.mjs +2951 -0
- package/fesm2022/_effect-chunk2.mjs.map +1 -0
- package/fesm2022/_not_found-chunk.mjs +39 -0
- package/fesm2022/_not_found-chunk.mjs.map +1 -0
- package/fesm2022/_resource-chunk.mjs +378 -0
- package/fesm2022/_resource-chunk.mjs.map +1 -0
- package/fesm2022/_untracked-chunk.mjs +96 -0
- package/fesm2022/_untracked-chunk.mjs.map +1 -0
- package/fesm2022/_weak_ref-chunk.mjs +10 -0
- package/fesm2022/_weak_ref-chunk.mjs.map +1 -0
- package/fesm2022/core.mjs +2499 -4185
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives-di.mjs +23 -0
- package/fesm2022/primitives-di.mjs.map +1 -0
- package/fesm2022/primitives-event-dispatch.mjs +788 -0
- package/fesm2022/primitives-event-dispatch.mjs.map +1 -0
- package/fesm2022/primitives-signals.mjs +187 -0
- package/fesm2022/primitives-signals.mjs.map +1 -0
- package/fesm2022/rxjs-interop.mjs +210 -308
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/testing.mjs +2309 -3170
- package/fesm2022/testing.mjs.map +1 -1
- package/package.json +18 -12
- package/resources/best-practices.md +56 -0
- package/schematics/bundles/add-bootstrap-context-to-server-main.cjs +117 -0
- package/schematics/bundles/application-config-core.cjs +84 -0
- package/schematics/bundles/{apply_import_manager-yycO3l8f.cjs → apply_import_manager-1Zs_gpB6.cjs} +4 -5
- package/schematics/bundles/bootstrap-options-migration.cjs +598 -0
- package/schematics/bundles/cleanup-unused-imports.cjs +9 -13
- package/schematics/bundles/common-to-standalone-migration.cjs +381 -0
- package/schematics/bundles/{compiler_host-DrXTGf_7.cjs → compiler_host-DBwYMlTo.cjs} +10 -11
- package/schematics/bundles/control-flow-migration.cjs +113 -82
- package/schematics/bundles/{imports-26VeX8i-.cjs → imports-DP72APSx.cjs} +5 -1
- package/schematics/bundles/{index-BdH2rlWJ.cjs → index-B7I9sIUx.cjs} +36 -39
- package/schematics/bundles/inject-migration.cjs +148 -70
- package/schematics/bundles/leading_space-D9nQ8UQC.cjs +1 -1
- package/schematics/bundles/{migrate_ts_type_references-BVSg43hf.cjs → migrate_ts_type_references-UGIUl7En.cjs} +500 -24
- package/schematics/bundles/{ng_component_template-DjLc4mdL.cjs → ng_component_template-Dsuq1Lw7.cjs} +4 -5
- package/schematics/bundles/{ng_decorators-CtYwz9Lw.cjs → ng_decorators-DSFlWYQY.cjs} +2 -2
- package/schematics/bundles/ngclass-to-class-migration.cjs +118 -109
- package/schematics/bundles/ngstyle-to-style-migration.cjs +487 -0
- package/schematics/bundles/nodes-B16H9JUd.cjs +1 -1
- package/schematics/bundles/output-migration.cjs +16 -19
- package/schematics/bundles/parse_html-8VLCL37B.cjs +132 -0
- package/schematics/bundles/{project_paths-T_M15e2g.cjs → project_paths-DvD50ouC.cjs} +14 -247
- package/schematics/bundles/project_tsconfig_paths-CDVxT6Ov.cjs +90 -0
- package/schematics/bundles/property_name-BBwFuqMe.cjs +1 -1
- package/schematics/bundles/route-lazy-loading.cjs +54 -26
- package/schematics/bundles/router-current-navigation.cjs +7 -18
- package/schematics/bundles/router-last-successful-navigation.cjs +7 -18
- package/schematics/bundles/router-testing-module-migration.cjs +502 -0
- package/schematics/bundles/self-closing-tags-migration.cjs +14 -17
- package/schematics/bundles/signal-input-migration.cjs +93 -29
- package/schematics/bundles/signal-queries-migration.cjs +22 -25
- package/schematics/bundles/signals.cjs +10 -13
- package/schematics/bundles/standalone-migration.cjs +135 -102
- package/schematics/bundles/{symbol-VPWguRxr.cjs → symbol-BObKoqes.cjs} +3 -2
- package/schematics/collection.json +17 -0
- package/schematics/migrations/common-to-standalone-migration/schema.json +14 -0
- package/schematics/migrations/ngclass-to-class-migration/schema.json +2 -2
- package/schematics/migrations/ngstyle-to-style-migration/schema.json +20 -0
- package/schematics/migrations/router-testing-module-migration/schema.json +14 -0
- package/schematics/migrations.json +16 -2
- package/{api.d.d.ts → types/_api-chunk.d.ts} +9 -6
- package/{chrome_dev_tools_performance.d.d.ts → types/_chrome_dev_tools_performance-chunk.d.ts} +25 -15
- package/{discovery.d.d.ts → types/_discovery-chunk.d.ts} +130 -75
- package/{effect.d.d.ts → types/_effect-chunk.d.ts} +3 -4
- package/{event_dispatcher.d.d.ts → types/_event_dispatcher-chunk.d.ts} +2 -2
- package/{graph.d.d.ts → types/_formatter-chunk.d.ts} +40 -7
- package/{weak_ref.d.d.ts → types/_weak_ref-chunk.d.ts} +2 -2
- package/{index.d.ts → types/core.d.ts} +233 -298
- package/{primitives/di/index.d.ts → types/primitives-di.d.ts} +2 -2
- package/{primitives/event-dispatch/index.d.ts → types/primitives-event-dispatch.d.ts} +4 -4
- package/{primitives/signals/index.d.ts → types/primitives-signals.d.ts} +7 -8
- package/{rxjs-interop/index.d.ts → types/rxjs-interop.d.ts} +8 -6
- package/{testing/index.d.ts → types/testing.d.ts} +7 -7
- package/fesm2022/attribute.mjs +0 -24
- package/fesm2022/attribute.mjs.map +0 -1
- package/fesm2022/debug_node.mjs +0 -31829
- package/fesm2022/debug_node.mjs.map +0 -1
- package/fesm2022/effect.mjs +0 -142
- package/fesm2022/effect.mjs.map +0 -1
- package/fesm2022/not_found.mjs +0 -56
- package/fesm2022/not_found.mjs.map +0 -1
- package/fesm2022/primitives/di.mjs +0 -23
- package/fesm2022/primitives/di.mjs.map +0 -1
- package/fesm2022/primitives/event-dispatch.mjs +0 -1622
- package/fesm2022/primitives/event-dispatch.mjs.map +0 -1
- package/fesm2022/primitives/signals.mjs +0 -89
- package/fesm2022/primitives/signals.mjs.map +0 -1
- package/fesm2022/resource.mjs +0 -624
- package/fesm2022/resource.mjs.map +0 -1
- package/fesm2022/root_effect_scheduler.mjs +0 -4001
- package/fesm2022/root_effect_scheduler.mjs.map +0 -1
- package/fesm2022/signal.mjs +0 -560
- package/fesm2022/signal.mjs.map +0 -1
- package/fesm2022/weak_ref.mjs +0 -12
- package/fesm2022/weak_ref.mjs.map +0 -1
- package/schematics/bundles/index-jjHOgYYs.cjs +0 -22074
- package/schematics/bundles/parse_html-CXR8hziE.cjs +0 -41
- package/schematics/bundles/project_tsconfig_paths-D7xzGqRi.cjs +0 -51085
|
@@ -0,0 +1,487 @@
|
|
|
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 ngStyleStr = 'NgStyle';
|
|
27
|
+
const commonModuleStr = '@angular/common';
|
|
28
|
+
const commonModuleImportsStr = 'CommonModule';
|
|
29
|
+
function migrateNgStyleBindings(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 NgStyleCollector(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 `NgStyle` from a component's `imports` array.
|
|
55
|
+
* Uses ReflectionHost + PartialEvaluator for robust AST analysis.
|
|
56
|
+
*/
|
|
57
|
+
function createNgStyleImportsArrayRemoval(classNode, file, typeChecker, removeCommonModule) {
|
|
58
|
+
const reflector = new migrations.TypeScriptReflectionHost(typeChecker);
|
|
59
|
+
const decorators = reflector.getDecoratorsOfDeclaration(classNode);
|
|
60
|
+
if (!decorators) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const componentDecorator = decorators.find((decorator) => decorator.name === 'Component');
|
|
64
|
+
if (!componentDecorator?.node) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const decoratorNode = componentDecorator.node;
|
|
68
|
+
if (!ts.isDecorator(decoratorNode) ||
|
|
69
|
+
!ts.isCallExpression(decoratorNode.expression) ||
|
|
70
|
+
decoratorNode.expression.arguments.length === 0 ||
|
|
71
|
+
!ts.isObjectLiteralExpression(decoratorNode.expression.arguments[0])) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const metadata = decoratorNode.expression.arguments[0];
|
|
75
|
+
const importsProperty = metadata.properties.find((p) => ts.isPropertyAssignment(p) && p.name?.getText() === 'imports');
|
|
76
|
+
if (!importsProperty || !ts.isArrayLiteralExpression(importsProperty.initializer)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const importsArray = importsProperty.initializer;
|
|
80
|
+
const elementsToRemove = new Set([ngStyleStr]);
|
|
81
|
+
if (removeCommonModule) {
|
|
82
|
+
elementsToRemove.add(commonModuleImportsStr);
|
|
83
|
+
}
|
|
84
|
+
const originalElements = importsArray.elements;
|
|
85
|
+
const filteredElements = originalElements.filter((el) => !ts.isIdentifier(el) || !elementsToRemove.has(el.text));
|
|
86
|
+
if (filteredElements.length === originalElements.length) {
|
|
87
|
+
return null; // No changes needed.
|
|
88
|
+
}
|
|
89
|
+
// If the array becomes empty, remove the entire `imports` property.
|
|
90
|
+
if (filteredElements.length === 0) {
|
|
91
|
+
const removalRange = getPropertyRemovalRange(importsProperty);
|
|
92
|
+
return new project_paths.Replacement(file, new project_paths.TextUpdate({
|
|
93
|
+
position: removalRange.start,
|
|
94
|
+
end: removalRange.end,
|
|
95
|
+
toInsert: '',
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
const printer = ts.createPrinter();
|
|
99
|
+
const newArray = ts.factory.updateArrayLiteralExpression(importsArray, filteredElements);
|
|
100
|
+
const newText = printer.printNode(ts.EmitHint.Unspecified, newArray, classNode.getSourceFile());
|
|
101
|
+
return new project_paths.Replacement(file, new project_paths.TextUpdate({
|
|
102
|
+
position: importsArray.getStart(),
|
|
103
|
+
end: importsArray.getEnd(),
|
|
104
|
+
toInsert: newText,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Calculates the removal range for a property in an object literal,
|
|
109
|
+
* including the trailing comma if it's not the last property.
|
|
110
|
+
*/
|
|
111
|
+
function getPropertyRemovalRange(property) {
|
|
112
|
+
const parent = property.parent;
|
|
113
|
+
if (!ts.isObjectLiteralExpression(parent)) {
|
|
114
|
+
return { start: property.getStart(), end: property.getEnd() };
|
|
115
|
+
}
|
|
116
|
+
const properties = parent.properties;
|
|
117
|
+
const propertyIndex = properties.indexOf(property);
|
|
118
|
+
const end = property.getEnd();
|
|
119
|
+
if (propertyIndex < properties.length - 1) {
|
|
120
|
+
const nextProperty = properties[propertyIndex + 1];
|
|
121
|
+
return { start: property.getStart(), end: nextProperty.getStart() };
|
|
122
|
+
}
|
|
123
|
+
return { start: property.getStart(), end };
|
|
124
|
+
}
|
|
125
|
+
function calculateImportReplacements(info, sourceFiles, filesToRemoveCommonModule) {
|
|
126
|
+
const importReplacements = {};
|
|
127
|
+
const importManager = new migrations.ImportManager();
|
|
128
|
+
for (const sf of sourceFiles) {
|
|
129
|
+
const file = project_paths.projectFile(sf, info);
|
|
130
|
+
// Always remove NgStyle if it's imported directly.
|
|
131
|
+
importManager.removeImport(sf, ngStyleStr, commonModuleStr);
|
|
132
|
+
// Conditionally remove CommonModule if it's no longer needed.
|
|
133
|
+
if (filesToRemoveCommonModule.has(file.id)) {
|
|
134
|
+
importManager.removeImport(sf, commonModuleImportsStr, commonModuleStr);
|
|
135
|
+
}
|
|
136
|
+
const addRemove = [];
|
|
137
|
+
apply_import_manager.applyImportManagerChanges(importManager, addRemove, [sf], info);
|
|
138
|
+
if (addRemove.length > 0) {
|
|
139
|
+
importReplacements[file.id] = {
|
|
140
|
+
add: [],
|
|
141
|
+
addAndRemove: addRemove,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return importReplacements;
|
|
146
|
+
}
|
|
147
|
+
function replaceTemplate(template, replaceValue, start, end, offset) {
|
|
148
|
+
return template.slice(0, start + offset) + replaceValue + template.slice(end + offset);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Visitor class that scans Angular templates and collects replacements
|
|
152
|
+
* for [ngStyle] bindings that use static object literals.
|
|
153
|
+
*/
|
|
154
|
+
class NgStyleCollector extends compiler.RecursiveVisitor {
|
|
155
|
+
originalTemplate;
|
|
156
|
+
replacements = [];
|
|
157
|
+
isNgStyleImported = true; // Default to true (permissive)
|
|
158
|
+
constructor(originalTemplate, componentNode, typeChecker) {
|
|
159
|
+
super();
|
|
160
|
+
this.originalTemplate = originalTemplate;
|
|
161
|
+
// If we have enough information, check if NgStyle is actually imported.
|
|
162
|
+
// If not, we can confidently disable the migration for this component.
|
|
163
|
+
if (componentNode && typeChecker) {
|
|
164
|
+
const imports$1 = imports.getImportSpecifiers(componentNode.getSourceFile(), commonModuleStr, [
|
|
165
|
+
ngStyleStr,
|
|
166
|
+
commonModuleImportsStr,
|
|
167
|
+
]);
|
|
168
|
+
if (imports$1.length === 0) {
|
|
169
|
+
this.isNgStyleImported = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
visitElement(element, config) {
|
|
174
|
+
// If NgStyle is not imported, do not attempt to migrate.
|
|
175
|
+
if (!this.isNgStyleImported) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
for (const attr of element.attrs) {
|
|
179
|
+
if (attr.name !== '[ngStyle]' && attr.name !== 'ngStyle') {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (attr.name === '[ngStyle]' && attr.valueSpan) {
|
|
183
|
+
const expr = this.originalTemplate.slice(attr.valueSpan.start.offset, attr.valueSpan.end.offset);
|
|
184
|
+
const staticMatch = parseStaticObjectLiteral(expr);
|
|
185
|
+
if (staticMatch === null) {
|
|
186
|
+
if (config.bestEffortMode && !isObjectLiteralSyntax(expr.trim())) {
|
|
187
|
+
const keyReplacement = this.originalTemplate
|
|
188
|
+
.slice(attr.sourceSpan.start.offset, attr.valueSpan.start.offset)
|
|
189
|
+
.replace('[ngStyle]', '[style]');
|
|
190
|
+
this.replacements.push({
|
|
191
|
+
start: attr.sourceSpan.start.offset,
|
|
192
|
+
end: attr.valueSpan.start.offset,
|
|
193
|
+
replacement: keyReplacement,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
let replacement;
|
|
199
|
+
if (staticMatch.length === 0) {
|
|
200
|
+
replacement = '';
|
|
201
|
+
}
|
|
202
|
+
else if (staticMatch.length === 1) {
|
|
203
|
+
const { key, value } = staticMatch[0];
|
|
204
|
+
// Special case: If the key is an empty string, use [style]=""
|
|
205
|
+
if (key === '') {
|
|
206
|
+
replacement = '';
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Normal single condition: use [style.styleName]="condition"
|
|
210
|
+
replacement = `[style.${key}]="${value}"`;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
replacement = `[style]="${expr}"`;
|
|
215
|
+
}
|
|
216
|
+
this.replacements.push({
|
|
217
|
+
start: attr.sourceSpan.start.offset,
|
|
218
|
+
end: attr.sourceSpan.end.offset,
|
|
219
|
+
replacement,
|
|
220
|
+
});
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (attr.name === 'ngStyle' && attr.value) {
|
|
224
|
+
this.replacements.push({
|
|
225
|
+
start: attr.sourceSpan.start.offset,
|
|
226
|
+
end: attr.sourceSpan.end.offset,
|
|
227
|
+
replacement: `style="${attr.value}"`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return super.visitElement(element, config);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function parseStaticObjectLiteral(expr) {
|
|
235
|
+
const trimmedExpr = expr.trim();
|
|
236
|
+
if (trimmedExpr === '{}' || trimmedExpr === '[]') {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
if (!isObjectLiteralSyntax(trimmedExpr)) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const objectLiteral = parseAsObjectLiteral(trimmedExpr);
|
|
243
|
+
if (!objectLiteral) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return extractStyleBindings(objectLiteral);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Validates basic object literal syntax
|
|
250
|
+
*/
|
|
251
|
+
function isObjectLiteralSyntax(expr) {
|
|
252
|
+
return expr.startsWith('{') && expr.endsWith('}');
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Parses expression as TypeScript object literal
|
|
256
|
+
*/
|
|
257
|
+
function parseAsObjectLiteral(expr) {
|
|
258
|
+
const sourceFile = ts.createSourceFile('temp.ts', `const obj = ${expr}`, ts.ScriptTarget.Latest, true);
|
|
259
|
+
const variableStatement = sourceFile.statements[0];
|
|
260
|
+
if (!ts.isVariableStatement(variableStatement)) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const declaration = variableStatement.declarationList.declarations[0];
|
|
264
|
+
if (!declaration.initializer || !ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
return declaration.initializer;
|
|
268
|
+
}
|
|
269
|
+
function extractStyleBindings(objectLiteral) {
|
|
270
|
+
const result = [];
|
|
271
|
+
for (const property of objectLiteral.properties) {
|
|
272
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
else if (ts.isPropertyAssignment(property)) {
|
|
276
|
+
const keyText = extractPropertyKey(property.name);
|
|
277
|
+
const valueText = extractPropertyValue(property.initializer);
|
|
278
|
+
if (keyText === '' && valueText) {
|
|
279
|
+
result.push({ key: '', value: valueText });
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (!keyText || !valueText) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
result.push({ key: keyText, value: valueText });
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Extracts text from property key (name)
|
|
295
|
+
*/
|
|
296
|
+
function extractPropertyKey(name) {
|
|
297
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
|
298
|
+
return name.text;
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Extracts text from property value
|
|
304
|
+
*/
|
|
305
|
+
function extractPropertyValue(initializer) {
|
|
306
|
+
// String literals: 'value' or "value"
|
|
307
|
+
if (ts.isStringLiteral(initializer)) {
|
|
308
|
+
return `'${initializer.text}'`;
|
|
309
|
+
}
|
|
310
|
+
// Numeric literals: 42, 3.14
|
|
311
|
+
if (ts.isNumericLiteral(initializer) || ts.isIdentifier(initializer)) {
|
|
312
|
+
return initializer.text;
|
|
313
|
+
}
|
|
314
|
+
// Boolean and null keywords
|
|
315
|
+
if (initializer.kind === ts.SyntaxKind.TrueKeyword) {
|
|
316
|
+
return 'true';
|
|
317
|
+
}
|
|
318
|
+
if (initializer.kind === ts.SyntaxKind.FalseKeyword) {
|
|
319
|
+
return 'false';
|
|
320
|
+
}
|
|
321
|
+
if (initializer.kind === ts.SyntaxKind.NullKeyword) {
|
|
322
|
+
return 'null';
|
|
323
|
+
}
|
|
324
|
+
return initializer.getText();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
class NgStyleMigration extends project_paths.TsurgeFunnelMigration {
|
|
328
|
+
config;
|
|
329
|
+
constructor(config = {}) {
|
|
330
|
+
super();
|
|
331
|
+
this.config = config;
|
|
332
|
+
}
|
|
333
|
+
processTemplate(template, node, file, info, typeChecker) {
|
|
334
|
+
const { migrated, changed, replacementCount, canRemoveCommonModule } = migrateNgStyleBindings(template.content, this.config, node, typeChecker);
|
|
335
|
+
if (!changed) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
const fileToMigrate = template.inline
|
|
339
|
+
? file
|
|
340
|
+
: project_paths.projectFile(template.filePath, info);
|
|
341
|
+
const end = template.start + template.content.length;
|
|
342
|
+
return {
|
|
343
|
+
replacements: [prepareTextReplacement(fileToMigrate, migrated, template.start, end)],
|
|
344
|
+
replacementCount,
|
|
345
|
+
canRemoveCommonModule,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
async analyze(info) {
|
|
349
|
+
const { sourceFiles, program } = info;
|
|
350
|
+
const typeChecker = program.getTypeChecker();
|
|
351
|
+
const ngStyleReplacements = [];
|
|
352
|
+
const filesWithNgStyleDeclarations = new Set();
|
|
353
|
+
const filesToRemoveCommonModule = new Set();
|
|
354
|
+
for (const sf of sourceFiles) {
|
|
355
|
+
ts.forEachChild(sf, (node) => {
|
|
356
|
+
if (!ts.isClassDeclaration(node)) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const file = project_paths.projectFile(sf, info);
|
|
360
|
+
if (this.config.shouldMigrate && !this.config.shouldMigrate(file)) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const templateVisitor = new ng_component_template.NgComponentTemplateVisitor(typeChecker);
|
|
364
|
+
templateVisitor.visitNode(node);
|
|
365
|
+
const replacementsForStyle = [];
|
|
366
|
+
let replacementCountForStyle = 0;
|
|
367
|
+
let canRemoveCommonModuleForFile = true;
|
|
368
|
+
for (const template of templateVisitor.resolvedTemplates) {
|
|
369
|
+
const result = this.processTemplate(template, node, file, info, typeChecker);
|
|
370
|
+
if (result) {
|
|
371
|
+
replacementsForStyle.push(...result.replacements);
|
|
372
|
+
replacementCountForStyle += result.replacementCount;
|
|
373
|
+
if (!result.canRemoveCommonModule) {
|
|
374
|
+
canRemoveCommonModuleForFile = false;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (replacementsForStyle.length > 0) {
|
|
379
|
+
if (canRemoveCommonModuleForFile) {
|
|
380
|
+
filesToRemoveCommonModule.add(file.id);
|
|
381
|
+
}
|
|
382
|
+
// Handle the `@Component({ imports: [...] })` array.
|
|
383
|
+
const importsRemoval = createNgStyleImportsArrayRemoval(node, file, typeChecker, canRemoveCommonModuleForFile);
|
|
384
|
+
if (importsRemoval) {
|
|
385
|
+
replacementsForStyle.push(importsRemoval);
|
|
386
|
+
}
|
|
387
|
+
ngStyleReplacements.push({
|
|
388
|
+
file,
|
|
389
|
+
replacementCount: replacementCountForStyle,
|
|
390
|
+
replacements: replacementsForStyle,
|
|
391
|
+
});
|
|
392
|
+
filesWithNgStyleDeclarations.add(sf);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
const importReplacements = calculateImportReplacements(info, filesWithNgStyleDeclarations, filesToRemoveCommonModule);
|
|
397
|
+
return project_paths.confirmAsSerializable({
|
|
398
|
+
ngStyleReplacements,
|
|
399
|
+
importReplacements,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
async combine(unitA, unitB) {
|
|
403
|
+
const importReplacements = {};
|
|
404
|
+
for (const unit of [unitA, unitB]) {
|
|
405
|
+
for (const fileIDStr of Object.keys(unit.importReplacements)) {
|
|
406
|
+
const fileID = fileIDStr;
|
|
407
|
+
importReplacements[fileID] = unit.importReplacements[fileID];
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return project_paths.confirmAsSerializable({
|
|
411
|
+
ngStyleReplacements: [...unitA.ngStyleReplacements, ...unitB.ngStyleReplacements],
|
|
412
|
+
importReplacements,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
async globalMeta(combinedData) {
|
|
416
|
+
return project_paths.confirmAsSerializable({
|
|
417
|
+
ngStyleReplacements: combinedData.ngStyleReplacements,
|
|
418
|
+
importReplacements: combinedData.importReplacements,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
async stats(globalMetadata) {
|
|
422
|
+
const touchedFilesCount = globalMetadata.ngStyleReplacements.length;
|
|
423
|
+
const replacementCount = globalMetadata.ngStyleReplacements.reduce((acc, cur) => acc + cur.replacementCount, 0);
|
|
424
|
+
return project_paths.confirmAsSerializable({
|
|
425
|
+
touchedFilesCount,
|
|
426
|
+
replacementCount,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
async migrate(globalData) {
|
|
430
|
+
const replacements = [];
|
|
431
|
+
replacements.push(...globalData.ngStyleReplacements.flatMap(({ replacements }) => replacements));
|
|
432
|
+
for (const fileIDStr of Object.keys(globalData.importReplacements)) {
|
|
433
|
+
const fileID = fileIDStr;
|
|
434
|
+
const importReplacements = globalData.importReplacements[fileID];
|
|
435
|
+
replacements.push(...importReplacements.addAndRemove);
|
|
436
|
+
}
|
|
437
|
+
return { replacements };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function prepareTextReplacement(file, replacement, start, end) {
|
|
441
|
+
return new project_paths.Replacement(file, new project_paths.TextUpdate({
|
|
442
|
+
position: start,
|
|
443
|
+
end: end,
|
|
444
|
+
toInsert: replacement,
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function migrate(options) {
|
|
449
|
+
return async (tree, context) => {
|
|
450
|
+
await project_paths.runMigrationInDevkit({
|
|
451
|
+
tree,
|
|
452
|
+
getMigration: (fs) => new NgStyleMigration({
|
|
453
|
+
bestEffortMode: options.bestEffortMode,
|
|
454
|
+
shouldMigrate: (file) => {
|
|
455
|
+
return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
|
|
456
|
+
!/(^|\/)node_modules\//.test(file.rootRelativePath));
|
|
457
|
+
},
|
|
458
|
+
}),
|
|
459
|
+
beforeProgramCreation: (tsconfigPath, stage) => {
|
|
460
|
+
if (stage === project_paths.MigrationStage.Analysis) {
|
|
461
|
+
context.logger.info(`Preparing analysis for: ${tsconfigPath}...`);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
context.logger.info(`Running migration for: ${tsconfigPath}...`);
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
beforeUnitAnalysis: (tsconfigPath) => {
|
|
468
|
+
context.logger.info(`Scanning for component tags: ${tsconfigPath}...`);
|
|
469
|
+
},
|
|
470
|
+
afterAllAnalyzed: () => {
|
|
471
|
+
context.logger.info(``);
|
|
472
|
+
context.logger.info(`Processing analysis data between targets...`);
|
|
473
|
+
context.logger.info(``);
|
|
474
|
+
},
|
|
475
|
+
afterAnalysisFailure: () => {
|
|
476
|
+
context.logger.error('Migration failed unexpectedly with no analysis data');
|
|
477
|
+
},
|
|
478
|
+
whenDone: ({ touchedFilesCount, replacementCount, }) => {
|
|
479
|
+
context.logger.info('');
|
|
480
|
+
context.logger.info(`Successfully migrated to style bindings from ngStyle 🎉`);
|
|
481
|
+
context.logger.info(` -> Migrated ${replacementCount} ngStyle to style bindings in ${touchedFilesCount} files.`);
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
exports.migrate = migrate;
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v21.0.0-next.
|
|
3
|
+
* @license Angular v21.0.0-next.10
|
|
4
4
|
* (c) 2010-2025 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
var ts = require('typescript');
|
|
10
|
-
require('
|
|
11
|
-
var
|
|
12
|
-
var index$1 = require('./index-jjHOgYYs.cjs');
|
|
13
|
-
require('path');
|
|
10
|
+
require('@angular/compiler-cli');
|
|
11
|
+
var migrations = require('@angular/compiler-cli/private/migrations');
|
|
14
12
|
require('node:path');
|
|
15
|
-
var project_paths = require('./project_paths-
|
|
16
|
-
var
|
|
17
|
-
var
|
|
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 index = require('./index-B7I9sIUx.cjs');
|
|
18
17
|
require('@angular-devkit/core');
|
|
19
18
|
require('node:path/posix');
|
|
20
|
-
require('fs');
|
|
21
|
-
require('module');
|
|
22
|
-
require('url');
|
|
23
19
|
require('@angular-devkit/schematics');
|
|
20
|
+
require('./project_tsconfig_paths-CDVxT6Ov.cjs');
|
|
24
21
|
|
|
25
22
|
function isOutputDeclarationEligibleForMigration(node) {
|
|
26
23
|
return (node.initializer !== undefined &&
|
|
@@ -67,7 +64,7 @@ function isOutputDeclaration(node, reflector, dtsReader) {
|
|
|
67
64
|
node.parent.name === undefined) {
|
|
68
65
|
return false;
|
|
69
66
|
}
|
|
70
|
-
const ref = new
|
|
67
|
+
const ref = new migrations.Reference(node.parent);
|
|
71
68
|
const directiveMeta = dtsReader.getDirectiveMetadata(ref);
|
|
72
69
|
return !!directiveMeta?.outputs.getByClassPropertyName(node.name.text);
|
|
73
70
|
}
|
|
@@ -84,7 +81,7 @@ function getTargetPropertyDeclaration(targetSymbol) {
|
|
|
84
81
|
/** Returns Angular `@Output` decorator or null when a given property declaration is not an @Output */
|
|
85
82
|
function getOutputDecorator(node, reflector) {
|
|
86
83
|
const decorators = reflector.getDecoratorsOfDeclaration(node);
|
|
87
|
-
const ngDecorators = decorators !== null ?
|
|
84
|
+
const ngDecorators = decorators !== null ? migrations.getAngularDecorators(decorators, ['Output'], /* isCore */ false) : [];
|
|
88
85
|
return ngDecorators.length > 0 ? ngDecorators[0] : null;
|
|
89
86
|
}
|
|
90
87
|
// THINK: this utility + type is not specific to @Output, really, maybe move it to tsurge?
|
|
@@ -114,7 +111,7 @@ function checkNonTsReferenceAccessesField(ref, fieldName) {
|
|
|
114
111
|
if (ref.from.read !== readFromPath) {
|
|
115
112
|
return null;
|
|
116
113
|
}
|
|
117
|
-
if (!(parentRead instanceof
|
|
114
|
+
if (!(parentRead instanceof compiler.PropertyRead) || parentRead.name !== fieldName) {
|
|
118
115
|
return null;
|
|
119
116
|
}
|
|
120
117
|
return parentRead;
|
|
@@ -170,7 +167,7 @@ function calculateDeclarationReplacement(info, node, aliasParam) {
|
|
|
170
167
|
function calculateImportReplacements(info, sourceFiles) {
|
|
171
168
|
const importReplacements = {};
|
|
172
169
|
for (const sf of sourceFiles) {
|
|
173
|
-
const importManager = new
|
|
170
|
+
const importManager = new migrations.ImportManager();
|
|
174
171
|
const addOnly = [];
|
|
175
172
|
const addRemove = [];
|
|
176
173
|
const file = project_paths.projectFile(sf, info);
|
|
@@ -205,7 +202,7 @@ function calculateCompleteCallReplacement(info, node) {
|
|
|
205
202
|
function calculatePipeCallReplacement(info, node) {
|
|
206
203
|
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
207
204
|
const sf = node.getSourceFile();
|
|
208
|
-
const importManager = new
|
|
205
|
+
const importManager = new migrations.ImportManager();
|
|
209
206
|
const outputToObservableIdent = importManager.addImport({
|
|
210
207
|
requestedFile: sf,
|
|
211
208
|
exportModuleSpecifier: '@angular/core/rxjs-interop',
|
|
@@ -256,9 +253,9 @@ class OutputMigration extends project_paths.TsurgeFunnelMigration {
|
|
|
256
253
|
let problematicDeclarationCount = 0;
|
|
257
254
|
const filesWithOutputDeclarations = new Set();
|
|
258
255
|
const checker = program.getTypeChecker();
|
|
259
|
-
const reflector = new
|
|
260
|
-
const dtsReader = new
|
|
261
|
-
const evaluator = new
|
|
256
|
+
const reflector = new migrations.TypeScriptReflectionHost(checker);
|
|
257
|
+
const dtsReader = new migrations.DtsMetadataReader(checker, reflector);
|
|
258
|
+
const evaluator = new migrations.PartialEvaluator(reflector, checker, null);
|
|
262
259
|
const resourceLoader = info.ngCompiler?.['resourceManager'] ?? null;
|
|
263
260
|
// Pre-analyze the program and get access to the template type checker.
|
|
264
261
|
// If we are processing a non-Angular target, there is no template info.
|