@angular/core 21.2.0 → 22.0.0-next.0
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 +1 -1
- package/fesm2022/_debug_node-chunk.mjs +613 -523
- package/fesm2022/_debug_node-chunk.mjs.map +1 -1
- package/fesm2022/_effect-chunk.mjs +1 -1
- package/fesm2022/_not_found-chunk.mjs +1 -1
- package/fesm2022/{_effect-chunk2.mjs → _pending_tasks-chunk.mjs} +31 -31
- package/fesm2022/_pending_tasks-chunk.mjs.map +1 -0
- package/fesm2022/_resource-chunk.mjs +92 -30
- package/fesm2022/_resource-chunk.mjs.map +1 -1
- package/fesm2022/_untracked-chunk.mjs +1 -1
- package/fesm2022/_weak_ref-chunk.mjs +1 -1
- package/fesm2022/core.mjs +5 -5
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives-di.mjs +1 -1
- package/fesm2022/primitives-event-dispatch.mjs +1 -1
- package/fesm2022/primitives-signals.mjs +1 -1
- package/fesm2022/rxjs-interop.mjs +2 -2
- package/fesm2022/testing.mjs +14 -2
- package/fesm2022/testing.mjs.map +1 -1
- package/package.json +3 -3
- package/schematics/bundles/apply_import_manager-CxA_YYgB.cjs +1 -1
- package/schematics/bundles/change-detection-eager.cjs +155 -0
- package/schematics/bundles/cleanup-unused-imports.cjs +1 -1
- package/schematics/bundles/common-to-standalone-migration.cjs +4 -4
- package/schematics/bundles/compiler_host-CY14HvaP.cjs +1 -1
- package/schematics/bundles/control-flow-migration.cjs +1 -1
- package/schematics/bundles/http-xhr-backend.cjs +118 -0
- package/schematics/bundles/{imports-CVmcbVA9.cjs → imports-CKV-ITqD.cjs} +4 -3
- package/schematics/bundles/index-BtLcQH8g.cjs +1 -1
- package/schematics/bundles/inject-migration.cjs +3 -3
- package/schematics/bundles/leading_space-BTPRV0wu.cjs +1 -1
- package/schematics/bundles/migrate_ts_type_references-MWoZx-Cb.cjs +1 -1
- package/schematics/bundles/{ng_component_template-BOuKAnQd.cjs → ng_component_template-DPAF1aEA.cjs} +2 -2
- package/schematics/bundles/{ng_decorators-DYy6II6x.cjs → ng_decorators-IVztR9rk.cjs} +2 -2
- package/schematics/bundles/ngclass-to-class-migration.cjs +4 -4
- package/schematics/bundles/ngstyle-to-style-migration.cjs +4 -4
- package/schematics/bundles/nodes-ZSQ7WZRB.cjs +1 -1
- package/schematics/bundles/output-migration.cjs +1 -1
- package/schematics/bundles/parse_html-C8eKA9px.cjs +1 -1
- package/schematics/bundles/project_paths-D2V-Uh2L.cjs +1 -1
- package/schematics/bundles/project_tsconfig_paths-DkkMibv-.cjs +1 -1
- package/schematics/bundles/property_name-BCpALNpZ.cjs +1 -1
- package/schematics/bundles/route-lazy-loading.cjs +4 -16
- package/schematics/bundles/router-testing-module-migration.cjs +1 -1
- package/schematics/bundles/self-closing-tags-migration.cjs +4 -4
- package/schematics/bundles/signal-input-migration.cjs +1 -1
- package/schematics/bundles/signal-queries-migration.cjs +1 -1
- package/schematics/bundles/signals.cjs +1 -1
- package/schematics/bundles/standalone-migration.cjs +20 -6
- package/schematics/collection.json +5 -5
- package/schematics/migrations.json +9 -30
- package/types/_api-chunk.d.ts +31 -4
- package/types/_chrome_dev_tools_performance-chunk.d.ts +1 -1
- package/types/_discovery-chunk.d.ts +2 -2
- package/types/_effect-chunk.d.ts +1 -1
- package/types/_event_dispatcher-chunk.d.ts +1 -1
- package/types/_formatter-chunk.d.ts +1 -1
- package/types/_weak_ref-chunk.d.ts +1 -1
- package/types/core.d.ts +44 -14
- package/types/primitives-di.d.ts +1 -1
- package/types/primitives-event-dispatch.d.ts +1 -1
- package/types/primitives-signals.d.ts +1 -1
- package/types/rxjs-interop.d.ts +1 -1
- package/types/testing.d.ts +6 -1
- package/fesm2022/_effect-chunk2.mjs.map +0 -1
- package/schematics/bundles/add-bootstrap-context-to-server-main.cjs +0 -117
- package/schematics/bundles/application-config-core.cjs +0 -84
- package/schematics/bundles/bootstrap-options-migration.cjs +0 -644
- package/schematics/bundles/router-current-navigation.cjs +0 -103
- package/schematics/bundles/router-last-successful-navigation.cjs +0 -103
- package/schematics/bundles/symbol-DZeHSR-V.cjs +0 -26
- /package/schematics/{migrations → ng-generate}/common-to-standalone-migration/schema.json +0 -0
- /package/schematics/{migrations → ng-generate}/control-flow-migration/schema.json +0 -0
- /package/schematics/{migrations → ng-generate}/ngclass-to-class-migration/schema.json +0 -0
- /package/schematics/{migrations → ng-generate}/ngstyle-to-style-migration/schema.json +0 -0
- /package/schematics/{migrations → ng-generate}/router-testing-module-migration/schema.json +0 -0
|
@@ -1,644 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/**
|
|
3
|
-
* @license Angular v21.2.0
|
|
4
|
-
* (c) 2010-2026 Google LLC. https://angular.dev/
|
|
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-D2V-Uh2L.cjs');
|
|
12
|
-
require('@angular/compiler-cli');
|
|
13
|
-
var migrations = require('@angular/compiler-cli/private/migrations');
|
|
14
|
-
var ts = require('typescript');
|
|
15
|
-
require('node:path');
|
|
16
|
-
var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs');
|
|
17
|
-
var property_name = require('./property_name-BCpALNpZ.cjs');
|
|
18
|
-
var imports = require('./imports-CVmcbVA9.cjs');
|
|
19
|
-
var symbol = require('./symbol-DZeHSR-V.cjs');
|
|
20
|
-
require('@angular-devkit/schematics');
|
|
21
|
-
require('./project_tsconfig_paths-DkkMibv-.cjs');
|
|
22
|
-
|
|
23
|
-
const CORE_PACKAGE = '@angular/core';
|
|
24
|
-
const PROVIDE_ZONE_CHANGE_DETECTION = 'provideZoneChangeDetection';
|
|
25
|
-
const ZONE_CD_PROVIDER = `${PROVIDE_ZONE_CHANGE_DETECTION}()`;
|
|
26
|
-
const SAFE_TO_REMOVE_OPTIONS = [
|
|
27
|
-
'ignoreChangesOutsideZone',
|
|
28
|
-
'ngZoneRunCoalescing',
|
|
29
|
-
'ngZoneEventCoalescing',
|
|
30
|
-
];
|
|
31
|
-
const BOOTSTRAP_OPTIONS = ['ngZone', ...SAFE_TO_REMOVE_OPTIONS];
|
|
32
|
-
class BootstrapOptionsMigration extends project_paths.TsurgeFunnelMigration {
|
|
33
|
-
async analyze(info) {
|
|
34
|
-
let replacements = [];
|
|
35
|
-
const importManager = new migrations.ImportManager();
|
|
36
|
-
for (const sourceFile of info.sourceFiles) {
|
|
37
|
-
// We need to migration either
|
|
38
|
-
// * `bootstrapApplication(App)
|
|
39
|
-
// * `platformBrowser().bootstrapModule(AppModule)`
|
|
40
|
-
// * `platformBrowserDynamic().bootstrapModule(AppModule)`
|
|
41
|
-
// * `TestBed.initTestEnvironment([AppModule], platformBrowserTesting())`
|
|
42
|
-
// * `getTestBed.initTestEnvironment([AppModule], platformBrowserTesting())`
|
|
43
|
-
const specifiers = getSpecifiers(sourceFile);
|
|
44
|
-
// If none of the imports related to bootstraping are present, we can skip the file.
|
|
45
|
-
if (specifiers === null)
|
|
46
|
-
continue;
|
|
47
|
-
const { bootstrapAppSpecifier, testBedSpecifier, createApplicationSpecifier, getTestBedSpecifier, } = specifiers;
|
|
48
|
-
const typeChecker = info.program.getTypeChecker();
|
|
49
|
-
const isCreateApplicationNode = (node) => {
|
|
50
|
-
return (ts.isCallExpression(node) &&
|
|
51
|
-
createApplicationSpecifier !== null &&
|
|
52
|
-
symbol.isReferenceToImport(typeChecker, node.expression, createApplicationSpecifier));
|
|
53
|
-
};
|
|
54
|
-
const isBootstrapAppNode = (node) => {
|
|
55
|
-
return (ts.isCallExpression(node) &&
|
|
56
|
-
bootstrapAppSpecifier !== null &&
|
|
57
|
-
symbol.isReferenceToImport(typeChecker, node.expression, bootstrapAppSpecifier));
|
|
58
|
-
};
|
|
59
|
-
const isBootstrapModuleNode = (node) => {
|
|
60
|
-
return (ts.isCallExpression(node) &&
|
|
61
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
62
|
-
node.expression.name.text === 'bootstrapModule' &&
|
|
63
|
-
node.arguments.length > 0);
|
|
64
|
-
};
|
|
65
|
-
const isTestBedInitEnvironmentNode = (node) => {
|
|
66
|
-
return (ts.isCallExpression(node) &&
|
|
67
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
68
|
-
node.expression.name.text === 'initTestEnvironment' &&
|
|
69
|
-
(symbol.isReferenceToImport(typeChecker, node.expression.expression, testBedSpecifier) ||
|
|
70
|
-
symbol.isReferenceToImport(typeChecker, node.expression.expression, getTestBedSpecifier)));
|
|
71
|
-
};
|
|
72
|
-
const reflector = new migrations.TypeScriptReflectionHost(typeChecker);
|
|
73
|
-
const evaluator = new migrations.PartialEvaluator(reflector, typeChecker, null);
|
|
74
|
-
const walk = (node) => {
|
|
75
|
-
if (isBootstrapAppNode(node)) {
|
|
76
|
-
this.analyzeBootstrapApplication(node, sourceFile, info, typeChecker, importManager, replacements);
|
|
77
|
-
}
|
|
78
|
-
else if (isCreateApplicationNode(node)) {
|
|
79
|
-
this.analyzeCreateApplication(node, sourceFile, info, typeChecker, importManager, replacements);
|
|
80
|
-
}
|
|
81
|
-
else if (isBootstrapModuleNode(node)) {
|
|
82
|
-
this.analyzeBootstrapModule(node, sourceFile, reflector, evaluator, info, typeChecker, importManager, replacements);
|
|
83
|
-
}
|
|
84
|
-
else if (isTestBedInitEnvironmentNode(node)) {
|
|
85
|
-
this.analyzeTestBedInitEnvironment(node, sourceFile, info, typeChecker, importManager, replacements);
|
|
86
|
-
}
|
|
87
|
-
node.forEachChild(walk);
|
|
88
|
-
};
|
|
89
|
-
sourceFile.forEachChild(walk);
|
|
90
|
-
}
|
|
91
|
-
apply_import_manager.applyImportManagerChanges(importManager, replacements, info.sourceFiles, info);
|
|
92
|
-
return project_paths.confirmAsSerializable({ replacements });
|
|
93
|
-
}
|
|
94
|
-
async combine(unitA, unitB) {
|
|
95
|
-
const combined = [...unitA.replacements, ...unitB.replacements];
|
|
96
|
-
return project_paths.confirmAsSerializable({ replacements: combined });
|
|
97
|
-
}
|
|
98
|
-
async globalMeta(data) {
|
|
99
|
-
return project_paths.confirmAsSerializable(data);
|
|
100
|
-
}
|
|
101
|
-
async stats(data) {
|
|
102
|
-
return project_paths.confirmAsSerializable({});
|
|
103
|
-
}
|
|
104
|
-
async migrate(data) {
|
|
105
|
-
return { replacements: data.replacements };
|
|
106
|
-
}
|
|
107
|
-
analyzeBootstrapApplication(node, sourceFile, info, typeChecker, importManager, replacements) {
|
|
108
|
-
const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(node, typeChecker);
|
|
109
|
-
if (hasExistingChangeDetectionProvider)
|
|
110
|
-
return;
|
|
111
|
-
const providerFn = 'provideZoneChangeDetection()';
|
|
112
|
-
const optionsNode = node.arguments[1];
|
|
113
|
-
const currentProjectFile = project_paths.projectFile(sourceFile, info);
|
|
114
|
-
if (optionsNode) {
|
|
115
|
-
let optionProjectFile = currentProjectFile;
|
|
116
|
-
let optionLiteral;
|
|
117
|
-
if (ts.isObjectLiteralExpression(optionsNode)) {
|
|
118
|
-
optionLiteral = optionsNode;
|
|
119
|
-
addProvidersToBootstrapOption(optionProjectFile, optionLiteral, providerFn, replacements);
|
|
120
|
-
}
|
|
121
|
-
else if (this.isServerConfigZoneless(optionsNode, typeChecker)) {
|
|
122
|
-
// Nothing to migrate for the SSR bootstrap
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
else if (ts.isIdentifier(optionsNode)) {
|
|
126
|
-
const text = `{...${optionsNode.getText()}, providers: [${providerFn}, ...${optionsNode.getText()}.providers]}`;
|
|
127
|
-
replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({
|
|
128
|
-
position: optionsNode.getStart(),
|
|
129
|
-
end: optionsNode.getEnd(),
|
|
130
|
-
toInsert: text,
|
|
131
|
-
})));
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
throw new Error('unsupported optionsNode: ' + optionsNode.getText());
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
// No options object, add it.
|
|
139
|
-
const text = `, {providers: [${providerFn}]}`;
|
|
140
|
-
const component = node.arguments[0];
|
|
141
|
-
replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({ position: component.getEnd(), end: component.getEnd(), toInsert: text })));
|
|
142
|
-
}
|
|
143
|
-
importManager.addImport({
|
|
144
|
-
exportModuleSpecifier: CORE_PACKAGE,
|
|
145
|
-
exportSymbolName: 'provideZoneChangeDetection',
|
|
146
|
-
requestedFile: sourceFile,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* The optionsNode can be a appConfig built with mergeApplicationConfig
|
|
151
|
-
* In this case we need to analyze if the base config uses provideZonelessChangeDetection
|
|
152
|
-
*/
|
|
153
|
-
isServerConfigZoneless(optionsNode, typeChecker) {
|
|
154
|
-
// Check if optionsNode is a result of mergeApplicationConfig
|
|
155
|
-
let symbol = typeChecker.getSymbolAtLocation(optionsNode);
|
|
156
|
-
if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
|
|
157
|
-
symbol = typeChecker.getAliasedSymbol(symbol);
|
|
158
|
-
}
|
|
159
|
-
const optionDeclaration = symbol?.getDeclarations()?.[0];
|
|
160
|
-
if (!optionDeclaration) {
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
if (!ts.isVariableDeclaration(optionDeclaration) ||
|
|
164
|
-
!optionDeclaration.initializer ||
|
|
165
|
-
!ts.isCallExpression(optionDeclaration.initializer) ||
|
|
166
|
-
!ts.isIdentifier(optionDeclaration.initializer.expression) ||
|
|
167
|
-
optionDeclaration.initializer.expression.text !== 'mergeApplicationConfig') {
|
|
168
|
-
// We didn't find a mergeApplicationConfig call, this isn't a server config
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
let maybeAppConfig = optionDeclaration.initializer.arguments[0];
|
|
172
|
-
if (ts.isIdentifier(maybeAppConfig)) {
|
|
173
|
-
const resolved = getObjectLiteralFromIdentifier(maybeAppConfig, typeChecker);
|
|
174
|
-
if (resolved) {
|
|
175
|
-
maybeAppConfig = resolved;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (maybeAppConfig && ts.isObjectLiteralExpression(maybeAppConfig)) {
|
|
179
|
-
for (const prop of maybeAppConfig.properties) {
|
|
180
|
-
if (ts.isPropertyAssignment(prop) &&
|
|
181
|
-
ts.isIdentifier(prop.name) &&
|
|
182
|
-
prop.name.text === 'providers' &&
|
|
183
|
-
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
184
|
-
for (const el of prop.initializer.elements) {
|
|
185
|
-
if (ts.isCallExpression(el) &&
|
|
186
|
-
ts.isIdentifier(el.expression) &&
|
|
187
|
-
el.expression.text === 'provideZonelessChangeDetection') {
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
analyzeCreateApplication(node, sourceFile, info, typeChecker, importManager, replacements) {
|
|
197
|
-
const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(node, typeChecker);
|
|
198
|
-
if (hasExistingChangeDetectionProvider)
|
|
199
|
-
return;
|
|
200
|
-
const providerFn = 'provideZoneChangeDetection()';
|
|
201
|
-
const optionsNode = node.arguments[0];
|
|
202
|
-
const currentProjectFile = project_paths.projectFile(sourceFile, info);
|
|
203
|
-
if (optionsNode) {
|
|
204
|
-
let optionProjectFile = currentProjectFile;
|
|
205
|
-
let optionLiteral;
|
|
206
|
-
if (ts.isObjectLiteralExpression(optionsNode)) {
|
|
207
|
-
optionLiteral = optionsNode;
|
|
208
|
-
addProvidersToBootstrapOption(optionProjectFile, optionLiteral, providerFn, replacements);
|
|
209
|
-
}
|
|
210
|
-
else if (ts.isIdentifier(optionsNode) ||
|
|
211
|
-
ts.isCallExpression(optionsNode) ||
|
|
212
|
-
ts.isPropertyAccessExpression(optionsNode)) {
|
|
213
|
-
// This is tricky case to handle, in G3 we're might not be able to resolve the identifier's value
|
|
214
|
-
// Our best alternative is to assume there is no CD providers set and add the ZoneChangeDetection provider
|
|
215
|
-
// In the cases where it is, we'll just override the zone provider we just set by re-used inthe appConfig providers
|
|
216
|
-
// TODO: Should we insert a TODO to clean this up ?
|
|
217
|
-
const text = `{...${optionsNode.getText()}, providers: [${providerFn}, ...${optionsNode.getText()}.providers]}`;
|
|
218
|
-
replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({
|
|
219
|
-
position: optionsNode.getStart(),
|
|
220
|
-
end: optionsNode.getEnd(),
|
|
221
|
-
toInsert: text,
|
|
222
|
-
})));
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
throw new Error('unsupported optionsNode: ' + optionsNode.getText());
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
// No options object, add it.
|
|
230
|
-
const text = `{providers: [${providerFn}]}`;
|
|
231
|
-
replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({
|
|
232
|
-
position: node.expression.getEnd() + 1,
|
|
233
|
-
end: node.expression.getEnd() + 1,
|
|
234
|
-
toInsert: text,
|
|
235
|
-
})));
|
|
236
|
-
}
|
|
237
|
-
importManager.addImport({
|
|
238
|
-
exportModuleSpecifier: CORE_PACKAGE,
|
|
239
|
-
exportSymbolName: 'provideZoneChangeDetection',
|
|
240
|
-
requestedFile: sourceFile,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
analyzeBootstrapModule(node, sourceFile, reflector, evaluator, info, typeChecker, importManager, replacements) {
|
|
244
|
-
const moduleIdentifier = node.arguments[0];
|
|
245
|
-
const moduleType = evaluator.evaluate(moduleIdentifier);
|
|
246
|
-
if (!(moduleType instanceof migrations.Reference) || !ts.isClassDeclaration(moduleType.node)) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
const moduleClass = moduleType.node;
|
|
250
|
-
const ngModule = findNgModule(moduleClass, reflector);
|
|
251
|
-
if (!ngModule) {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
const optionsNode = node.arguments[1];
|
|
255
|
-
const file = project_paths.projectFile(sourceFile, info);
|
|
256
|
-
replacements.push(new project_paths.Replacement(file, new project_paths.TextUpdate({
|
|
257
|
-
position: moduleIdentifier.getEnd(),
|
|
258
|
-
end: node.getEnd() - 1,
|
|
259
|
-
toInsert: '',
|
|
260
|
-
})));
|
|
261
|
-
const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(ngModule, typeChecker);
|
|
262
|
-
if (hasExistingChangeDetectionProvider) {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
// Let's try to understand the bootstrap options.
|
|
266
|
-
let options = optionsNode ? evaluator.evaluate(optionsNode) : null;
|
|
267
|
-
let extraOptions = new Map();
|
|
268
|
-
let zoneCdProvider = ZONE_CD_PROVIDER;
|
|
269
|
-
let zoneInstanceProvider = null;
|
|
270
|
-
if (Array.isArray(options)) {
|
|
271
|
-
const mergedOptions = options.reduce((acc, item) => {
|
|
272
|
-
if (item instanceof Map) {
|
|
273
|
-
for (const [k, v] of item) {
|
|
274
|
-
acc.set(k, v);
|
|
275
|
-
if (!SAFE_TO_REMOVE_OPTIONS.includes(k)) {
|
|
276
|
-
extraOptions.set(k, v);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return acc;
|
|
281
|
-
}, new Map());
|
|
282
|
-
options = mergedOptions;
|
|
283
|
-
}
|
|
284
|
-
if (options instanceof Map) {
|
|
285
|
-
[...options.entries()].forEach(([k, v]) => {
|
|
286
|
-
if (!BOOTSTRAP_OPTIONS.includes(k) && typeof v !== 'string') {
|
|
287
|
-
extraOptions.set(k, v);
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
if (options.has('ngZoneRunCoalescing') || options.has('ngZoneEventCoalescing')) {
|
|
291
|
-
const config = [];
|
|
292
|
-
if (options.get('ngZoneRunCoalescing')) {
|
|
293
|
-
config.push('runCoalescing: true');
|
|
294
|
-
}
|
|
295
|
-
if (options.get('ngZoneEventCoalescing')) {
|
|
296
|
-
config.push('eventCoalescing: true');
|
|
297
|
-
}
|
|
298
|
-
zoneCdProvider = `${PROVIDE_ZONE_CHANGE_DETECTION}(${config.length > 0 ? `{ ${config.join(', ')} }` : ''})`;
|
|
299
|
-
}
|
|
300
|
-
const ngZoneOption = options.get('ngZone');
|
|
301
|
-
if (ngZoneOption instanceof migrations.Reference) {
|
|
302
|
-
const clazz = ngZoneOption.node;
|
|
303
|
-
if (ts.isClassDeclaration(clazz) && clazz.name) {
|
|
304
|
-
zoneInstanceProvider = `{provide: NgZone, useClass: ${clazz.name.text}}`;
|
|
305
|
-
removePropertiesFromLiteral(file, optionsNode, ['ngZone'], replacements);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
else if (typeof ngZoneOption === 'string') {
|
|
309
|
-
if (ngZoneOption === 'noop') {
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
else if (ngZoneOption && typeof ngZoneOption !== 'string') {
|
|
314
|
-
// This is a case where we're not able to migrate automatically
|
|
315
|
-
// The migration fails gracefully, keeps the ngZone option and adds a TODO.
|
|
316
|
-
let ngZoneValue;
|
|
317
|
-
optionsNode.properties.forEach((p) => {
|
|
318
|
-
if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'ngZone') {
|
|
319
|
-
ngZoneValue = p.initializer.getText();
|
|
320
|
-
}
|
|
321
|
-
else if (ts.isShorthandPropertyAssignment(p) && p.name.text === 'ngZone') {
|
|
322
|
-
ngZoneValue = p.name.text;
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
if (ngZoneValue) {
|
|
326
|
-
// We re-add the ngZone option
|
|
327
|
-
extraOptions.set('ngZone', ngZoneValue);
|
|
328
|
-
}
|
|
329
|
-
replacements.push(new project_paths.Replacement(project_paths.projectFile(sourceFile, info), new project_paths.TextUpdate({
|
|
330
|
-
position: node.getStart() - 1,
|
|
331
|
-
end: node.getStart() - 1,
|
|
332
|
-
toInsert: '// TODO: BootstrapOptions are deprecated & ignored. Configure NgZone in the providers array of the application module instead.',
|
|
333
|
-
})));
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
const providers = [zoneCdProvider];
|
|
337
|
-
if (zoneInstanceProvider) {
|
|
338
|
-
providers.push(zoneInstanceProvider);
|
|
339
|
-
}
|
|
340
|
-
importManager.addImport({
|
|
341
|
-
exportModuleSpecifier: CORE_PACKAGE,
|
|
342
|
-
exportSymbolName: PROVIDE_ZONE_CHANGE_DETECTION,
|
|
343
|
-
requestedFile: sourceFile,
|
|
344
|
-
});
|
|
345
|
-
// if we only use the key, we use the a shorthand asignment
|
|
346
|
-
const extraOptionsStr = [...extraOptions.entries()]
|
|
347
|
-
.map(([k, v]) => (k != v ? `${k}: ${v},` : `${k},`))
|
|
348
|
-
.join(', ');
|
|
349
|
-
replacements.push(new project_paths.Replacement(file, new project_paths.TextUpdate({
|
|
350
|
-
position: moduleIdentifier.end,
|
|
351
|
-
end: moduleIdentifier.end,
|
|
352
|
-
toInsert: `, { applicationProviders: [${providers.join(', ')}], ${extraOptionsStr}}`,
|
|
353
|
-
})));
|
|
354
|
-
}
|
|
355
|
-
analyzeTestBedInitEnvironment(callExpr, sourceFile, info, typeChecker, importManager, replacements) {
|
|
356
|
-
const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(callExpr, typeChecker);
|
|
357
|
-
if (hasExistingChangeDetectionProvider)
|
|
358
|
-
return;
|
|
359
|
-
const ngModules = callExpr.arguments[0];
|
|
360
|
-
const moduleProjectFile = project_paths.projectFile(sourceFile, info);
|
|
361
|
-
importManager.addImport({
|
|
362
|
-
exportModuleSpecifier: CORE_PACKAGE,
|
|
363
|
-
exportSymbolName: PROVIDE_ZONE_CHANGE_DETECTION,
|
|
364
|
-
requestedFile: sourceFile,
|
|
365
|
-
});
|
|
366
|
-
let tmpNode = callExpr;
|
|
367
|
-
let insertPosition = 0;
|
|
368
|
-
while (tmpNode.parent.kind !== ts.SyntaxKind.SourceFile) {
|
|
369
|
-
insertPosition = tmpNode.parent.getStart(sourceFile, true) - 1;
|
|
370
|
-
tmpNode = tmpNode.parent;
|
|
371
|
-
}
|
|
372
|
-
importManager.addImport({
|
|
373
|
-
exportModuleSpecifier: CORE_PACKAGE,
|
|
374
|
-
exportSymbolName: 'NgModule',
|
|
375
|
-
requestedFile: sourceFile,
|
|
376
|
-
});
|
|
377
|
-
addZoneCDModule(ZONE_CD_PROVIDER, moduleProjectFile, insertPosition, replacements);
|
|
378
|
-
insertZoneCDModule(ngModules, moduleProjectFile, replacements, 'ZoneChangeDetectionModule');
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
function addZoneCDModule(providersText, projectFile, location, replacements) {
|
|
382
|
-
const newModuleText = `\n@NgModule({ providers: [ ${providersText} ] })
|
|
383
|
-
export class ZoneChangeDetectionModule {}\n\n`;
|
|
384
|
-
if (replacementsHaveZoneCdModule(projectFile.rootRelativePath, replacements, newModuleText)) {
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
388
|
-
position: location,
|
|
389
|
-
end: location,
|
|
390
|
-
toInsert: newModuleText,
|
|
391
|
-
})));
|
|
392
|
-
}
|
|
393
|
-
function insertZoneCDModule(node, projectFile, replacements, importedModule) {
|
|
394
|
-
if (ts.isArrayLiteralExpression(node)) {
|
|
395
|
-
const literal = node;
|
|
396
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
397
|
-
position: literal.elements[0]?.getStart() ?? literal.getEnd() - 1,
|
|
398
|
-
end: literal.elements[0]?.getStart() ?? literal.getEnd() - 1,
|
|
399
|
-
toInsert: importedModule + ',',
|
|
400
|
-
})));
|
|
401
|
-
}
|
|
402
|
-
else if (ts.isIdentifier(node)) {
|
|
403
|
-
// This should be a good enough heuristic to determine if the identifier is not array
|
|
404
|
-
let isArray = !node.text.endsWith('Module');
|
|
405
|
-
// Because if it's an array, we need to spread it
|
|
406
|
-
const newImports = `[${importedModule}, ${isArray ? '...' : ''}${node.text}]`;
|
|
407
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
408
|
-
position: node.getStart(),
|
|
409
|
-
end: node.getEnd(),
|
|
410
|
-
toInsert: newImports,
|
|
411
|
-
})));
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
throw new Error('unsupported importsNode: ' + node.getText());
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
function addProvidersToBootstrapOption(projectFile, optionsNode, providersText, replacements) {
|
|
418
|
-
const providersProp = property_name.findLiteralProperty(optionsNode, 'providers');
|
|
419
|
-
if (providersProp && ts.isPropertyAssignment(providersProp)) {
|
|
420
|
-
// Can be bootstrap(App, {providers: [...]}), bootstrap(App, {providers}), bootstrap(App, {...appConfig, providers}) etc.
|
|
421
|
-
if (ts.isArrayLiteralExpression(providersProp.initializer)) {
|
|
422
|
-
const initializer = providersProp.initializer;
|
|
423
|
-
const text = `${providersText},`;
|
|
424
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
425
|
-
position: initializer.elements[0]?.getStart() ?? initializer.getEnd() - 1,
|
|
426
|
-
end: initializer.elements[0]?.getStart() ?? initializer.getEnd() - 1,
|
|
427
|
-
toInsert: text,
|
|
428
|
-
})));
|
|
429
|
-
}
|
|
430
|
-
else if (ts.isIdentifier(providersProp.initializer)) {
|
|
431
|
-
const newProviders = `[${providersText}, ...${providersProp.initializer.text}]`;
|
|
432
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
433
|
-
position: providersProp.initializer.getStart(),
|
|
434
|
-
end: providersProp.initializer.getEnd(),
|
|
435
|
-
toInsert: newProviders,
|
|
436
|
-
})));
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
const newProviders = `[${providersText}, ...`;
|
|
440
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
441
|
-
position: providersProp.initializer.getStart(),
|
|
442
|
-
end: providersProp.initializer.getStart(),
|
|
443
|
-
toInsert: newProviders,
|
|
444
|
-
})));
|
|
445
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
446
|
-
position: providersProp.initializer.getEnd(),
|
|
447
|
-
end: providersProp.initializer.getEnd(),
|
|
448
|
-
toInsert: ']',
|
|
449
|
-
})));
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
else if (providersProp && ts.isShorthandPropertyAssignment(providersProp)) {
|
|
453
|
-
const newProviders = `providers: [${providersText}, ...${providersProp.name.text}]`;
|
|
454
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
455
|
-
position: providersProp.getStart(),
|
|
456
|
-
end: providersProp.getEnd(),
|
|
457
|
-
toInsert: newProviders,
|
|
458
|
-
})));
|
|
459
|
-
}
|
|
460
|
-
else if (optionsNode.properties.length === 1 &&
|
|
461
|
-
ts.isSpreadAssignment(optionsNode.properties[0])) {
|
|
462
|
-
const spread = optionsNode.properties[0];
|
|
463
|
-
const newProviders = `, providers: [${providersText}, ...${spread.expression.getText()}.providers]`;
|
|
464
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
465
|
-
position: spread.getEnd(),
|
|
466
|
-
end: spread.getEnd(),
|
|
467
|
-
toInsert: newProviders,
|
|
468
|
-
})));
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
const text = `providers: [${providersText}]`;
|
|
472
|
-
let toInsert;
|
|
473
|
-
let position;
|
|
474
|
-
if (optionsNode.properties.length > 0) {
|
|
475
|
-
const lastProperty = optionsNode.properties[optionsNode.properties.length - 1];
|
|
476
|
-
toInsert = `,\n ${text}`;
|
|
477
|
-
position = lastProperty.getEnd();
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
toInsert = `\n ${text}\n`;
|
|
481
|
-
position = optionsNode.getStart() + 1;
|
|
482
|
-
}
|
|
483
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
|
|
484
|
-
position,
|
|
485
|
-
end: position,
|
|
486
|
-
toInsert,
|
|
487
|
-
})));
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
function findNgModule(node, reflector) {
|
|
491
|
-
const decorators = reflector.getDecoratorsOfDeclaration(node);
|
|
492
|
-
if (decorators) {
|
|
493
|
-
const ngModuleDecorator = migrations.getAngularDecorators(decorators, ['NgModule'], true)[0];
|
|
494
|
-
if (ngModuleDecorator &&
|
|
495
|
-
ngModuleDecorator.args &&
|
|
496
|
-
ngModuleDecorator.args.length > 0 &&
|
|
497
|
-
ts.isObjectLiteralExpression(ngModuleDecorator.args[0])) {
|
|
498
|
-
return ngModuleDecorator.args[0];
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return null;
|
|
502
|
-
}
|
|
503
|
-
function hasChangeDetectionProvider(expression, // either the bootstrapApplication or platformBrowserDynamic().bootstrapModule()
|
|
504
|
-
typeChecker) {
|
|
505
|
-
let literal;
|
|
506
|
-
if (ts.isCallExpression(expression)) {
|
|
507
|
-
let optionsNode = expression.arguments[1];
|
|
508
|
-
if (!optionsNode &&
|
|
509
|
-
symbol.isReferenceToImport(typeChecker, expression.expression, imports.getImportSpecifier(expression.getSourceFile(), '@angular/core', 'createApplication'))) {
|
|
510
|
-
optionsNode = expression.arguments[0];
|
|
511
|
-
}
|
|
512
|
-
if (!optionsNode)
|
|
513
|
-
return false;
|
|
514
|
-
if (ts.isIdentifier(optionsNode)) {
|
|
515
|
-
literal = getObjectLiteralFromIdentifier(optionsNode, typeChecker);
|
|
516
|
-
}
|
|
517
|
-
else {
|
|
518
|
-
literal = optionsNode;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
literal = expression;
|
|
523
|
-
}
|
|
524
|
-
if (!literal) {
|
|
525
|
-
return false;
|
|
526
|
-
}
|
|
527
|
-
const provideZoneCdSpecifier = imports.getImportSpecifier(literal.getSourceFile(), '@angular/core', 'provideZoneChangeDetection');
|
|
528
|
-
const provideZonelessCdSpecifier = imports.getImportSpecifier(literal.getSourceFile(), '@angular/core', 'provideZonelessChangeDetection');
|
|
529
|
-
if (provideZoneCdSpecifier === null && provideZonelessCdSpecifier === null) {
|
|
530
|
-
return false;
|
|
531
|
-
}
|
|
532
|
-
const found = ts.forEachChild(literal, function walk(node) {
|
|
533
|
-
if (ts.isCallExpression(node)) {
|
|
534
|
-
if (provideZonelessCdSpecifier &&
|
|
535
|
-
node.getText().includes(provideZonelessCdSpecifier.getText())) {
|
|
536
|
-
return true;
|
|
537
|
-
}
|
|
538
|
-
if (provideZoneCdSpecifier && node.getText().includes(provideZoneCdSpecifier.getText())) {
|
|
539
|
-
return true;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return ts.forEachChild(node, walk);
|
|
543
|
-
});
|
|
544
|
-
return !!found;
|
|
545
|
-
}
|
|
546
|
-
function getObjectLiteralFromIdentifier(identifier, typeChecker) {
|
|
547
|
-
let symbol = typeChecker.getSymbolAtLocation(identifier);
|
|
548
|
-
if (!symbol)
|
|
549
|
-
return;
|
|
550
|
-
// Follow aliases (for imported symbols)
|
|
551
|
-
if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
|
|
552
|
-
symbol = typeChecker.getAliasedSymbol(symbol);
|
|
553
|
-
}
|
|
554
|
-
const declarations = symbol.getDeclarations();
|
|
555
|
-
if (!declarations)
|
|
556
|
-
return;
|
|
557
|
-
for (const decl of declarations) {
|
|
558
|
-
if (ts.isVariableDeclaration(decl) &&
|
|
559
|
-
decl.initializer &&
|
|
560
|
-
ts.isObjectLiteralExpression(decl.initializer)) {
|
|
561
|
-
return decl.initializer;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Extracts the import specifiers related to bootstraping from the source file.
|
|
568
|
-
* Returns null if no relevant specifiers are found.
|
|
569
|
-
*/
|
|
570
|
-
function getSpecifiers(sourceFile) {
|
|
571
|
-
const createApplicationSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core', 'createApplication');
|
|
572
|
-
const bootstrapAppSpecifier = imports.getImportSpecifier(sourceFile, '@angular/platform-browser', 'bootstrapApplication');
|
|
573
|
-
const platformBrowserDynamicSpecifier = imports.getImportSpecifier(sourceFile, '@angular/platform-browser-dynamic', 'platformBrowserDynamic');
|
|
574
|
-
const platformBrowserSpecifier = imports.getImportSpecifier(sourceFile, '@angular/platform-browser', 'platformBrowser');
|
|
575
|
-
const testBedSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core/testing', 'TestBed');
|
|
576
|
-
const getTestBedSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core/testing', 'getTestBed');
|
|
577
|
-
const ngModuleSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core', 'NgModule');
|
|
578
|
-
if (!createApplicationSpecifier &&
|
|
579
|
-
!bootstrapAppSpecifier &&
|
|
580
|
-
!platformBrowserSpecifier &&
|
|
581
|
-
!platformBrowserDynamicSpecifier &&
|
|
582
|
-
!testBedSpecifier &&
|
|
583
|
-
!ngModuleSpecifier &&
|
|
584
|
-
!getTestBedSpecifier) {
|
|
585
|
-
return null;
|
|
586
|
-
}
|
|
587
|
-
return {
|
|
588
|
-
createApplicationSpecifier,
|
|
589
|
-
bootstrapAppSpecifier,
|
|
590
|
-
platformBrowserDynamicSpecifier,
|
|
591
|
-
platformBrowserSpecifier,
|
|
592
|
-
testBedSpecifier,
|
|
593
|
-
ngModuleSpecifier,
|
|
594
|
-
getTestBedSpecifier,
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
/**
|
|
598
|
-
* In the case we're looking to insert a new ZoneChangeDetectionModule, we need to check if we already inserted one.
|
|
599
|
-
*
|
|
600
|
-
* This function also checks if the existing one has fewer options (shorter text length), which means the previous migration strategy inserted one
|
|
601
|
-
* but the following one is more complete and we should still add it (the dedup function will take care of the cleanup).
|
|
602
|
-
*/
|
|
603
|
-
function replacementsHaveZoneCdModule(rootRelativePath, replacements, text) {
|
|
604
|
-
return replacements.some((replacement) => {
|
|
605
|
-
const exisitingText = replacement.update.data.toInsert;
|
|
606
|
-
const isSameFile = replacement.projectFile.rootRelativePath === rootRelativePath;
|
|
607
|
-
return (isSameFile &&
|
|
608
|
-
text.includes('ZoneChangeDetectionModule') &&
|
|
609
|
-
exisitingText.length >= text.length);
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
function removePropertiesFromLiteral(projectFile, literal, propertyNames, replacements) {
|
|
613
|
-
const syntaxList = literal.getChildren().find((ch) => ch.kind === ts.SyntaxKind.SyntaxList);
|
|
614
|
-
const optionsElements = syntaxList.getChildren();
|
|
615
|
-
const optionsToRemove = [];
|
|
616
|
-
optionsElements.forEach((node, i, children) => {
|
|
617
|
-
if (ts.isPropertyAssignment(node) &&
|
|
618
|
-
ts.isIdentifier(node.name) &&
|
|
619
|
-
propertyNames.includes(node.name.text)) {
|
|
620
|
-
// Look ahead for comma
|
|
621
|
-
const next = children[i + 1];
|
|
622
|
-
if (next && next.kind === ts.SyntaxKind.CommaToken) {
|
|
623
|
-
optionsToRemove.push({ start: node.getStart(), end: next.getEnd() });
|
|
624
|
-
}
|
|
625
|
-
else {
|
|
626
|
-
optionsToRemove.push({ start: node.getStart(), end: node.getEnd() });
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
optionsToRemove.forEach((toRemove) => {
|
|
631
|
-
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({ position: toRemove.start, end: toRemove.end, toInsert: '' })));
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function migrate() {
|
|
636
|
-
return async (tree) => {
|
|
637
|
-
await project_paths.runMigrationInDevkit({
|
|
638
|
-
tree,
|
|
639
|
-
getMigration: () => new BootstrapOptionsMigration(),
|
|
640
|
-
});
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
exports.migrate = migrate;
|