@angular/core 19.0.0-next.8 → 19.0.0-rc.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/core.mjs +21591 -19590
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +71 -47
- package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
- package/fesm2022/primitives/signals.mjs +8 -6
- package/fesm2022/primitives/signals.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +90 -10
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/testing.mjs +177 -114
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +591 -101
- package/package.json +1 -1
- package/primitives/event-dispatch/index.d.ts +7 -4
- package/primitives/signals/index.d.ts +7 -1
- package/rxjs-interop/index.d.ts +35 -4
- package/schematics/bundles/{checker-e68dd7ce.js → checker-2451e7c5.js} +2464 -1132
- package/schematics/bundles/{group_replacements-472b2387.js → combine_units-c52492ab.js} +1964 -2207
- package/schematics/bundles/{compiler_host-9a4d0c2b.js → compiler_host-f54f8309.js} +2 -2
- package/schematics/bundles/control-flow-migration.js +3 -3
- package/schematics/bundles/explicit-standalone-flag.js +31 -11
- package/schematics/bundles/{imports-4ac08251.js → imports-44987700.js} +1 -1
- package/schematics/bundles/inject-migration.js +122 -48
- package/schematics/bundles/{leading_space-d190b83b.js → leading_space-6e7a8ec6.js} +1 -1
- package/schematics/bundles/migrate_ts_type_references-ab18a7c3.js +1463 -0
- package/schematics/bundles/{nodes-0e7d45ca.js → ng_decorators-3ad437d2.js} +2 -15
- package/schematics/bundles/nodes-ffdce442.js +27 -0
- package/schematics/bundles/output-migration.js +7450 -0
- package/schematics/bundles/pending-tasks.js +5 -5
- package/schematics/bundles/{program-105283c5.js → program-58424797.js} +1359 -455
- package/schematics/bundles/{project_tsconfig_paths-e9ccccbf.js → project_tsconfig_paths-6c9cde78.js} +1 -1
- package/schematics/bundles/provide-initializer.js +190 -0
- package/schematics/bundles/route-lazy-loading.js +4 -4
- package/schematics/bundles/signal-input-migration.js +197 -349
- package/schematics/bundles/signal-queries-migration.js +462 -185
- package/schematics/bundles/signals.js +54 -0
- package/schematics/bundles/standalone-migration.js +38 -20
- package/schematics/collection.json +11 -0
- package/schematics/migrations.json +7 -1
- package/schematics/ng-generate/output-migration/schema.json +19 -0
- package/schematics/ng-generate/signal-queries-migration/schema.json +11 -0
- package/schematics/ng-generate/signals/schema.json +65 -0
- package/testing/index.d.ts +3 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.0.0-
|
|
3
|
+
* @license Angular v19.0.0-rc.0
|
|
4
4
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -9,19 +9,21 @@
|
|
|
9
9
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
10
10
|
|
|
11
11
|
var schematics = require('@angular-devkit/schematics');
|
|
12
|
-
var project_tsconfig_paths = require('./project_tsconfig_paths-
|
|
13
|
-
var
|
|
12
|
+
var project_tsconfig_paths = require('./project_tsconfig_paths-6c9cde78.js');
|
|
13
|
+
var combine_units = require('./combine_units-c52492ab.js');
|
|
14
14
|
require('os');
|
|
15
15
|
var ts = require('typescript');
|
|
16
|
-
var checker = require('./checker-
|
|
17
|
-
var program = require('./program-
|
|
18
|
-
require('path');
|
|
16
|
+
var checker = require('./checker-2451e7c5.js');
|
|
17
|
+
var program = require('./program-58424797.js');
|
|
19
18
|
var assert = require('assert');
|
|
19
|
+
require('path');
|
|
20
|
+
var migrate_ts_type_references = require('./migrate_ts_type_references-ab18a7c3.js');
|
|
20
21
|
require('@angular-devkit/core');
|
|
21
|
-
require('node:path');
|
|
22
|
+
require('node:path/posix');
|
|
22
23
|
require('fs');
|
|
23
24
|
require('module');
|
|
24
25
|
require('url');
|
|
26
|
+
require('./leading_space-6e7a8ec6.js');
|
|
25
27
|
|
|
26
28
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
27
29
|
|
|
@@ -36,7 +38,7 @@ function migrateHostBindings(host, references, info) {
|
|
|
36
38
|
const seenReferences = new WeakMap();
|
|
37
39
|
for (const reference of references) {
|
|
38
40
|
// This pass only deals with host binding references.
|
|
39
|
-
if (!
|
|
41
|
+
if (!combine_units.isHostBindingReference(reference)) {
|
|
40
42
|
continue;
|
|
41
43
|
}
|
|
42
44
|
// Skip references to incompatible inputs.
|
|
@@ -60,7 +62,7 @@ function migrateHostBindings(host, references, info) {
|
|
|
60
62
|
const appendText = reference.from.isObjectShorthandExpression
|
|
61
63
|
? `: ${reference.from.read.name}()`
|
|
62
64
|
: `()`;
|
|
63
|
-
host.replacements.push(new
|
|
65
|
+
host.replacements.push(new combine_units.Replacement(combine_units.projectFile(bindingField.getSourceFile(), info), new combine_units.TextUpdate({ position: readEndPos, end: readEndPos, toInsert: appendText })));
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -72,7 +74,7 @@ function migrateTemplateReferences(host, references) {
|
|
|
72
74
|
const seenFileReferences = new Set();
|
|
73
75
|
for (const reference of references) {
|
|
74
76
|
// This pass only deals with HTML template references.
|
|
75
|
-
if (!
|
|
77
|
+
if (!combine_units.isTemplateReference(reference)) {
|
|
76
78
|
continue;
|
|
77
79
|
}
|
|
78
80
|
// Skip references to incompatible inputs.
|
|
@@ -89,7 +91,7 @@ function migrateTemplateReferences(host, references) {
|
|
|
89
91
|
const appendText = reference.from.isObjectShorthandExpression
|
|
90
92
|
? `: ${reference.from.read.name}()`
|
|
91
93
|
: `()`;
|
|
92
|
-
host.replacements.push(new
|
|
94
|
+
host.replacements.push(new combine_units.Replacement(reference.from.templateFile, new combine_units.TextUpdate({
|
|
93
95
|
position: reference.from.read.sourceSpan.end,
|
|
94
96
|
end: reference.from.read.sourceSpan.end,
|
|
95
97
|
toInsert: appendText,
|
|
@@ -97,48 +99,6 @@ function migrateTemplateReferences(host, references) {
|
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
/**
|
|
101
|
-
* Migrates TypeScript "ts.Type" references. E.g.
|
|
102
|
-
|
|
103
|
-
* - `Partial<MyComp>` will be converted to `UnwrapSignalInputs<Partial<MyComp>>`.
|
|
104
|
-
in Catalyst test files.
|
|
105
|
-
*/
|
|
106
|
-
function migrateTypeScriptTypeReferences(host, references, importManager, info) {
|
|
107
|
-
const seenTypeNodes = new WeakSet();
|
|
108
|
-
for (const reference of references) {
|
|
109
|
-
// This pass only deals with TS input class type references.
|
|
110
|
-
if (!group_replacements.isTsClassTypeReference(reference)) {
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
// Skip references to classes that are not fully migrated.
|
|
114
|
-
if (!host.shouldMigrateReferencesToClass(reference.target)) {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
// Skip duplicate references. E.g. in batching.
|
|
118
|
-
if (seenTypeNodes.has(reference.from.node)) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
seenTypeNodes.add(reference.from.node);
|
|
122
|
-
if (reference.isPartialReference && reference.isPartOfCatalystFile) {
|
|
123
|
-
assert__default["default"](reference.from.node.typeArguments, 'Expected type arguments for partial reference.');
|
|
124
|
-
assert__default["default"](reference.from.node.typeArguments.length === 1, 'Expected an argument for reference.');
|
|
125
|
-
const firstArg = reference.from.node.typeArguments[0];
|
|
126
|
-
const sf = firstArg.getSourceFile();
|
|
127
|
-
const unwrapImportExpr = importManager.addImport({
|
|
128
|
-
exportModuleSpecifier: 'google3/javascript/angular2/testing/catalyst',
|
|
129
|
-
exportSymbolName: 'UnwrapSignalInputs',
|
|
130
|
-
requestedFile: sf,
|
|
131
|
-
});
|
|
132
|
-
host.replacements.push(new group_replacements.Replacement(group_replacements.projectFile(sf, info), new group_replacements.TextUpdate({
|
|
133
|
-
position: firstArg.getStart(),
|
|
134
|
-
end: firstArg.getStart(),
|
|
135
|
-
toInsert: `${host.printer.printNode(ts__default["default"].EmitHint.Unspecified, unwrapImportExpr, sf)}<`,
|
|
136
|
-
})));
|
|
137
|
-
host.replacements.push(new group_replacements.Replacement(group_replacements.projectFile(sf, info), new group_replacements.TextUpdate({ position: firstArg.getEnd(), end: firstArg.getEnd(), toInsert: '>' })));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
102
|
/**
|
|
143
103
|
* Extracts the type `T` of expressions referencing `QueryList<T>`.
|
|
144
104
|
*/
|
|
@@ -179,7 +139,7 @@ function extractQueryListType(node) {
|
|
|
179
139
|
* --> read stays
|
|
180
140
|
* --> emitDistinctChangesOnly is gone!
|
|
181
141
|
*/
|
|
182
|
-
function computeReplacementsToMigrateQuery(node, metadata, importManager, info, printer) {
|
|
142
|
+
function computeReplacementsToMigrateQuery(node, metadata, importManager, info, printer, options, checker$1) {
|
|
183
143
|
const sf = node.getSourceFile();
|
|
184
144
|
let newQueryFn = importManager.addImport({
|
|
185
145
|
requestedFile: sf,
|
|
@@ -213,36 +173,56 @@ function computeReplacementsToMigrateQuery(node, metadata, importManager, info,
|
|
|
213
173
|
if (optionProperties.length > 0) {
|
|
214
174
|
args.push(ts__default["default"].factory.createObjectLiteralExpression(optionProperties));
|
|
215
175
|
}
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
//
|
|
220
|
-
|
|
176
|
+
const strictNullChecksEnabled = options.strict === true || options.strictNullChecks === true;
|
|
177
|
+
const strictPropertyInitialization = options.strict === true || options.strictPropertyInitialization === true;
|
|
178
|
+
let isRequired = node.exclamationToken !== undefined;
|
|
179
|
+
// If we come across an application with strict null checks enabled, but strict
|
|
180
|
+
// property initialization is disabled, there are two options:
|
|
181
|
+
// - Either the query is already typed to include `undefined` explicitly,
|
|
182
|
+
// in which case an option query makes sense.
|
|
183
|
+
// - OR, the query is not typed to include `undefined`. In which case, the query
|
|
184
|
+
// should be marked as required to not break the app. The user-code throughout
|
|
185
|
+
// the application (given strict null checks) already assumes non-nullable!
|
|
186
|
+
if (strictNullChecksEnabled &&
|
|
187
|
+
!strictPropertyInitialization &&
|
|
188
|
+
node.initializer === undefined &&
|
|
189
|
+
node.questionToken === undefined &&
|
|
190
|
+
type !== undefined &&
|
|
191
|
+
!checker$1.isTypeAssignableTo(checker$1.getUndefinedType(), checker$1.getTypeFromTypeNode(type))) {
|
|
192
|
+
isRequired = true;
|
|
193
|
+
}
|
|
194
|
+
if (isRequired && metadata.queryInfo.first) {
|
|
195
|
+
// If the query is required already via some indicators, and this is a "single"
|
|
196
|
+
// query, use the available `.required` method.
|
|
221
197
|
newQueryFn = ts__default["default"].factory.createPropertyAccessExpression(newQueryFn, 'required');
|
|
222
198
|
}
|
|
223
199
|
// If this query is still nullable (i.e. not required), attempt to remove
|
|
224
200
|
// explicit `undefined` types if possible.
|
|
225
|
-
if (!
|
|
226
|
-
type =
|
|
201
|
+
if (!isRequired && type !== undefined && ts__default["default"].isUnionTypeNode(type)) {
|
|
202
|
+
type = migrate_ts_type_references.removeFromUnionIfPossible(type, (v) => v.kind !== ts__default["default"].SyntaxKind.UndefinedKeyword);
|
|
227
203
|
}
|
|
228
|
-
|
|
204
|
+
let locatorType = Array.isArray(metadata.queryInfo.predicate)
|
|
229
205
|
? null
|
|
230
206
|
: metadata.queryInfo.predicate.expression;
|
|
231
|
-
|
|
232
|
-
// If the type and the read type are matching, we can rely
|
|
233
|
-
//
|
|
207
|
+
let resolvedReadType = metadata.queryInfo.read ?? locatorType;
|
|
208
|
+
// If the original property type and the read type are matching, we can rely
|
|
209
|
+
// on the TS inference, instead of repeating types, like in `viewChild<Button>(Button)`.
|
|
234
210
|
if (type !== undefined &&
|
|
235
|
-
|
|
236
|
-
ts__default["default"].isIdentifier(
|
|
211
|
+
resolvedReadType instanceof checker.WrappedNodeExpr &&
|
|
212
|
+
ts__default["default"].isIdentifier(resolvedReadType.node) &&
|
|
237
213
|
ts__default["default"].isTypeReferenceNode(type) &&
|
|
238
214
|
ts__default["default"].isIdentifier(type.typeName) &&
|
|
239
|
-
type.typeName.text ===
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
const call = ts__default["default"].factory.createCallExpression(newQueryFn,
|
|
243
|
-
|
|
215
|
+
type.typeName.text === resolvedReadType.node.text) {
|
|
216
|
+
locatorType = null;
|
|
217
|
+
}
|
|
218
|
+
const call = ts__default["default"].factory.createCallExpression(newQueryFn,
|
|
219
|
+
// If there is no resolved `ReadT` (e.g. string predicate), we use the
|
|
220
|
+
// original type explicitly as generic. Otherwise, query API is smart
|
|
221
|
+
// enough to always infer.
|
|
222
|
+
resolvedReadType === null && type !== undefined ? [type] : undefined, args);
|
|
223
|
+
const updated = ts__default["default"].factory.createPropertyDeclaration([ts__default["default"].factory.createModifier(ts__default["default"].SyntaxKind.ReadonlyKeyword)], node.name, undefined, undefined, call);
|
|
244
224
|
return [
|
|
245
|
-
new
|
|
225
|
+
new combine_units.Replacement(combine_units.projectFile(node.getSourceFile(), info), new combine_units.TextUpdate({
|
|
246
226
|
position: node.getStart(),
|
|
247
227
|
end: node.getEnd(),
|
|
248
228
|
toInsert: printer.printNode(ts__default["default"].EmitHint.Unspecified, updated, sf),
|
|
@@ -280,7 +260,10 @@ function getUniqueIDForClassProperty(property, info) {
|
|
|
280
260
|
if (!ts__default["default"].isClassDeclaration(property.parent) || property.parent.name === undefined) {
|
|
281
261
|
return null;
|
|
282
262
|
}
|
|
283
|
-
|
|
263
|
+
if (property.name === undefined) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
const id = combine_units.projectFile(property.getSourceFile(), info).id.replace(/\.d\.ts$/, '.ts');
|
|
284
267
|
// Note: If a class is nested, there could be an ID clash.
|
|
285
268
|
// This is highly unlikely though, and this is not a problem because
|
|
286
269
|
// in such cases, there is even less chance there are any references to
|
|
@@ -294,7 +277,7 @@ function getUniqueIDForClassProperty(property, info) {
|
|
|
294
277
|
* returns its resolved metadata if possible.
|
|
295
278
|
*/
|
|
296
279
|
function extractSourceQueryDefinition(node, reflector, evaluator, info) {
|
|
297
|
-
if (!ts__default["default"].isPropertyDeclaration(node) ||
|
|
280
|
+
if ((!ts__default["default"].isPropertyDeclaration(node) && !ts__default["default"].isAccessor(node)) ||
|
|
298
281
|
!ts__default["default"].isClassDeclaration(node.parent) ||
|
|
299
282
|
node.parent.name === undefined ||
|
|
300
283
|
!ts__default["default"].isIdentifier(node.name)) {
|
|
@@ -326,32 +309,72 @@ function extractSourceQueryDefinition(node, reflector, evaluator, info) {
|
|
|
326
309
|
else {
|
|
327
310
|
throw new Error('Unexpected query decorator detected.');
|
|
328
311
|
}
|
|
329
|
-
|
|
312
|
+
let queryInfo = null;
|
|
313
|
+
try {
|
|
314
|
+
queryInfo = program.extractDecoratorQueryMetadata(node, decorator.name, decorator.args ?? [], node.name.text, reflector, evaluator);
|
|
315
|
+
}
|
|
316
|
+
catch (e) {
|
|
317
|
+
if (!(e instanceof checker.FatalDiagnosticError)) {
|
|
318
|
+
throw e;
|
|
319
|
+
}
|
|
320
|
+
console.error(`Skipping query: ${e.node.getSourceFile().fileName}: ${e.toString()}`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
330
323
|
return {
|
|
331
324
|
id,
|
|
332
325
|
kind,
|
|
333
326
|
args: decorator.args ?? [],
|
|
334
327
|
queryInfo,
|
|
335
|
-
node,
|
|
328
|
+
node: node,
|
|
329
|
+
fieldDecorators: decorators,
|
|
336
330
|
};
|
|
337
331
|
}
|
|
338
332
|
|
|
333
|
+
function markFieldIncompatibleInMetadata(data, id, reason) {
|
|
334
|
+
const existing = data[id];
|
|
335
|
+
if (existing === undefined) {
|
|
336
|
+
data[id] = {
|
|
337
|
+
fieldReason: reason,
|
|
338
|
+
classReason: null,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
else if (existing.fieldReason === null) {
|
|
342
|
+
existing.fieldReason = reason;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
existing.fieldReason = migrate_ts_type_references.pickFieldIncompatibility({ reason, context: null }, { reason: existing.fieldReason, context: null }).reason;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function filterBestEffortIncompatibilities(knownQueries) {
|
|
349
|
+
for (const query of Object.values(knownQueries.globalMetadata.problematicQueries)) {
|
|
350
|
+
if (query.fieldReason !== null &&
|
|
351
|
+
!migrate_ts_type_references.nonIgnorableFieldIncompatibilities.includes(query.fieldReason)) {
|
|
352
|
+
query.fieldReason = null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
339
357
|
class KnownQueries {
|
|
340
|
-
|
|
358
|
+
info;
|
|
359
|
+
config;
|
|
360
|
+
globalMetadata;
|
|
361
|
+
classToQueryFields = new Map();
|
|
362
|
+
knownQueryIDs = new Map();
|
|
363
|
+
constructor(info, config, globalMetadata) {
|
|
341
364
|
this.info = info;
|
|
365
|
+
this.config = config;
|
|
342
366
|
this.globalMetadata = globalMetadata;
|
|
343
|
-
this.classToQueryFields = new Map();
|
|
344
|
-
this.knownQueryIDs = new Set();
|
|
345
367
|
}
|
|
346
368
|
isFieldIncompatible(descriptor) {
|
|
347
|
-
return this.
|
|
369
|
+
return this.getIncompatibilityForField(descriptor) !== null;
|
|
348
370
|
}
|
|
349
|
-
markFieldIncompatible(field) {
|
|
350
|
-
this.globalMetadata.problematicQueries
|
|
371
|
+
markFieldIncompatible(field, incompatibility) {
|
|
372
|
+
markFieldIncompatibleInMetadata(this.globalMetadata.problematicQueries, field.key, incompatibility.reason);
|
|
351
373
|
}
|
|
352
|
-
markClassIncompatible(node) {
|
|
374
|
+
markClassIncompatible(node, reason) {
|
|
353
375
|
this.classToQueryFields.get(node)?.forEach((f) => {
|
|
354
|
-
this.globalMetadata.problematicQueries[f.key]
|
|
376
|
+
this.globalMetadata.problematicQueries[f.key] ??= { classReason: null, fieldReason: null };
|
|
377
|
+
this.globalMetadata.problematicQueries[f.key].classReason = reason;
|
|
355
378
|
});
|
|
356
379
|
}
|
|
357
380
|
registerQueryField(queryField, id) {
|
|
@@ -362,7 +385,16 @@ class KnownQueries {
|
|
|
362
385
|
key: id,
|
|
363
386
|
node: queryField,
|
|
364
387
|
});
|
|
365
|
-
this.knownQueryIDs.
|
|
388
|
+
this.knownQueryIDs.set(id, { key: id, node: queryField });
|
|
389
|
+
const descriptor = { key: id, node: queryField };
|
|
390
|
+
const file = combine_units.projectFile(queryField.getSourceFile(), this.info);
|
|
391
|
+
if (this.config.shouldMigrateQuery !== undefined &&
|
|
392
|
+
!this.config.shouldMigrateQuery(descriptor, file)) {
|
|
393
|
+
this.markFieldIncompatible(descriptor, {
|
|
394
|
+
context: null,
|
|
395
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.SkippedViaConfigFilter,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
366
398
|
}
|
|
367
399
|
attemptRetrieveDescriptorFromSymbol(symbol) {
|
|
368
400
|
const descriptor = getClassFieldDescriptorForSymbol(symbol, this.info);
|
|
@@ -381,16 +413,67 @@ class KnownQueries {
|
|
|
381
413
|
return Array.from(this.classToQueryFields.keys()).filter((c) => ts__default["default"].isClassDeclaration(c));
|
|
382
414
|
}
|
|
383
415
|
captureKnownFieldInheritanceRelationship(derived, parent) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
416
|
+
// Note: The edge problematic pattern recognition is not as good as the one
|
|
417
|
+
// we have in the signal input migration. That is because we couldn't trivially
|
|
418
|
+
// build up an inheritance graph during analyze phase where we DON'T know what
|
|
419
|
+
// fields refer to queries. Usually we'd use the graph to smartly propagate
|
|
420
|
+
// incompatibilities using topological sort. This doesn't work here and is
|
|
421
|
+
// unnecessarily complex, so we try our best at detecting direct edge
|
|
422
|
+
// incompatibilities (which are quite order dependent).
|
|
423
|
+
if (this.isFieldIncompatible(parent) && !this.isFieldIncompatible(derived)) {
|
|
424
|
+
this.markFieldIncompatible(derived, {
|
|
425
|
+
context: null,
|
|
426
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.ParentIsIncompatible,
|
|
427
|
+
});
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (this.isFieldIncompatible(derived) && !this.isFieldIncompatible(parent)) {
|
|
431
|
+
this.markFieldIncompatible(parent, {
|
|
432
|
+
context: null,
|
|
433
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.DerivedIsIncompatible,
|
|
434
|
+
});
|
|
387
435
|
}
|
|
388
436
|
}
|
|
389
437
|
captureUnknownDerivedField(field) {
|
|
390
|
-
this.markFieldIncompatible(field
|
|
438
|
+
this.markFieldIncompatible(field, {
|
|
439
|
+
context: null,
|
|
440
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.OverriddenByDerivedClass,
|
|
441
|
+
});
|
|
391
442
|
}
|
|
392
443
|
captureUnknownParentField(field) {
|
|
393
|
-
this.markFieldIncompatible(field
|
|
444
|
+
this.markFieldIncompatible(field, {
|
|
445
|
+
context: null,
|
|
446
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.TypeConflictWithBaseClass,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
getIncompatibilityForField(descriptor) {
|
|
450
|
+
const problematicInfo = this.globalMetadata.problematicQueries[descriptor.key];
|
|
451
|
+
if (problematicInfo === undefined) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
if (problematicInfo.fieldReason !== null) {
|
|
455
|
+
return { context: null, reason: problematicInfo.fieldReason };
|
|
456
|
+
}
|
|
457
|
+
if (problematicInfo.classReason !== null) {
|
|
458
|
+
return problematicInfo.classReason;
|
|
459
|
+
}
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
getIncompatibilityTextForField(field) {
|
|
463
|
+
const incompatibilityInfo = this.globalMetadata.problematicQueries[field.key];
|
|
464
|
+
if (incompatibilityInfo.fieldReason !== null) {
|
|
465
|
+
return migrate_ts_type_references.getMessageForFieldIncompatibility(incompatibilityInfo.fieldReason, {
|
|
466
|
+
single: 'query',
|
|
467
|
+
plural: 'queries',
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (incompatibilityInfo.classReason !== null) {
|
|
471
|
+
return migrate_ts_type_references.getMessageForClassIncompatibility(incompatibilityInfo.classReason, {
|
|
472
|
+
single: 'query',
|
|
473
|
+
plural: 'queries',
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
394
477
|
}
|
|
395
478
|
}
|
|
396
479
|
|
|
@@ -418,7 +501,7 @@ function queryFunctionNameToDecorator(name) {
|
|
|
418
501
|
* E.g. whether `<my-read>.toArray` is detected.
|
|
419
502
|
*/
|
|
420
503
|
function checkTsReferenceAccessesField(ref, fieldName) {
|
|
421
|
-
const accessNode =
|
|
504
|
+
const accessNode = combine_units.traverseAccess(ref.from.node);
|
|
422
505
|
// Check if the reference is part of a property access.
|
|
423
506
|
if (!ts__default["default"].isPropertyAccessExpression(accessNode.parent) ||
|
|
424
507
|
!ts__default["default"].isIdentifier(accessNode.parent.name)) {
|
|
@@ -486,21 +569,24 @@ function checkNonTsReferenceCallsField(ref, fieldName) {
|
|
|
486
569
|
return potentialCall;
|
|
487
570
|
}
|
|
488
571
|
|
|
489
|
-
function removeQueryListToArrayCall(ref, info, globalMetadata, replacements) {
|
|
490
|
-
if (!
|
|
572
|
+
function removeQueryListToArrayCall(ref, info, globalMetadata, knownQueries, replacements) {
|
|
573
|
+
if (!combine_units.isHostBindingReference(ref) && !combine_units.isTemplateReference(ref) && !combine_units.isTsReference(ref)) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (knownQueries.isFieldIncompatible(ref.target)) {
|
|
491
577
|
return;
|
|
492
578
|
}
|
|
493
579
|
if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
|
|
494
580
|
return;
|
|
495
581
|
}
|
|
496
582
|
// TS references.
|
|
497
|
-
if (
|
|
583
|
+
if (combine_units.isTsReference(ref)) {
|
|
498
584
|
const toArrayCallExpr = checkTsReferenceCallsField(ref, 'toArray');
|
|
499
585
|
if (toArrayCallExpr === null) {
|
|
500
586
|
return;
|
|
501
587
|
}
|
|
502
588
|
const toArrayExpr = toArrayCallExpr.expression;
|
|
503
|
-
replacements.push(new
|
|
589
|
+
replacements.push(new combine_units.Replacement(combine_units.projectFile(toArrayExpr.getSourceFile(), info), new combine_units.TextUpdate({
|
|
504
590
|
// Delete from expression end to call end. E.g. `.toArray(<..>)`.
|
|
505
591
|
position: toArrayExpr.expression.getEnd(),
|
|
506
592
|
end: toArrayCallExpr.getEnd(),
|
|
@@ -513,9 +599,9 @@ function removeQueryListToArrayCall(ref, info, globalMetadata, replacements) {
|
|
|
513
599
|
if (callExpr === null) {
|
|
514
600
|
return;
|
|
515
601
|
}
|
|
516
|
-
const file =
|
|
517
|
-
const offset =
|
|
518
|
-
replacements.push(new
|
|
602
|
+
const file = combine_units.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
|
|
603
|
+
const offset = combine_units.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
|
|
604
|
+
replacements.push(new combine_units.Replacement(file, new combine_units.TextUpdate({
|
|
519
605
|
// Delete from expression end to call end. E.g. `.toArray(<..>)`.
|
|
520
606
|
position: offset + callExpr.receiver.receiver.sourceSpan.end,
|
|
521
607
|
end: offset + callExpr.sourceSpan.end,
|
|
@@ -523,20 +609,23 @@ function removeQueryListToArrayCall(ref, info, globalMetadata, replacements) {
|
|
|
523
609
|
})));
|
|
524
610
|
}
|
|
525
611
|
|
|
526
|
-
function replaceQueryListGetCall(ref, info, globalMetadata, replacements) {
|
|
527
|
-
if (!
|
|
612
|
+
function replaceQueryListGetCall(ref, info, globalMetadata, knownQueries, replacements) {
|
|
613
|
+
if (!combine_units.isHostBindingReference(ref) && !combine_units.isTemplateReference(ref) && !combine_units.isTsReference(ref)) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (knownQueries.isFieldIncompatible(ref.target)) {
|
|
528
617
|
return;
|
|
529
618
|
}
|
|
530
619
|
if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
|
|
531
620
|
return;
|
|
532
621
|
}
|
|
533
|
-
if (
|
|
622
|
+
if (combine_units.isTsReference(ref)) {
|
|
534
623
|
const getCallExpr = checkTsReferenceCallsField(ref, 'get');
|
|
535
624
|
if (getCallExpr === null) {
|
|
536
625
|
return;
|
|
537
626
|
}
|
|
538
627
|
const getExpr = getCallExpr.expression;
|
|
539
|
-
replacements.push(new
|
|
628
|
+
replacements.push(new combine_units.Replacement(combine_units.projectFile(getExpr.getSourceFile(), info), new combine_units.TextUpdate({
|
|
540
629
|
position: getExpr.name.getStart(),
|
|
541
630
|
end: getExpr.name.getEnd(),
|
|
542
631
|
toInsert: 'at',
|
|
@@ -548,9 +637,9 @@ function replaceQueryListGetCall(ref, info, globalMetadata, replacements) {
|
|
|
548
637
|
if (callExpr === null) {
|
|
549
638
|
return;
|
|
550
639
|
}
|
|
551
|
-
const file =
|
|
552
|
-
const offset =
|
|
553
|
-
replacements.push(new
|
|
640
|
+
const file = combine_units.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
|
|
641
|
+
const offset = combine_units.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
|
|
642
|
+
replacements.push(new combine_units.Replacement(file, new combine_units.TextUpdate({
|
|
554
643
|
position: offset + callExpr.receiver.nameSpan.start,
|
|
555
644
|
end: offset + callExpr.receiver.nameSpan.end,
|
|
556
645
|
toInsert: 'at',
|
|
@@ -566,7 +655,7 @@ const problematicQueryListMethods = [
|
|
|
566
655
|
'destroy',
|
|
567
656
|
];
|
|
568
657
|
function checkForIncompatibleQueryListAccesses(ref, result) {
|
|
569
|
-
if (
|
|
658
|
+
if (combine_units.isTsReference(ref)) {
|
|
570
659
|
for (const problematicFn of problematicQueryListMethods) {
|
|
571
660
|
const access = checkTsReferenceAccessesField(ref, problematicFn);
|
|
572
661
|
if (access !== null) {
|
|
@@ -575,7 +664,7 @@ function checkForIncompatibleQueryListAccesses(ref, result) {
|
|
|
575
664
|
}
|
|
576
665
|
}
|
|
577
666
|
}
|
|
578
|
-
if (
|
|
667
|
+
if (combine_units.isHostBindingReference(ref) || combine_units.isTemplateReference(ref)) {
|
|
579
668
|
for (const problematicFn of problematicQueryListMethods) {
|
|
580
669
|
const access = checkNonTsReferenceAccessesField(ref, problematicFn);
|
|
581
670
|
if (access !== null) {
|
|
@@ -590,19 +679,22 @@ const mapping = new Map([
|
|
|
590
679
|
['first', 'at(0)!'],
|
|
591
680
|
['last', 'at(-1)!'],
|
|
592
681
|
]);
|
|
593
|
-
function replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, replacements) {
|
|
594
|
-
if (!
|
|
682
|
+
function replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, knownQueries, replacements) {
|
|
683
|
+
if (!combine_units.isHostBindingReference(ref) && !combine_units.isTemplateReference(ref) && !combine_units.isTsReference(ref)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (knownQueries.isFieldIncompatible(ref.target)) {
|
|
595
687
|
return;
|
|
596
688
|
}
|
|
597
689
|
if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
|
|
598
690
|
return;
|
|
599
691
|
}
|
|
600
|
-
if (
|
|
692
|
+
if (combine_units.isTsReference(ref)) {
|
|
601
693
|
const expr = checkTsReferenceAccessesField(ref, 'first') ?? checkTsReferenceAccessesField(ref, 'last');
|
|
602
694
|
if (expr === null) {
|
|
603
695
|
return;
|
|
604
696
|
}
|
|
605
|
-
replacements.push(new
|
|
697
|
+
replacements.push(new combine_units.Replacement(combine_units.projectFile(expr.getSourceFile(), info), new combine_units.TextUpdate({
|
|
606
698
|
position: expr.name.getStart(),
|
|
607
699
|
end: expr.name.getEnd(),
|
|
608
700
|
toInsert: mapping.get(expr.name.text),
|
|
@@ -614,24 +706,23 @@ function replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, repla
|
|
|
614
706
|
if (expr === null) {
|
|
615
707
|
return;
|
|
616
708
|
}
|
|
617
|
-
const file =
|
|
618
|
-
const offset =
|
|
619
|
-
replacements.push(new
|
|
709
|
+
const file = combine_units.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
|
|
710
|
+
const offset = combine_units.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
|
|
711
|
+
replacements.push(new combine_units.Replacement(file, new combine_units.TextUpdate({
|
|
620
712
|
position: offset + expr.nameSpan.start,
|
|
621
713
|
end: offset + expr.nameSpan.end,
|
|
622
714
|
toInsert: mapping.get(expr.name),
|
|
623
715
|
})));
|
|
624
716
|
}
|
|
625
717
|
|
|
626
|
-
class SignalQueriesMigration extends
|
|
718
|
+
class SignalQueriesMigration extends combine_units.TsurgeComplexMigration {
|
|
719
|
+
config;
|
|
627
720
|
constructor(config = {}) {
|
|
628
721
|
super();
|
|
629
722
|
this.config = config;
|
|
630
723
|
}
|
|
631
724
|
async analyze(info) {
|
|
632
725
|
assert__default["default"](info.ngCompiler !== null, 'Expected queries migration to have an Angular program.');
|
|
633
|
-
// TODO: This stage for this migration doesn't necessarily need a full
|
|
634
|
-
// compilation unit program.
|
|
635
726
|
// Pre-Analyze the program and get access to the template type checker.
|
|
636
727
|
const { templateTypeChecker } = info.ngCompiler['ensureAnalyzed']();
|
|
637
728
|
const { sourceFiles, program: program$1 } = info;
|
|
@@ -642,76 +733,184 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
642
733
|
knownQueryFields: {},
|
|
643
734
|
potentialProblematicQueries: {},
|
|
644
735
|
potentialProblematicReferenceForMultiQueries: {},
|
|
736
|
+
reusableAnalysisReferences: null,
|
|
645
737
|
};
|
|
646
|
-
const groupedAstVisitor = new
|
|
738
|
+
const groupedAstVisitor = new migrate_ts_type_references.GroupedTsAstVisitor(sourceFiles);
|
|
647
739
|
const referenceResult = { references: [] };
|
|
740
|
+
const classesWithFilteredQueries = new Set();
|
|
741
|
+
const filteredQueriesForCompilationUnit = new Map();
|
|
648
742
|
const findQueryDefinitionsVisitor = (node) => {
|
|
649
743
|
const extractedQuery = extractSourceQueryDefinition(node, reflector, evaluator, info);
|
|
650
744
|
if (extractedQuery !== null) {
|
|
745
|
+
const queryNode = extractedQuery.node;
|
|
746
|
+
const descriptor = {
|
|
747
|
+
key: extractedQuery.id,
|
|
748
|
+
node: queryNode,
|
|
749
|
+
};
|
|
750
|
+
const containingFile = combine_units.projectFile(queryNode.getSourceFile(), info);
|
|
751
|
+
// If we have a config filter function, use it here for later
|
|
752
|
+
// perf-boosted reference lookups. Useful in non-batch mode.
|
|
753
|
+
if (this.config.shouldMigrateQuery === undefined ||
|
|
754
|
+
this.config.shouldMigrateQuery(descriptor, containingFile)) {
|
|
755
|
+
classesWithFilteredQueries.add(queryNode.parent);
|
|
756
|
+
filteredQueriesForCompilationUnit.set(extractedQuery.id, {
|
|
757
|
+
fieldName: extractedQuery.queryInfo.propertyName,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
651
760
|
res.knownQueryFields[extractedQuery.id] = {
|
|
652
761
|
fieldName: extractedQuery.queryInfo.propertyName,
|
|
653
762
|
isMulti: extractedQuery.queryInfo.first === false,
|
|
654
763
|
};
|
|
764
|
+
if (ts__default["default"].isAccessor(queryNode)) {
|
|
765
|
+
markFieldIncompatibleInMetadata(res.potentialProblematicQueries, extractedQuery.id, migrate_ts_type_references.FieldIncompatibilityReason.Accessor);
|
|
766
|
+
}
|
|
767
|
+
// Detect queries with union types that are uncommon to be
|
|
768
|
+
// automatically migrate-able. E.g. `refs: ElementRef|null`,
|
|
769
|
+
// or `ElementRef|SomeOtherType`.
|
|
770
|
+
if (queryNode.type !== undefined &&
|
|
771
|
+
ts__default["default"].isUnionTypeNode(queryNode.type) &&
|
|
772
|
+
// Either too large union, or doesn't match `T|undefined`.
|
|
773
|
+
(queryNode.type.types.length > 2 ||
|
|
774
|
+
!queryNode.type.types.some((t) => t.kind === ts__default["default"].SyntaxKind.UndefinedKeyword))) {
|
|
775
|
+
markFieldIncompatibleInMetadata(res.potentialProblematicQueries, extractedQuery.id, migrate_ts_type_references.FieldIncompatibilityReason.SignalQueries__IncompatibleMultiUnionType);
|
|
776
|
+
}
|
|
777
|
+
// Migrating fields with `@HostBinding` is incompatible as
|
|
778
|
+
// the host binding decorator does not invoke the signal.
|
|
779
|
+
const hostBindingDecorators = checker.getAngularDecorators(extractedQuery.fieldDecorators, ['HostBinding'],
|
|
780
|
+
/* isCore */ false);
|
|
781
|
+
if (hostBindingDecorators.length > 0) {
|
|
782
|
+
markFieldIncompatibleInMetadata(res.potentialProblematicQueries, extractedQuery.id, migrate_ts_type_references.FieldIncompatibilityReason.SignalIncompatibleWithHostBinding);
|
|
783
|
+
}
|
|
655
784
|
}
|
|
656
785
|
};
|
|
786
|
+
this.config.reportProgressFn?.(20, 'Scanning for queries..');
|
|
657
787
|
groupedAstVisitor.register(findQueryDefinitionsVisitor);
|
|
658
|
-
groupedAstVisitor.
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
788
|
+
groupedAstVisitor.execute();
|
|
789
|
+
const allFieldsOrKnownQueries = {
|
|
790
|
+
// Note: We don't support cross-target migration of `Partial<T>` usages.
|
|
791
|
+
// This is an acceptable limitation for performance reasons.
|
|
792
|
+
shouldTrackClassReference: (node) => classesWithFilteredQueries.has(node),
|
|
793
|
+
attemptRetrieveDescriptorFromSymbol: (s) => {
|
|
794
|
+
const descriptor = getClassFieldDescriptorForSymbol(s, info);
|
|
795
|
+
// If we are executing in upgraded analysis phase mode, we know all
|
|
796
|
+
// of the queries since there aren't any other compilation units.
|
|
797
|
+
// Ignore references to non-query class fields.
|
|
798
|
+
if (this.config.assumeNonBatch &&
|
|
799
|
+
descriptor !== null &&
|
|
800
|
+
!filteredQueriesForCompilationUnit.has(descriptor.key)) {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
// In batch mode, we eagerly, rather expensively, track all references.
|
|
804
|
+
// We don't know yet if something refers to a different query or not, so we
|
|
805
|
+
// eagerly detect such and later filter those problematic references that
|
|
806
|
+
// turned out to refer to queries (once we have the global metadata).
|
|
807
|
+
return descriptor;
|
|
808
|
+
},
|
|
809
|
+
};
|
|
810
|
+
groupedAstVisitor.register(combine_units.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, info.ngCompiler['resourceManager'], evaluator, templateTypeChecker, allFieldsOrKnownQueries,
|
|
811
|
+
// In non-batch mode, we know what inputs exist and can optimize the reference
|
|
812
|
+
// resolution significantly (for e.g. VSCode integration)— as we know what
|
|
813
|
+
// field names may be used to reference potential queries.
|
|
814
|
+
this.config.assumeNonBatch
|
|
815
|
+
? new Set(Array.from(filteredQueriesForCompilationUnit.values()).map((f) => f.fieldName))
|
|
816
|
+
: null, referenceResult).visitor);
|
|
817
|
+
const inheritanceGraph = new migrate_ts_type_references.InheritanceGraph(checker$1).expensivePopulate(info.sourceFiles);
|
|
818
|
+
migrate_ts_type_references.checkIncompatiblePatterns(inheritanceGraph, checker$1, groupedAstVisitor, {
|
|
819
|
+
...allFieldsOrKnownQueries,
|
|
820
|
+
isFieldIncompatible: (f) => res.potentialProblematicQueries[f.key]?.fieldReason !== null ||
|
|
821
|
+
res.potentialProblematicQueries[f.key]?.classReason !== null,
|
|
822
|
+
markClassIncompatible: (clazz, reason) => {
|
|
823
|
+
for (const field of clazz.members) {
|
|
824
|
+
const key = getUniqueIDForClassProperty(field, info);
|
|
825
|
+
if (key !== null) {
|
|
826
|
+
res.potentialProblematicQueries[key] ??= { classReason: null, fieldReason: null };
|
|
827
|
+
res.potentialProblematicQueries[key].classReason = reason;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
markFieldIncompatible: (f, incompatibility) => markFieldIncompatibleInMetadata(res.potentialProblematicQueries, f.key, incompatibility.reason),
|
|
832
|
+
}, () => Array.from(classesWithFilteredQueries));
|
|
833
|
+
this.config.reportProgressFn?.(60, 'Scanning for references and problematic patterns..');
|
|
669
834
|
groupedAstVisitor.execute();
|
|
670
835
|
// Determine incompatible queries based on problematic references
|
|
671
836
|
// we saw in TS code, templates or host bindings.
|
|
672
837
|
for (const ref of referenceResult.references) {
|
|
673
|
-
if (
|
|
674
|
-
res.potentialProblematicQueries
|
|
838
|
+
if (combine_units.isTsReference(ref) && ref.from.isWrite) {
|
|
839
|
+
markFieldIncompatibleInMetadata(res.potentialProblematicQueries, ref.target.key, migrate_ts_type_references.FieldIncompatibilityReason.WriteAssignment);
|
|
675
840
|
}
|
|
676
|
-
if ((
|
|
677
|
-
res.potentialProblematicQueries
|
|
841
|
+
if ((combine_units.isTemplateReference(ref) || combine_units.isHostBindingReference(ref)) && ref.from.isWrite) {
|
|
842
|
+
markFieldIncompatibleInMetadata(res.potentialProblematicQueries, ref.target.key, migrate_ts_type_references.FieldIncompatibilityReason.WriteAssignment);
|
|
678
843
|
}
|
|
679
844
|
// TODO: Remove this when we support signal narrowing in templates.
|
|
680
845
|
// https://github.com/angular/angular/pull/55456.
|
|
681
|
-
if (
|
|
682
|
-
res.potentialProblematicQueries
|
|
846
|
+
if (combine_units.isTemplateReference(ref) && ref.from.isLikelyPartOfNarrowing) {
|
|
847
|
+
markFieldIncompatibleInMetadata(res.potentialProblematicQueries, ref.target.key, migrate_ts_type_references.FieldIncompatibilityReason.PotentiallyNarrowedInTemplateButNoSupportYet);
|
|
683
848
|
}
|
|
684
849
|
// Check for other incompatible query list accesses.
|
|
685
850
|
checkForIncompatibleQueryListAccesses(ref, res);
|
|
686
851
|
}
|
|
687
|
-
|
|
852
|
+
if (this.config.assumeNonBatch) {
|
|
853
|
+
res.reusableAnalysisReferences = referenceResult.references;
|
|
854
|
+
}
|
|
855
|
+
return combine_units.confirmAsSerializable(res);
|
|
688
856
|
}
|
|
689
|
-
async
|
|
690
|
-
const
|
|
857
|
+
async combine(unitA, unitB) {
|
|
858
|
+
const combined = {
|
|
691
859
|
knownQueryFields: {},
|
|
692
|
-
|
|
860
|
+
potentialProblematicQueries: {},
|
|
861
|
+
potentialProblematicReferenceForMultiQueries: {},
|
|
862
|
+
reusableAnalysisReferences: null,
|
|
693
863
|
};
|
|
694
|
-
for (const unit of
|
|
864
|
+
for (const unit of [unitA, unitB]) {
|
|
695
865
|
for (const [id, value] of Object.entries(unit.knownQueryFields)) {
|
|
696
|
-
|
|
866
|
+
combined.knownQueryFields[id] = value;
|
|
867
|
+
}
|
|
868
|
+
for (const [id, info] of Object.entries(unit.potentialProblematicQueries)) {
|
|
869
|
+
if (info.fieldReason !== null) {
|
|
870
|
+
markFieldIncompatibleInMetadata(combined.potentialProblematicQueries, id, info.fieldReason);
|
|
871
|
+
}
|
|
872
|
+
if (info.classReason !== null) {
|
|
873
|
+
combined.potentialProblematicQueries[id] ??= {
|
|
874
|
+
classReason: null,
|
|
875
|
+
fieldReason: null,
|
|
876
|
+
};
|
|
877
|
+
combined.potentialProblematicQueries[id].classReason =
|
|
878
|
+
info.classReason;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
for (const id of Object.keys(unit.potentialProblematicReferenceForMultiQueries)) {
|
|
882
|
+
combined.potentialProblematicReferenceForMultiQueries[id] = true;
|
|
697
883
|
}
|
|
698
|
-
|
|
699
|
-
|
|
884
|
+
if (unit.reusableAnalysisReferences !== null) {
|
|
885
|
+
combined.reusableAnalysisReferences = unit.reusableAnalysisReferences;
|
|
700
886
|
}
|
|
701
887
|
}
|
|
702
|
-
for (const unit of
|
|
888
|
+
for (const unit of [unitA, unitB]) {
|
|
703
889
|
for (const id of Object.keys(unit.potentialProblematicReferenceForMultiQueries)) {
|
|
704
|
-
if (
|
|
705
|
-
|
|
890
|
+
if (combined.knownQueryFields[id]?.isMulti) {
|
|
891
|
+
markFieldIncompatibleInMetadata(combined.potentialProblematicQueries, id, migrate_ts_type_references.FieldIncompatibilityReason.SignalQueries__QueryListProblematicFieldAccessed);
|
|
706
892
|
}
|
|
707
893
|
}
|
|
708
894
|
}
|
|
709
|
-
return
|
|
895
|
+
return combine_units.confirmAsSerializable(combined);
|
|
896
|
+
}
|
|
897
|
+
async globalMeta(combinedData) {
|
|
898
|
+
const globalUnitData = {
|
|
899
|
+
knownQueryFields: combinedData.knownQueryFields,
|
|
900
|
+
problematicQueries: combinedData.potentialProblematicQueries,
|
|
901
|
+
reusableAnalysisReferences: combinedData.reusableAnalysisReferences,
|
|
902
|
+
};
|
|
903
|
+
for (const id of Object.keys(combinedData.potentialProblematicReferenceForMultiQueries)) {
|
|
904
|
+
if (combinedData.knownQueryFields[id]?.isMulti) {
|
|
905
|
+
markFieldIncompatibleInMetadata(globalUnitData.problematicQueries, id, migrate_ts_type_references.FieldIncompatibilityReason.SignalQueries__QueryListProblematicFieldAccessed);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return combine_units.confirmAsSerializable(globalUnitData);
|
|
710
909
|
}
|
|
711
910
|
async migrate(globalMetadata, info) {
|
|
712
911
|
assert__default["default"](info.ngCompiler !== null, 'Expected queries migration to have an Angular program.');
|
|
713
912
|
// Pre-Analyze the program and get access to the template type checker.
|
|
714
|
-
const { templateTypeChecker, metaReader } =
|
|
913
|
+
const { templateTypeChecker, metaReader } = info.ngCompiler['ensureAnalyzed']();
|
|
715
914
|
const { program: program$1, sourceFiles } = info;
|
|
716
915
|
const checker$1 = program$1.getTypeChecker();
|
|
717
916
|
const reflector = new checker.TypeScriptReflectionHost(checker$1);
|
|
@@ -719,16 +918,12 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
719
918
|
const replacements = [];
|
|
720
919
|
const importManager = new checker.ImportManager();
|
|
721
920
|
const printer = ts__default["default"].createPrinter();
|
|
722
|
-
const
|
|
921
|
+
const filesWithSourceQueries = new Map();
|
|
723
922
|
const filesWithIncompleteMigration = new Map();
|
|
724
|
-
const
|
|
725
|
-
const knownQueries = new KnownQueries(info, globalMetadata);
|
|
923
|
+
const filesWithQueryListOutsideOfDeclarations = new WeakSet();
|
|
924
|
+
const knownQueries = new KnownQueries(info, this.config, globalMetadata);
|
|
726
925
|
const referenceResult = { references: [] };
|
|
727
926
|
const sourceQueries = [];
|
|
728
|
-
const isMigratedQuery = (descriptor) => globalMetadata.knownQueryFields[descriptor.key] !== undefined &&
|
|
729
|
-
globalMetadata.problematicQueries[descriptor.key] === undefined &&
|
|
730
|
-
(this.config.shouldMigrateQuery === undefined ||
|
|
731
|
-
this.config.shouldMigrateQuery(descriptor, group_replacements.projectFile(descriptor.node.getSourceFile(), info)));
|
|
732
927
|
// Detect all queries in this unit.
|
|
733
928
|
const queryWholeProgramVisitor = (node) => {
|
|
734
929
|
// Detect all SOURCE queries and migrate them, if possible.
|
|
@@ -739,7 +934,8 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
739
934
|
return;
|
|
740
935
|
}
|
|
741
936
|
// Detect OTHER queries, inside `.d.ts`. Needed for reference resolution below.
|
|
742
|
-
if (ts__default["default"].isPropertyDeclaration(node)
|
|
937
|
+
if (ts__default["default"].isPropertyDeclaration(node) ||
|
|
938
|
+
(ts__default["default"].isAccessor(node) && ts__default["default"].isClassDeclaration(node.parent))) {
|
|
743
939
|
const classFieldID = getUniqueIDForClassProperty(node, info);
|
|
744
940
|
if (classFieldID !== null && globalMetadata.knownQueryFields[classFieldID] !== undefined) {
|
|
745
941
|
knownQueries.registerQueryField(node, classFieldID);
|
|
@@ -751,10 +947,11 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
751
947
|
if (ts__default["default"].isIdentifier(node) &&
|
|
752
948
|
node.text === 'QueryList' &&
|
|
753
949
|
ts__default["default"].findAncestor(node, ts__default["default"].isImportDeclaration) === undefined) {
|
|
754
|
-
|
|
950
|
+
filesWithQueryListOutsideOfDeclarations.add(node.getSourceFile());
|
|
755
951
|
}
|
|
756
952
|
ts__default["default"].forEachChild(node, queryWholeProgramVisitor);
|
|
757
953
|
};
|
|
954
|
+
this.config.reportProgressFn?.(40, 'Tracking query declarations..');
|
|
758
955
|
for (const sf of info.fullProgramSourceFiles) {
|
|
759
956
|
ts__default["default"].forEachChild(sf, queryWholeProgramVisitor);
|
|
760
957
|
}
|
|
@@ -762,47 +959,71 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
762
959
|
// lookups below.
|
|
763
960
|
const fieldNamesToConsiderForReferenceLookup = new Set(Object.values(globalMetadata.knownQueryFields).map((f) => f.fieldName));
|
|
764
961
|
// Find all references.
|
|
765
|
-
const groupedAstVisitor = new
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
962
|
+
const groupedAstVisitor = new migrate_ts_type_references.GroupedTsAstVisitor(sourceFiles);
|
|
963
|
+
// Re-use previous reference result if available, instead of
|
|
964
|
+
// looking for references which is quite expensive.
|
|
965
|
+
if (globalMetadata.reusableAnalysisReferences !== null) {
|
|
966
|
+
referenceResult.references = globalMetadata.reusableAnalysisReferences;
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
groupedAstVisitor.register(combine_units.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, info.ngCompiler['resourceManager'], evaluator, templateTypeChecker, knownQueries, fieldNamesToConsiderForReferenceLookup, referenceResult).visitor);
|
|
970
|
+
}
|
|
770
971
|
// Check inheritance.
|
|
771
|
-
|
|
972
|
+
// NOTE: Inheritance is only checked in the migrate stage as we cannot reliably
|
|
973
|
+
// check during analyze— where we don't know what fields from foreign `.d.ts`
|
|
974
|
+
// files refer to queries or not.
|
|
975
|
+
const inheritanceGraph = new migrate_ts_type_references.InheritanceGraph(checker$1).expensivePopulate(info.sourceFiles);
|
|
976
|
+
migrate_ts_type_references.checkInheritanceOfKnownFields(inheritanceGraph, metaReader, knownQueries, {
|
|
772
977
|
getFieldsForClass: (n) => knownQueries.getQueryFieldsOfClass(n) ?? [],
|
|
773
978
|
isClassWithKnownFields: (clazz) => knownQueries.getQueryFieldsOfClass(clazz) !== undefined,
|
|
774
979
|
});
|
|
980
|
+
this.config.reportProgressFn?.(70, 'Checking inheritance..');
|
|
981
|
+
groupedAstVisitor.execute();
|
|
982
|
+
if (this.config.bestEffortMode) {
|
|
983
|
+
filterBestEffortIncompatibilities(knownQueries);
|
|
984
|
+
}
|
|
985
|
+
this.config.reportProgressFn?.(80, 'Migrating queries..');
|
|
775
986
|
// Migrate declarations.
|
|
776
987
|
for (const extractedQuery of sourceQueries) {
|
|
777
988
|
const node = extractedQuery.node;
|
|
778
989
|
const sf = node.getSourceFile();
|
|
779
990
|
const descriptor = { key: extractedQuery.id, node: extractedQuery.node };
|
|
780
|
-
|
|
991
|
+
const incompatibility = knownQueries.getIncompatibilityForField(descriptor);
|
|
992
|
+
updateFileState(filesWithSourceQueries, sf, extractedQuery.kind);
|
|
993
|
+
if (incompatibility !== null) {
|
|
994
|
+
// Add a TODO for the incompatible query, if desired.
|
|
995
|
+
if (this.config.insertTodosForSkippedFields) {
|
|
996
|
+
replacements.push(...migrate_ts_type_references.insertTodoForIncompatibility(node, info, incompatibility, {
|
|
997
|
+
single: 'query',
|
|
998
|
+
plural: 'queries',
|
|
999
|
+
}));
|
|
1000
|
+
}
|
|
781
1001
|
updateFileState(filesWithIncompleteMigration, sf, extractedQuery.kind);
|
|
782
1002
|
continue;
|
|
783
1003
|
}
|
|
784
|
-
|
|
785
|
-
replacements.push(...computeReplacementsToMigrateQuery(node, extractedQuery, importManager, info, printer));
|
|
1004
|
+
replacements.push(...computeReplacementsToMigrateQuery(node, extractedQuery, importManager, info, printer, info.userOptions, checker$1));
|
|
786
1005
|
}
|
|
787
1006
|
// Migrate references.
|
|
788
1007
|
const referenceMigrationHost = {
|
|
789
1008
|
printer,
|
|
790
1009
|
replacements,
|
|
791
|
-
shouldMigrateReferencesToField: (field) =>
|
|
792
|
-
shouldMigrateReferencesToClass: (clazz) => !!knownQueries
|
|
1010
|
+
shouldMigrateReferencesToField: (field) => !knownQueries.isFieldIncompatible(field),
|
|
1011
|
+
shouldMigrateReferencesToClass: (clazz) => !!knownQueries
|
|
1012
|
+
.getQueryFieldsOfClass(clazz)
|
|
1013
|
+
?.some((q) => !knownQueries.isFieldIncompatible(q)),
|
|
793
1014
|
};
|
|
794
|
-
|
|
1015
|
+
migrate_ts_type_references.migrateTypeScriptReferences(referenceMigrationHost, referenceResult.references, checker$1, info);
|
|
795
1016
|
migrateTemplateReferences(referenceMigrationHost, referenceResult.references);
|
|
796
1017
|
migrateHostBindings(referenceMigrationHost, referenceResult.references, info);
|
|
797
|
-
migrateTypeScriptTypeReferences(referenceMigrationHost, referenceResult.references, importManager, info);
|
|
1018
|
+
migrate_ts_type_references.migrateTypeScriptTypeReferences(referenceMigrationHost, referenceResult.references, importManager, info);
|
|
798
1019
|
// Fix problematic calls, like `QueryList#toArray`, or `QueryList#get`.
|
|
799
1020
|
for (const ref of referenceResult.references) {
|
|
800
|
-
removeQueryListToArrayCall(ref, info, globalMetadata, replacements);
|
|
801
|
-
replaceQueryListGetCall(ref, info, globalMetadata, replacements);
|
|
802
|
-
replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, replacements);
|
|
1021
|
+
removeQueryListToArrayCall(ref, info, globalMetadata, knownQueries, replacements);
|
|
1022
|
+
replaceQueryListGetCall(ref, info, globalMetadata, knownQueries, replacements);
|
|
1023
|
+
replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, knownQueries, replacements);
|
|
803
1024
|
}
|
|
804
1025
|
// Remove imports if possible.
|
|
805
|
-
for (const [file, types] of
|
|
1026
|
+
for (const [file, types] of filesWithSourceQueries) {
|
|
806
1027
|
let seenIncompatibleMultiQuery = false;
|
|
807
1028
|
for (const type of types) {
|
|
808
1029
|
const incompatibleQueryTypesForFile = filesWithIncompleteMigration.get(file);
|
|
@@ -814,16 +1035,52 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
814
1035
|
seenIncompatibleMultiQuery = true;
|
|
815
1036
|
}
|
|
816
1037
|
}
|
|
817
|
-
if (!seenIncompatibleMultiQuery && !
|
|
1038
|
+
if (!seenIncompatibleMultiQuery && !filesWithQueryListOutsideOfDeclarations.has(file)) {
|
|
818
1039
|
importManager.removeImport(file, 'QueryList', '@angular/core');
|
|
819
1040
|
}
|
|
820
1041
|
}
|
|
821
|
-
|
|
822
|
-
return replacements;
|
|
1042
|
+
combine_units.applyImportManagerChanges(importManager, replacements, sourceFiles, info);
|
|
1043
|
+
return { replacements, knownQueries };
|
|
823
1044
|
}
|
|
824
1045
|
async stats(globalMetadata) {
|
|
825
|
-
|
|
826
|
-
|
|
1046
|
+
let queriesCount = 0;
|
|
1047
|
+
let multiQueries = 0;
|
|
1048
|
+
let incompatibleQueries = 0;
|
|
1049
|
+
const fieldIncompatibleCounts = {};
|
|
1050
|
+
const classIncompatibleCounts = {};
|
|
1051
|
+
for (const query of Object.values(globalMetadata.knownQueryFields)) {
|
|
1052
|
+
queriesCount++;
|
|
1053
|
+
if (query.isMulti) {
|
|
1054
|
+
multiQueries++;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
for (const [id, info] of Object.entries(globalMetadata.problematicQueries)) {
|
|
1058
|
+
if (globalMetadata.knownQueryFields[id] === undefined) {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
incompatibleQueries++;
|
|
1062
|
+
if (info.classReason !== null) {
|
|
1063
|
+
const reasonName = migrate_ts_type_references.ClassIncompatibilityReason[info.classReason];
|
|
1064
|
+
const key = `incompat-class-${reasonName}`;
|
|
1065
|
+
classIncompatibleCounts[key] ??= 0;
|
|
1066
|
+
classIncompatibleCounts[key]++;
|
|
1067
|
+
}
|
|
1068
|
+
if (info.fieldReason !== null) {
|
|
1069
|
+
const reasonName = migrate_ts_type_references.FieldIncompatibilityReason[info.fieldReason];
|
|
1070
|
+
const key = `incompat-field-${reasonName}`;
|
|
1071
|
+
fieldIncompatibleCounts[key] ??= 0;
|
|
1072
|
+
fieldIncompatibleCounts[key]++;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return {
|
|
1076
|
+
counters: {
|
|
1077
|
+
queriesCount,
|
|
1078
|
+
multiQueries,
|
|
1079
|
+
incompatibleQueries,
|
|
1080
|
+
...fieldIncompatibleCounts,
|
|
1081
|
+
...classIncompatibleCounts,
|
|
1082
|
+
},
|
|
1083
|
+
};
|
|
827
1084
|
}
|
|
828
1085
|
}
|
|
829
1086
|
/**
|
|
@@ -843,11 +1100,13 @@ function migrate(options) {
|
|
|
843
1100
|
return async (tree, context) => {
|
|
844
1101
|
const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(tree);
|
|
845
1102
|
if (!buildPaths.length && !testPaths.length) {
|
|
846
|
-
throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run signal
|
|
1103
|
+
throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run signal queries migration.');
|
|
847
1104
|
}
|
|
848
|
-
const fs = new
|
|
1105
|
+
const fs = new combine_units.DevkitMigrationFilesystem(tree);
|
|
849
1106
|
checker.setFileSystem(fs);
|
|
850
1107
|
const migration = new SignalQueriesMigration({
|
|
1108
|
+
bestEffortMode: options.bestEffortMode,
|
|
1109
|
+
insertTodosForSkippedFields: options.insertTodos,
|
|
851
1110
|
shouldMigrateQuery: (_query, file) => {
|
|
852
1111
|
return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
|
|
853
1112
|
!/(^|\/)node_modules\//.test(file.rootRelativePath));
|
|
@@ -875,12 +1134,17 @@ function migrate(options) {
|
|
|
875
1134
|
context.logger.info(``);
|
|
876
1135
|
context.logger.info(`Processing analysis data between targets..`);
|
|
877
1136
|
context.logger.info(``);
|
|
878
|
-
const
|
|
1137
|
+
const combined = await combine_units.synchronouslyCombineUnitData(migration, unitResults);
|
|
1138
|
+
if (combined === null) {
|
|
1139
|
+
context.logger.error('Migration failed unexpectedly with no analysis data');
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const globalMeta = await migration.globalMeta(combined);
|
|
879
1143
|
const replacementsPerFile = new Map();
|
|
880
1144
|
for (const { info, tsconfigPath } of programInfos) {
|
|
881
1145
|
context.logger.info(`Migrating: ${tsconfigPath}..`);
|
|
882
|
-
const replacements = await migration.migrate(
|
|
883
|
-
const changesPerFile =
|
|
1146
|
+
const { replacements } = await migration.migrate(globalMeta, info);
|
|
1147
|
+
const changesPerFile = combine_units.groupReplacementsByFile(replacements);
|
|
884
1148
|
for (const [file, changes] of changesPerFile) {
|
|
885
1149
|
if (!replacementsPerFile.has(file)) {
|
|
886
1150
|
replacementsPerFile.set(file, changes);
|
|
@@ -899,6 +1163,19 @@ function migrate(options) {
|
|
|
899
1163
|
}
|
|
900
1164
|
context.logger.info('');
|
|
901
1165
|
context.logger.info(`Successfully migrated to signal queries 🎉`);
|
|
1166
|
+
const { counters: { queriesCount, incompatibleQueries, multiQueries }, } = await migration.stats(globalMeta);
|
|
1167
|
+
const migratedQueries = queriesCount - incompatibleQueries;
|
|
1168
|
+
context.logger.info('');
|
|
1169
|
+
context.logger.info(`Successfully migrated to signal queries 🎉`);
|
|
1170
|
+
context.logger.info(` -> Migrated ${migratedQueries}/${queriesCount} queries.`);
|
|
1171
|
+
if (incompatibleQueries > 0 && !options.insertTodos) {
|
|
1172
|
+
context.logger.warn(`To see why ${incompatibleQueries} queries couldn't be migrated`);
|
|
1173
|
+
context.logger.warn(`consider re-running with "--insert-todos" or "--best-effort-mode".`);
|
|
1174
|
+
}
|
|
1175
|
+
if (options.bestEffortMode) {
|
|
1176
|
+
context.logger.warn(`You ran with best effort mode. Manually verify all code ` +
|
|
1177
|
+
`works as intended, and fix where necessary.`);
|
|
1178
|
+
}
|
|
902
1179
|
};
|
|
903
1180
|
}
|
|
904
1181
|
|