@angular/core 19.0.0-next.9 → 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 +21506 -19585
- 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 +175 -114
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +556 -89
- package/package.json +1 -1
- package/primitives/event-dispatch/index.d.ts +7 -4
- package/primitives/signals/index.d.ts +3 -1
- package/rxjs-interop/index.d.ts +35 -4
- package/schematics/bundles/{checker-3b2ea20f.js → checker-2451e7c5.js} +2347 -1063
- package/schematics/bundles/{group_replacements-e1b5cbf8.js → combine_units-c52492ab.js} +1767 -2136
- package/schematics/bundles/{compiler_host-b4ba5a28.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-6534a30a.js → program-58424797.js} +1305 -447
- 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 +184 -312
- package/schematics/bundles/signal-queries-migration.js +401 -143
- 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 +1 -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
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,
|
|
@@ -137,7 +139,7 @@ function extractQueryListType(node) {
|
|
|
137
139
|
* --> read stays
|
|
138
140
|
* --> emitDistinctChangesOnly is gone!
|
|
139
141
|
*/
|
|
140
|
-
function computeReplacementsToMigrateQuery(node, metadata, importManager, info, printer) {
|
|
142
|
+
function computeReplacementsToMigrateQuery(node, metadata, importManager, info, printer, options, checker$1) {
|
|
141
143
|
const sf = node.getSourceFile();
|
|
142
144
|
let newQueryFn = importManager.addImport({
|
|
143
145
|
requestedFile: sf,
|
|
@@ -171,36 +173,56 @@ function computeReplacementsToMigrateQuery(node, metadata, importManager, info,
|
|
|
171
173
|
if (optionProperties.length > 0) {
|
|
172
174
|
args.push(ts__default["default"].factory.createObjectLiteralExpression(optionProperties));
|
|
173
175
|
}
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
|
|
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.
|
|
179
197
|
newQueryFn = ts__default["default"].factory.createPropertyAccessExpression(newQueryFn, 'required');
|
|
180
198
|
}
|
|
181
199
|
// If this query is still nullable (i.e. not required), attempt to remove
|
|
182
200
|
// explicit `undefined` types if possible.
|
|
183
|
-
if (!
|
|
184
|
-
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);
|
|
185
203
|
}
|
|
186
|
-
|
|
204
|
+
let locatorType = Array.isArray(metadata.queryInfo.predicate)
|
|
187
205
|
? null
|
|
188
206
|
: metadata.queryInfo.predicate.expression;
|
|
189
|
-
|
|
190
|
-
// If the type and the read type are matching, we can rely
|
|
191
|
-
//
|
|
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)`.
|
|
192
210
|
if (type !== undefined &&
|
|
193
|
-
|
|
194
|
-
ts__default["default"].isIdentifier(
|
|
211
|
+
resolvedReadType instanceof checker.WrappedNodeExpr &&
|
|
212
|
+
ts__default["default"].isIdentifier(resolvedReadType.node) &&
|
|
195
213
|
ts__default["default"].isTypeReferenceNode(type) &&
|
|
196
214
|
ts__default["default"].isIdentifier(type.typeName) &&
|
|
197
|
-
type.typeName.text ===
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
const call = ts__default["default"].factory.createCallExpression(newQueryFn,
|
|
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);
|
|
201
223
|
const updated = ts__default["default"].factory.createPropertyDeclaration([ts__default["default"].factory.createModifier(ts__default["default"].SyntaxKind.ReadonlyKeyword)], node.name, undefined, undefined, call);
|
|
202
224
|
return [
|
|
203
|
-
new
|
|
225
|
+
new combine_units.Replacement(combine_units.projectFile(node.getSourceFile(), info), new combine_units.TextUpdate({
|
|
204
226
|
position: node.getStart(),
|
|
205
227
|
end: node.getEnd(),
|
|
206
228
|
toInsert: printer.printNode(ts__default["default"].EmitHint.Unspecified, updated, sf),
|
|
@@ -238,7 +260,10 @@ function getUniqueIDForClassProperty(property, info) {
|
|
|
238
260
|
if (!ts__default["default"].isClassDeclaration(property.parent) || property.parent.name === undefined) {
|
|
239
261
|
return null;
|
|
240
262
|
}
|
|
241
|
-
|
|
263
|
+
if (property.name === undefined) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
const id = combine_units.projectFile(property.getSourceFile(), info).id.replace(/\.d\.ts$/, '.ts');
|
|
242
267
|
// Note: If a class is nested, there could be an ID clash.
|
|
243
268
|
// This is highly unlikely though, and this is not a problem because
|
|
244
269
|
// in such cases, there is even less chance there are any references to
|
|
@@ -252,7 +277,7 @@ function getUniqueIDForClassProperty(property, info) {
|
|
|
252
277
|
* returns its resolved metadata if possible.
|
|
253
278
|
*/
|
|
254
279
|
function extractSourceQueryDefinition(node, reflector, evaluator, info) {
|
|
255
|
-
if (!ts__default["default"].isPropertyDeclaration(node) ||
|
|
280
|
+
if ((!ts__default["default"].isPropertyDeclaration(node) && !ts__default["default"].isAccessor(node)) ||
|
|
256
281
|
!ts__default["default"].isClassDeclaration(node.parent) ||
|
|
257
282
|
node.parent.name === undefined ||
|
|
258
283
|
!ts__default["default"].isIdentifier(node.name)) {
|
|
@@ -284,32 +309,72 @@ function extractSourceQueryDefinition(node, reflector, evaluator, info) {
|
|
|
284
309
|
else {
|
|
285
310
|
throw new Error('Unexpected query decorator detected.');
|
|
286
311
|
}
|
|
287
|
-
|
|
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
|
+
}
|
|
288
323
|
return {
|
|
289
324
|
id,
|
|
290
325
|
kind,
|
|
291
326
|
args: decorator.args ?? [],
|
|
292
327
|
queryInfo,
|
|
293
|
-
node,
|
|
328
|
+
node: node,
|
|
329
|
+
fieldDecorators: decorators,
|
|
294
330
|
};
|
|
295
331
|
}
|
|
296
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
|
+
|
|
297
357
|
class KnownQueries {
|
|
298
|
-
|
|
358
|
+
info;
|
|
359
|
+
config;
|
|
360
|
+
globalMetadata;
|
|
361
|
+
classToQueryFields = new Map();
|
|
362
|
+
knownQueryIDs = new Map();
|
|
363
|
+
constructor(info, config, globalMetadata) {
|
|
299
364
|
this.info = info;
|
|
365
|
+
this.config = config;
|
|
300
366
|
this.globalMetadata = globalMetadata;
|
|
301
|
-
this.classToQueryFields = new Map();
|
|
302
|
-
this.knownQueryIDs = new Map();
|
|
303
367
|
}
|
|
304
368
|
isFieldIncompatible(descriptor) {
|
|
305
|
-
return this.
|
|
369
|
+
return this.getIncompatibilityForField(descriptor) !== null;
|
|
306
370
|
}
|
|
307
|
-
markFieldIncompatible(field) {
|
|
308
|
-
this.globalMetadata.problematicQueries
|
|
371
|
+
markFieldIncompatible(field, incompatibility) {
|
|
372
|
+
markFieldIncompatibleInMetadata(this.globalMetadata.problematicQueries, field.key, incompatibility.reason);
|
|
309
373
|
}
|
|
310
|
-
markClassIncompatible(node) {
|
|
374
|
+
markClassIncompatible(node, reason) {
|
|
311
375
|
this.classToQueryFields.get(node)?.forEach((f) => {
|
|
312
|
-
this.globalMetadata.problematicQueries[f.key]
|
|
376
|
+
this.globalMetadata.problematicQueries[f.key] ??= { classReason: null, fieldReason: null };
|
|
377
|
+
this.globalMetadata.problematicQueries[f.key].classReason = reason;
|
|
313
378
|
});
|
|
314
379
|
}
|
|
315
380
|
registerQueryField(queryField, id) {
|
|
@@ -321,6 +386,15 @@ class KnownQueries {
|
|
|
321
386
|
node: queryField,
|
|
322
387
|
});
|
|
323
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
|
+
}
|
|
324
398
|
}
|
|
325
399
|
attemptRetrieveDescriptorFromSymbol(symbol) {
|
|
326
400
|
const descriptor = getClassFieldDescriptorForSymbol(symbol, this.info);
|
|
@@ -339,16 +413,67 @@ class KnownQueries {
|
|
|
339
413
|
return Array.from(this.classToQueryFields.keys()).filter((c) => ts__default["default"].isClassDeclaration(c));
|
|
340
414
|
}
|
|
341
415
|
captureKnownFieldInheritanceRelationship(derived, parent) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
+
});
|
|
345
435
|
}
|
|
346
436
|
}
|
|
347
437
|
captureUnknownDerivedField(field) {
|
|
348
|
-
this.markFieldIncompatible(field
|
|
438
|
+
this.markFieldIncompatible(field, {
|
|
439
|
+
context: null,
|
|
440
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.OverriddenByDerivedClass,
|
|
441
|
+
});
|
|
349
442
|
}
|
|
350
443
|
captureUnknownParentField(field) {
|
|
351
|
-
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;
|
|
352
477
|
}
|
|
353
478
|
}
|
|
354
479
|
|
|
@@ -376,7 +501,7 @@ function queryFunctionNameToDecorator(name) {
|
|
|
376
501
|
* E.g. whether `<my-read>.toArray` is detected.
|
|
377
502
|
*/
|
|
378
503
|
function checkTsReferenceAccessesField(ref, fieldName) {
|
|
379
|
-
const accessNode =
|
|
504
|
+
const accessNode = combine_units.traverseAccess(ref.from.node);
|
|
380
505
|
// Check if the reference is part of a property access.
|
|
381
506
|
if (!ts__default["default"].isPropertyAccessExpression(accessNode.parent) ||
|
|
382
507
|
!ts__default["default"].isIdentifier(accessNode.parent.name)) {
|
|
@@ -444,21 +569,24 @@ function checkNonTsReferenceCallsField(ref, fieldName) {
|
|
|
444
569
|
return potentialCall;
|
|
445
570
|
}
|
|
446
571
|
|
|
447
|
-
function removeQueryListToArrayCall(ref, info, globalMetadata, replacements) {
|
|
448
|
-
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)) {
|
|
449
577
|
return;
|
|
450
578
|
}
|
|
451
579
|
if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
|
|
452
580
|
return;
|
|
453
581
|
}
|
|
454
582
|
// TS references.
|
|
455
|
-
if (
|
|
583
|
+
if (combine_units.isTsReference(ref)) {
|
|
456
584
|
const toArrayCallExpr = checkTsReferenceCallsField(ref, 'toArray');
|
|
457
585
|
if (toArrayCallExpr === null) {
|
|
458
586
|
return;
|
|
459
587
|
}
|
|
460
588
|
const toArrayExpr = toArrayCallExpr.expression;
|
|
461
|
-
replacements.push(new
|
|
589
|
+
replacements.push(new combine_units.Replacement(combine_units.projectFile(toArrayExpr.getSourceFile(), info), new combine_units.TextUpdate({
|
|
462
590
|
// Delete from expression end to call end. E.g. `.toArray(<..>)`.
|
|
463
591
|
position: toArrayExpr.expression.getEnd(),
|
|
464
592
|
end: toArrayCallExpr.getEnd(),
|
|
@@ -471,9 +599,9 @@ function removeQueryListToArrayCall(ref, info, globalMetadata, replacements) {
|
|
|
471
599
|
if (callExpr === null) {
|
|
472
600
|
return;
|
|
473
601
|
}
|
|
474
|
-
const file =
|
|
475
|
-
const offset =
|
|
476
|
-
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({
|
|
477
605
|
// Delete from expression end to call end. E.g. `.toArray(<..>)`.
|
|
478
606
|
position: offset + callExpr.receiver.receiver.sourceSpan.end,
|
|
479
607
|
end: offset + callExpr.sourceSpan.end,
|
|
@@ -481,20 +609,23 @@ function removeQueryListToArrayCall(ref, info, globalMetadata, replacements) {
|
|
|
481
609
|
})));
|
|
482
610
|
}
|
|
483
611
|
|
|
484
|
-
function replaceQueryListGetCall(ref, info, globalMetadata, replacements) {
|
|
485
|
-
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)) {
|
|
486
617
|
return;
|
|
487
618
|
}
|
|
488
619
|
if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
|
|
489
620
|
return;
|
|
490
621
|
}
|
|
491
|
-
if (
|
|
622
|
+
if (combine_units.isTsReference(ref)) {
|
|
492
623
|
const getCallExpr = checkTsReferenceCallsField(ref, 'get');
|
|
493
624
|
if (getCallExpr === null) {
|
|
494
625
|
return;
|
|
495
626
|
}
|
|
496
627
|
const getExpr = getCallExpr.expression;
|
|
497
|
-
replacements.push(new
|
|
628
|
+
replacements.push(new combine_units.Replacement(combine_units.projectFile(getExpr.getSourceFile(), info), new combine_units.TextUpdate({
|
|
498
629
|
position: getExpr.name.getStart(),
|
|
499
630
|
end: getExpr.name.getEnd(),
|
|
500
631
|
toInsert: 'at',
|
|
@@ -506,9 +637,9 @@ function replaceQueryListGetCall(ref, info, globalMetadata, replacements) {
|
|
|
506
637
|
if (callExpr === null) {
|
|
507
638
|
return;
|
|
508
639
|
}
|
|
509
|
-
const file =
|
|
510
|
-
const offset =
|
|
511
|
-
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({
|
|
512
643
|
position: offset + callExpr.receiver.nameSpan.start,
|
|
513
644
|
end: offset + callExpr.receiver.nameSpan.end,
|
|
514
645
|
toInsert: 'at',
|
|
@@ -524,7 +655,7 @@ const problematicQueryListMethods = [
|
|
|
524
655
|
'destroy',
|
|
525
656
|
];
|
|
526
657
|
function checkForIncompatibleQueryListAccesses(ref, result) {
|
|
527
|
-
if (
|
|
658
|
+
if (combine_units.isTsReference(ref)) {
|
|
528
659
|
for (const problematicFn of problematicQueryListMethods) {
|
|
529
660
|
const access = checkTsReferenceAccessesField(ref, problematicFn);
|
|
530
661
|
if (access !== null) {
|
|
@@ -533,7 +664,7 @@ function checkForIncompatibleQueryListAccesses(ref, result) {
|
|
|
533
664
|
}
|
|
534
665
|
}
|
|
535
666
|
}
|
|
536
|
-
if (
|
|
667
|
+
if (combine_units.isHostBindingReference(ref) || combine_units.isTemplateReference(ref)) {
|
|
537
668
|
for (const problematicFn of problematicQueryListMethods) {
|
|
538
669
|
const access = checkNonTsReferenceAccessesField(ref, problematicFn);
|
|
539
670
|
if (access !== null) {
|
|
@@ -548,19 +679,22 @@ const mapping = new Map([
|
|
|
548
679
|
['first', 'at(0)!'],
|
|
549
680
|
['last', 'at(-1)!'],
|
|
550
681
|
]);
|
|
551
|
-
function replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, replacements) {
|
|
552
|
-
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)) {
|
|
553
687
|
return;
|
|
554
688
|
}
|
|
555
689
|
if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
|
|
556
690
|
return;
|
|
557
691
|
}
|
|
558
|
-
if (
|
|
692
|
+
if (combine_units.isTsReference(ref)) {
|
|
559
693
|
const expr = checkTsReferenceAccessesField(ref, 'first') ?? checkTsReferenceAccessesField(ref, 'last');
|
|
560
694
|
if (expr === null) {
|
|
561
695
|
return;
|
|
562
696
|
}
|
|
563
|
-
replacements.push(new
|
|
697
|
+
replacements.push(new combine_units.Replacement(combine_units.projectFile(expr.getSourceFile(), info), new combine_units.TextUpdate({
|
|
564
698
|
position: expr.name.getStart(),
|
|
565
699
|
end: expr.name.getEnd(),
|
|
566
700
|
toInsert: mapping.get(expr.name.text),
|
|
@@ -572,16 +706,17 @@ function replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, repla
|
|
|
572
706
|
if (expr === null) {
|
|
573
707
|
return;
|
|
574
708
|
}
|
|
575
|
-
const file =
|
|
576
|
-
const offset =
|
|
577
|
-
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({
|
|
578
712
|
position: offset + expr.nameSpan.start,
|
|
579
713
|
end: offset + expr.nameSpan.end,
|
|
580
714
|
toInsert: mapping.get(expr.name),
|
|
581
715
|
})));
|
|
582
716
|
}
|
|
583
717
|
|
|
584
|
-
class SignalQueriesMigration extends
|
|
718
|
+
class SignalQueriesMigration extends combine_units.TsurgeComplexMigration {
|
|
719
|
+
config;
|
|
585
720
|
constructor(config = {}) {
|
|
586
721
|
super();
|
|
587
722
|
this.config = config;
|
|
@@ -600,21 +735,24 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
600
735
|
potentialProblematicReferenceForMultiQueries: {},
|
|
601
736
|
reusableAnalysisReferences: null,
|
|
602
737
|
};
|
|
603
|
-
const groupedAstVisitor = new
|
|
738
|
+
const groupedAstVisitor = new migrate_ts_type_references.GroupedTsAstVisitor(sourceFiles);
|
|
604
739
|
const referenceResult = { references: [] };
|
|
605
|
-
const classesWithFilteredQueries = new
|
|
740
|
+
const classesWithFilteredQueries = new Set();
|
|
606
741
|
const filteredQueriesForCompilationUnit = new Map();
|
|
607
742
|
const findQueryDefinitionsVisitor = (node) => {
|
|
608
743
|
const extractedQuery = extractSourceQueryDefinition(node, reflector, evaluator, info);
|
|
609
744
|
if (extractedQuery !== null) {
|
|
745
|
+
const queryNode = extractedQuery.node;
|
|
610
746
|
const descriptor = {
|
|
611
747
|
key: extractedQuery.id,
|
|
612
|
-
node:
|
|
748
|
+
node: queryNode,
|
|
613
749
|
};
|
|
614
|
-
const containingFile =
|
|
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.
|
|
615
753
|
if (this.config.shouldMigrateQuery === undefined ||
|
|
616
754
|
this.config.shouldMigrateQuery(descriptor, containingFile)) {
|
|
617
|
-
classesWithFilteredQueries.add(
|
|
755
|
+
classesWithFilteredQueries.add(queryNode.parent);
|
|
618
756
|
filteredQueriesForCompilationUnit.set(extractedQuery.id, {
|
|
619
757
|
fieldName: extractedQuery.queryInfo.propertyName,
|
|
620
758
|
});
|
|
@@ -623,20 +761,32 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
623
761
|
fieldName: extractedQuery.queryInfo.propertyName,
|
|
624
762
|
isMulti: extractedQuery.queryInfo.first === false,
|
|
625
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
|
+
}
|
|
626
784
|
}
|
|
627
785
|
};
|
|
786
|
+
this.config.reportProgressFn?.(20, 'Scanning for queries..');
|
|
628
787
|
groupedAstVisitor.register(findQueryDefinitionsVisitor);
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
// improved reference resolution.
|
|
632
|
-
this.config.reportProgressFn?.(20, 'Scanning for queries..');
|
|
633
|
-
groupedAstVisitor.execute();
|
|
634
|
-
this.config.reportProgressFn?.(30, 'Scanning for references..');
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
this.config.reportProgressFn?.(20, 'Scanning for queries and references..');
|
|
638
|
-
}
|
|
639
|
-
groupedAstVisitor.register(group_replacements.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, info.ngCompiler['resourceManager'], evaluator, templateTypeChecker, {
|
|
788
|
+
groupedAstVisitor.execute();
|
|
789
|
+
const allFieldsOrKnownQueries = {
|
|
640
790
|
// Note: We don't support cross-target migration of `Partial<T>` usages.
|
|
641
791
|
// This is an acceptable limitation for performance reasons.
|
|
642
792
|
shouldTrackClassReference: (node) => classesWithFilteredQueries.has(node),
|
|
@@ -650,35 +800,51 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
650
800
|
!filteredQueriesForCompilationUnit.has(descriptor.key)) {
|
|
651
801
|
return null;
|
|
652
802
|
}
|
|
653
|
-
// TODO: Also consider skipping if we know this cannot be a query.
|
|
654
|
-
// e.g. missing class decorators or some other checks.
|
|
655
803
|
// In batch mode, we eagerly, rather expensively, track all references.
|
|
656
804
|
// We don't know yet if something refers to a different query or not, so we
|
|
657
805
|
// eagerly detect such and later filter those problematic references that
|
|
658
806
|
// turned out to refer to queries (once we have the global metadata).
|
|
659
807
|
return descriptor;
|
|
660
808
|
},
|
|
661
|
-
}
|
|
809
|
+
};
|
|
810
|
+
groupedAstVisitor.register(combine_units.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, info.ngCompiler['resourceManager'], evaluator, templateTypeChecker, allFieldsOrKnownQueries,
|
|
662
811
|
// In non-batch mode, we know what inputs exist and can optimize the reference
|
|
663
812
|
// resolution significantly (for e.g. VSCode integration)— as we know what
|
|
664
813
|
// field names may be used to reference potential queries.
|
|
665
814
|
this.config.assumeNonBatch
|
|
666
815
|
? new Set(Array.from(filteredQueriesForCompilationUnit.values()).map((f) => f.fieldName))
|
|
667
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..');
|
|
668
834
|
groupedAstVisitor.execute();
|
|
669
835
|
// Determine incompatible queries based on problematic references
|
|
670
836
|
// we saw in TS code, templates or host bindings.
|
|
671
837
|
for (const ref of referenceResult.references) {
|
|
672
|
-
if (
|
|
673
|
-
res.potentialProblematicQueries
|
|
838
|
+
if (combine_units.isTsReference(ref) && ref.from.isWrite) {
|
|
839
|
+
markFieldIncompatibleInMetadata(res.potentialProblematicQueries, ref.target.key, migrate_ts_type_references.FieldIncompatibilityReason.WriteAssignment);
|
|
674
840
|
}
|
|
675
|
-
if ((
|
|
676
|
-
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);
|
|
677
843
|
}
|
|
678
844
|
// TODO: Remove this when we support signal narrowing in templates.
|
|
679
845
|
// https://github.com/angular/angular/pull/55456.
|
|
680
|
-
if (
|
|
681
|
-
res.potentialProblematicQueries
|
|
846
|
+
if (combine_units.isTemplateReference(ref) && ref.from.isLikelyPartOfNarrowing) {
|
|
847
|
+
markFieldIncompatibleInMetadata(res.potentialProblematicQueries, ref.target.key, migrate_ts_type_references.FieldIncompatibilityReason.PotentiallyNarrowedInTemplateButNoSupportYet);
|
|
682
848
|
}
|
|
683
849
|
// Check for other incompatible query list accesses.
|
|
684
850
|
checkForIncompatibleQueryListAccesses(ref, res);
|
|
@@ -686,34 +852,60 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
686
852
|
if (this.config.assumeNonBatch) {
|
|
687
853
|
res.reusableAnalysisReferences = referenceResult.references;
|
|
688
854
|
}
|
|
689
|
-
return
|
|
855
|
+
return combine_units.confirmAsSerializable(res);
|
|
690
856
|
}
|
|
691
|
-
async
|
|
692
|
-
const
|
|
857
|
+
async combine(unitA, unitB) {
|
|
858
|
+
const combined = {
|
|
693
859
|
knownQueryFields: {},
|
|
694
|
-
|
|
860
|
+
potentialProblematicQueries: {},
|
|
861
|
+
potentialProblematicReferenceForMultiQueries: {},
|
|
695
862
|
reusableAnalysisReferences: null,
|
|
696
863
|
};
|
|
697
|
-
for (const unit of
|
|
864
|
+
for (const unit of [unitA, unitB]) {
|
|
698
865
|
for (const [id, value] of Object.entries(unit.knownQueryFields)) {
|
|
699
|
-
|
|
866
|
+
combined.knownQueryFields[id] = value;
|
|
700
867
|
}
|
|
701
|
-
for (const id of Object.
|
|
702
|
-
|
|
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;
|
|
703
883
|
}
|
|
704
884
|
if (unit.reusableAnalysisReferences !== null) {
|
|
705
|
-
|
|
706
|
-
merged.reusableAnalysisReferences = unit.reusableAnalysisReferences;
|
|
885
|
+
combined.reusableAnalysisReferences = unit.reusableAnalysisReferences;
|
|
707
886
|
}
|
|
708
887
|
}
|
|
709
|
-
for (const unit of
|
|
888
|
+
for (const unit of [unitA, unitB]) {
|
|
710
889
|
for (const id of Object.keys(unit.potentialProblematicReferenceForMultiQueries)) {
|
|
711
|
-
if (
|
|
712
|
-
|
|
890
|
+
if (combined.knownQueryFields[id]?.isMulti) {
|
|
891
|
+
markFieldIncompatibleInMetadata(combined.potentialProblematicQueries, id, migrate_ts_type_references.FieldIncompatibilityReason.SignalQueries__QueryListProblematicFieldAccessed);
|
|
713
892
|
}
|
|
714
893
|
}
|
|
715
894
|
}
|
|
716
|
-
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);
|
|
717
909
|
}
|
|
718
910
|
async migrate(globalMetadata, info) {
|
|
719
911
|
assert__default["default"](info.ngCompiler !== null, 'Expected queries migration to have an Angular program.');
|
|
@@ -729,13 +921,9 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
729
921
|
const filesWithSourceQueries = new Map();
|
|
730
922
|
const filesWithIncompleteMigration = new Map();
|
|
731
923
|
const filesWithQueryListOutsideOfDeclarations = new WeakSet();
|
|
732
|
-
const knownQueries = new KnownQueries(info, globalMetadata);
|
|
924
|
+
const knownQueries = new KnownQueries(info, this.config, globalMetadata);
|
|
733
925
|
const referenceResult = { references: [] };
|
|
734
926
|
const sourceQueries = [];
|
|
735
|
-
const isMigratedQuery = (descriptor) => globalMetadata.knownQueryFields[descriptor.key] !== undefined &&
|
|
736
|
-
globalMetadata.problematicQueries[descriptor.key] === undefined &&
|
|
737
|
-
(this.config.shouldMigrateQuery === undefined ||
|
|
738
|
-
this.config.shouldMigrateQuery(descriptor, group_replacements.projectFile(descriptor.node.getSourceFile(), info)));
|
|
739
927
|
// Detect all queries in this unit.
|
|
740
928
|
const queryWholeProgramVisitor = (node) => {
|
|
741
929
|
// Detect all SOURCE queries and migrate them, if possible.
|
|
@@ -746,7 +934,8 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
746
934
|
return;
|
|
747
935
|
}
|
|
748
936
|
// Detect OTHER queries, inside `.d.ts`. Needed for reference resolution below.
|
|
749
|
-
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))) {
|
|
750
939
|
const classFieldID = getUniqueIDForClassProperty(node, info);
|
|
751
940
|
if (classFieldID !== null && globalMetadata.knownQueryFields[classFieldID] !== undefined) {
|
|
752
941
|
knownQueries.registerQueryField(node, classFieldID);
|
|
@@ -770,55 +959,68 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
770
959
|
// lookups below.
|
|
771
960
|
const fieldNamesToConsiderForReferenceLookup = new Set(Object.values(globalMetadata.knownQueryFields).map((f) => f.fieldName));
|
|
772
961
|
// Find all references.
|
|
773
|
-
const groupedAstVisitor = new
|
|
962
|
+
const groupedAstVisitor = new migrate_ts_type_references.GroupedTsAstVisitor(sourceFiles);
|
|
774
963
|
// Re-use previous reference result if available, instead of
|
|
775
964
|
// looking for references which is quite expensive.
|
|
776
965
|
if (globalMetadata.reusableAnalysisReferences !== null) {
|
|
777
966
|
referenceResult.references = globalMetadata.reusableAnalysisReferences;
|
|
778
967
|
}
|
|
779
968
|
else {
|
|
780
|
-
groupedAstVisitor.register(
|
|
969
|
+
groupedAstVisitor.register(combine_units.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, info.ngCompiler['resourceManager'], evaluator, templateTypeChecker, knownQueries, fieldNamesToConsiderForReferenceLookup, referenceResult).visitor);
|
|
781
970
|
}
|
|
782
|
-
const inheritanceGraph = new group_replacements.InheritanceGraph(checker$1).expensivePopulate(info.sourceFiles);
|
|
783
|
-
group_replacements.checkIncompatiblePatterns(inheritanceGraph, checker$1, groupedAstVisitor, knownQueries, () => knownQueries.getAllClassesWithQueries());
|
|
784
|
-
this.config.reportProgressFn?.(60, 'Checking for problematic patterns..');
|
|
785
|
-
groupedAstVisitor.execute();
|
|
786
971
|
// Check inheritance.
|
|
787
|
-
|
|
788
|
-
|
|
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, {
|
|
789
977
|
getFieldsForClass: (n) => knownQueries.getQueryFieldsOfClass(n) ?? [],
|
|
790
978
|
isClassWithKnownFields: (clazz) => knownQueries.getQueryFieldsOfClass(clazz) !== undefined,
|
|
791
979
|
});
|
|
980
|
+
this.config.reportProgressFn?.(70, 'Checking inheritance..');
|
|
981
|
+
groupedAstVisitor.execute();
|
|
982
|
+
if (this.config.bestEffortMode) {
|
|
983
|
+
filterBestEffortIncompatibilities(knownQueries);
|
|
984
|
+
}
|
|
792
985
|
this.config.reportProgressFn?.(80, 'Migrating queries..');
|
|
793
986
|
// Migrate declarations.
|
|
794
987
|
for (const extractedQuery of sourceQueries) {
|
|
795
988
|
const node = extractedQuery.node;
|
|
796
989
|
const sf = node.getSourceFile();
|
|
797
990
|
const descriptor = { key: extractedQuery.id, node: extractedQuery.node };
|
|
798
|
-
|
|
799
|
-
|
|
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
|
+
}
|
|
800
1001
|
updateFileState(filesWithIncompleteMigration, sf, extractedQuery.kind);
|
|
801
1002
|
continue;
|
|
802
1003
|
}
|
|
803
|
-
|
|
804
|
-
replacements.push(...computeReplacementsToMigrateQuery(node, extractedQuery, importManager, info, printer));
|
|
1004
|
+
replacements.push(...computeReplacementsToMigrateQuery(node, extractedQuery, importManager, info, printer, info.userOptions, checker$1));
|
|
805
1005
|
}
|
|
806
1006
|
// Migrate references.
|
|
807
1007
|
const referenceMigrationHost = {
|
|
808
1008
|
printer,
|
|
809
1009
|
replacements,
|
|
810
|
-
shouldMigrateReferencesToField: (field) =>
|
|
811
|
-
shouldMigrateReferencesToClass: (clazz) => !!knownQueries
|
|
1010
|
+
shouldMigrateReferencesToField: (field) => !knownQueries.isFieldIncompatible(field),
|
|
1011
|
+
shouldMigrateReferencesToClass: (clazz) => !!knownQueries
|
|
1012
|
+
.getQueryFieldsOfClass(clazz)
|
|
1013
|
+
?.some((q) => !knownQueries.isFieldIncompatible(q)),
|
|
812
1014
|
};
|
|
813
|
-
|
|
1015
|
+
migrate_ts_type_references.migrateTypeScriptReferences(referenceMigrationHost, referenceResult.references, checker$1, info);
|
|
814
1016
|
migrateTemplateReferences(referenceMigrationHost, referenceResult.references);
|
|
815
1017
|
migrateHostBindings(referenceMigrationHost, referenceResult.references, info);
|
|
816
|
-
|
|
1018
|
+
migrate_ts_type_references.migrateTypeScriptTypeReferences(referenceMigrationHost, referenceResult.references, importManager, info);
|
|
817
1019
|
// Fix problematic calls, like `QueryList#toArray`, or `QueryList#get`.
|
|
818
1020
|
for (const ref of referenceResult.references) {
|
|
819
|
-
removeQueryListToArrayCall(ref, info, globalMetadata, replacements);
|
|
820
|
-
replaceQueryListGetCall(ref, info, globalMetadata, replacements);
|
|
821
|
-
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);
|
|
822
1024
|
}
|
|
823
1025
|
// Remove imports if possible.
|
|
824
1026
|
for (const [file, types] of filesWithSourceQueries) {
|
|
@@ -837,12 +1039,48 @@ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
837
1039
|
importManager.removeImport(file, 'QueryList', '@angular/core');
|
|
838
1040
|
}
|
|
839
1041
|
}
|
|
840
|
-
|
|
1042
|
+
combine_units.applyImportManagerChanges(importManager, replacements, sourceFiles, info);
|
|
841
1043
|
return { replacements, knownQueries };
|
|
842
1044
|
}
|
|
843
1045
|
async stats(globalMetadata) {
|
|
844
|
-
|
|
845
|
-
|
|
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
|
+
};
|
|
846
1084
|
}
|
|
847
1085
|
}
|
|
848
1086
|
/**
|
|
@@ -862,11 +1100,13 @@ function migrate(options) {
|
|
|
862
1100
|
return async (tree, context) => {
|
|
863
1101
|
const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(tree);
|
|
864
1102
|
if (!buildPaths.length && !testPaths.length) {
|
|
865
|
-
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.');
|
|
866
1104
|
}
|
|
867
|
-
const fs = new
|
|
1105
|
+
const fs = new combine_units.DevkitMigrationFilesystem(tree);
|
|
868
1106
|
checker.setFileSystem(fs);
|
|
869
1107
|
const migration = new SignalQueriesMigration({
|
|
1108
|
+
bestEffortMode: options.bestEffortMode,
|
|
1109
|
+
insertTodosForSkippedFields: options.insertTodos,
|
|
870
1110
|
shouldMigrateQuery: (_query, file) => {
|
|
871
1111
|
return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
|
|
872
1112
|
!/(^|\/)node_modules\//.test(file.rootRelativePath));
|
|
@@ -894,12 +1134,17 @@ function migrate(options) {
|
|
|
894
1134
|
context.logger.info(``);
|
|
895
1135
|
context.logger.info(`Processing analysis data between targets..`);
|
|
896
1136
|
context.logger.info(``);
|
|
897
|
-
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);
|
|
898
1143
|
const replacementsPerFile = new Map();
|
|
899
1144
|
for (const { info, tsconfigPath } of programInfos) {
|
|
900
1145
|
context.logger.info(`Migrating: ${tsconfigPath}..`);
|
|
901
|
-
const { replacements } = await migration.migrate(
|
|
902
|
-
const changesPerFile =
|
|
1146
|
+
const { replacements } = await migration.migrate(globalMeta, info);
|
|
1147
|
+
const changesPerFile = combine_units.groupReplacementsByFile(replacements);
|
|
903
1148
|
for (const [file, changes] of changesPerFile) {
|
|
904
1149
|
if (!replacementsPerFile.has(file)) {
|
|
905
1150
|
replacementsPerFile.set(file, changes);
|
|
@@ -918,6 +1163,19 @@ function migrate(options) {
|
|
|
918
1163
|
}
|
|
919
1164
|
context.logger.info('');
|
|
920
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
|
+
}
|
|
921
1179
|
};
|
|
922
1180
|
}
|
|
923
1181
|
|