@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.
Files changed (42) hide show
  1. package/fesm2022/core.mjs +21591 -19590
  2. package/fesm2022/core.mjs.map +1 -1
  3. package/fesm2022/primitives/event-dispatch.mjs +71 -47
  4. package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
  5. package/fesm2022/primitives/signals.mjs +8 -6
  6. package/fesm2022/primitives/signals.mjs.map +1 -1
  7. package/fesm2022/rxjs-interop.mjs +90 -10
  8. package/fesm2022/rxjs-interop.mjs.map +1 -1
  9. package/fesm2022/testing.mjs +177 -114
  10. package/fesm2022/testing.mjs.map +1 -1
  11. package/index.d.ts +591 -101
  12. package/package.json +1 -1
  13. package/primitives/event-dispatch/index.d.ts +7 -4
  14. package/primitives/signals/index.d.ts +7 -1
  15. package/rxjs-interop/index.d.ts +35 -4
  16. package/schematics/bundles/{checker-e68dd7ce.js → checker-2451e7c5.js} +2464 -1132
  17. package/schematics/bundles/{group_replacements-472b2387.js → combine_units-c52492ab.js} +1964 -2207
  18. package/schematics/bundles/{compiler_host-9a4d0c2b.js → compiler_host-f54f8309.js} +2 -2
  19. package/schematics/bundles/control-flow-migration.js +3 -3
  20. package/schematics/bundles/explicit-standalone-flag.js +31 -11
  21. package/schematics/bundles/{imports-4ac08251.js → imports-44987700.js} +1 -1
  22. package/schematics/bundles/inject-migration.js +122 -48
  23. package/schematics/bundles/{leading_space-d190b83b.js → leading_space-6e7a8ec6.js} +1 -1
  24. package/schematics/bundles/migrate_ts_type_references-ab18a7c3.js +1463 -0
  25. package/schematics/bundles/{nodes-0e7d45ca.js → ng_decorators-3ad437d2.js} +2 -15
  26. package/schematics/bundles/nodes-ffdce442.js +27 -0
  27. package/schematics/bundles/output-migration.js +7450 -0
  28. package/schematics/bundles/pending-tasks.js +5 -5
  29. package/schematics/bundles/{program-105283c5.js → program-58424797.js} +1359 -455
  30. package/schematics/bundles/{project_tsconfig_paths-e9ccccbf.js → project_tsconfig_paths-6c9cde78.js} +1 -1
  31. package/schematics/bundles/provide-initializer.js +190 -0
  32. package/schematics/bundles/route-lazy-loading.js +4 -4
  33. package/schematics/bundles/signal-input-migration.js +197 -349
  34. package/schematics/bundles/signal-queries-migration.js +462 -185
  35. package/schematics/bundles/signals.js +54 -0
  36. package/schematics/bundles/standalone-migration.js +38 -20
  37. package/schematics/collection.json +11 -0
  38. package/schematics/migrations.json +7 -1
  39. package/schematics/ng-generate/output-migration/schema.json +19 -0
  40. package/schematics/ng-generate/signal-queries-migration/schema.json +11 -0
  41. package/schematics/ng-generate/signals/schema.json +65 -0
  42. package/testing/index.d.ts +3 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  /**
3
- * @license Angular v19.0.0-next.8
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-e9ccccbf.js');
13
- var group_replacements = require('./group_replacements-472b2387.js');
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-e68dd7ce.js');
17
- var program = require('./program-105283c5.js');
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 (!group_replacements.isHostBindingReference(reference)) {
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 group_replacements.Replacement(group_replacements.projectFile(bindingField.getSourceFile(), info), new group_replacements.TextUpdate({ position: readEndPos, end: readEndPos, toInsert: appendText })));
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 (!group_replacements.isTemplateReference(reference)) {
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 group_replacements.Replacement(reference.from.templateFile, new group_replacements.TextUpdate({
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
- // TODO: Can we consult, based on references and non-null assertions?
217
- const isIndicatedAsRequired = node.exclamationToken !== undefined;
218
- // If the query is required already via some indicators, and this is a "single"
219
- // query, use the available `.required` method.
220
- if (isIndicatedAsRequired && metadata.queryInfo.first) {
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 (!isIndicatedAsRequired && type !== undefined && ts__default["default"].isUnionTypeNode(type)) {
226
- type = group_replacements.removeFromUnionIfPossible(type, (v) => v.kind !== ts__default["default"].SyntaxKind.UndefinedKeyword);
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
- const locatorType = Array.isArray(metadata.queryInfo.predicate)
204
+ let locatorType = Array.isArray(metadata.queryInfo.predicate)
229
205
  ? null
230
206
  : metadata.queryInfo.predicate.expression;
231
- const readType = metadata.queryInfo.read ?? locatorType;
232
- // If the type and the read type are matching, we can rely on the TS generic
233
- // signature rather than repeating e.g. `viewChild<Button>(Button)`.
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
- readType instanceof checker.WrappedNodeExpr &&
236
- ts__default["default"].isIdentifier(readType.node) &&
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 === readType.node.text) {
240
- type = undefined;
241
- }
242
- const call = ts__default["default"].factory.createCallExpression(newQueryFn, type ? [type] : undefined, args);
243
- const updated = ts__default["default"].factory.updatePropertyDeclaration(node, [ts__default["default"].factory.createModifier(ts__default["default"].SyntaxKind.ReadonlyKeyword)], node.name, undefined, undefined, call);
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 group_replacements.Replacement(group_replacements.projectFile(node.getSourceFile(), info), new group_replacements.TextUpdate({
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
- const id = group_replacements.projectFile(property.getSourceFile(), info).id.replace(/\.d\.ts$/, '.ts');
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
- const queryInfo = program.extractDecoratorQueryMetadata(node, decorator.name, decorator.args ?? [], node.name.text, reflector, evaluator);
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
- constructor(info, globalMetadata) {
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.globalMetadata.problematicQueries[descriptor.key] !== undefined;
369
+ return this.getIncompatibilityForField(descriptor) !== null;
348
370
  }
349
- markFieldIncompatible(field) {
350
- this.globalMetadata.problematicQueries[field.key] = true;
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] = true;
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.add(id);
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
- if (this.isFieldIncompatible(parent) || this.isFieldIncompatible(derived)) {
385
- this.markFieldIncompatible(parent);
386
- this.markFieldIncompatible(derived);
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 = group_replacements.traverseAccess(ref.from.node);
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 (!group_replacements.isHostBindingReference(ref) && !group_replacements.isTemplateReference(ref) && !group_replacements.isTsReference(ref)) {
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 (group_replacements.isTsReference(ref)) {
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 group_replacements.Replacement(group_replacements.projectFile(toArrayExpr.getSourceFile(), info), new group_replacements.TextUpdate({
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 = group_replacements.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
517
- const offset = group_replacements.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
518
- replacements.push(new group_replacements.Replacement(file, new group_replacements.TextUpdate({
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 (!group_replacements.isHostBindingReference(ref) && !group_replacements.isTemplateReference(ref) && !group_replacements.isTsReference(ref)) {
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 (group_replacements.isTsReference(ref)) {
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 group_replacements.Replacement(group_replacements.projectFile(getExpr.getSourceFile(), info), new group_replacements.TextUpdate({
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 = group_replacements.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
552
- const offset = group_replacements.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
553
- replacements.push(new group_replacements.Replacement(file, new group_replacements.TextUpdate({
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 (group_replacements.isTsReference(ref)) {
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 (group_replacements.isHostBindingReference(ref) || group_replacements.isTemplateReference(ref)) {
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 (!group_replacements.isHostBindingReference(ref) && !group_replacements.isTemplateReference(ref) && !group_replacements.isTsReference(ref)) {
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 (group_replacements.isTsReference(ref)) {
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 group_replacements.Replacement(group_replacements.projectFile(expr.getSourceFile(), info), new group_replacements.TextUpdate({
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 = group_replacements.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
618
- const offset = group_replacements.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
619
- replacements.push(new group_replacements.Replacement(file, new group_replacements.TextUpdate({
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 group_replacements.TsurgeComplexMigration {
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 group_replacements.GroupedTsAstVisitor(sourceFiles);
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.register(group_replacements.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, info.ngCompiler['resourceManager'], evaluator, templateTypeChecker,
659
- // Eager, rather expensive tracking of all references.
660
- // We don't know yet if something refers to a different query or not, so we
661
- // eagerly detect such and later filter those problematic references that
662
- // turned out to refer to queries.
663
- // TODO: Consider skipping this extra work when running in non-batch mode.
664
- // TODO: Also consider skipping if we know this query cannot be part.
665
- {
666
- shouldTrackClassReference: (_class) => false,
667
- attemptRetrieveDescriptorFromSymbol: (s) => getClassFieldDescriptorForSymbol(s, info),
668
- }, null, referenceResult).visitor);
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 (group_replacements.isTsReference(ref) && ref.from.isWrite) {
674
- res.potentialProblematicQueries[ref.target.key] = true;
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 ((group_replacements.isTemplateReference(ref) || group_replacements.isHostBindingReference(ref)) && ref.from.isWrite) {
677
- res.potentialProblematicQueries[ref.target.key] = true;
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 (group_replacements.isTemplateReference(ref) && ref.from.isLikelyPartOfNarrowing) {
682
- res.potentialProblematicQueries[ref.target.key] = true;
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
- return group_replacements.confirmAsSerializable(res);
852
+ if (this.config.assumeNonBatch) {
853
+ res.reusableAnalysisReferences = referenceResult.references;
854
+ }
855
+ return combine_units.confirmAsSerializable(res);
688
856
  }
689
- async merge(units) {
690
- const merged = {
857
+ async combine(unitA, unitB) {
858
+ const combined = {
691
859
  knownQueryFields: {},
692
- problematicQueries: {},
860
+ potentialProblematicQueries: {},
861
+ potentialProblematicReferenceForMultiQueries: {},
862
+ reusableAnalysisReferences: null,
693
863
  };
694
- for (const unit of units) {
864
+ for (const unit of [unitA, unitB]) {
695
865
  for (const [id, value] of Object.entries(unit.knownQueryFields)) {
696
- merged.knownQueryFields[id] = value;
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
- for (const id of Object.keys(unit.potentialProblematicQueries)) {
699
- merged.problematicQueries[id] = true;
884
+ if (unit.reusableAnalysisReferences !== null) {
885
+ combined.reusableAnalysisReferences = unit.reusableAnalysisReferences;
700
886
  }
701
887
  }
702
- for (const unit of units) {
888
+ for (const unit of [unitA, unitB]) {
703
889
  for (const id of Object.keys(unit.potentialProblematicReferenceForMultiQueries)) {
704
- if (merged.knownQueryFields[id]?.isMulti) {
705
- merged.problematicQueries[id] = true;
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 group_replacements.confirmAsSerializable(merged);
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 } = await info.ngCompiler['ensureAnalyzed']();
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 filesWithMigratedQueries = new Map();
921
+ const filesWithSourceQueries = new Map();
723
922
  const filesWithIncompleteMigration = new Map();
724
- const filesWithUnrelatedQueryListImports = new WeakSet();
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
- filesWithUnrelatedQueryListImports.add(node.getSourceFile());
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 group_replacements.GroupedTsAstVisitor(sourceFiles);
766
- groupedAstVisitor.register(group_replacements.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, info.ngCompiler['resourceManager'], evaluator, templateTypeChecker, knownQueries, fieldNamesToConsiderForReferenceLookup, referenceResult).visitor);
767
- const inheritanceGraph = new group_replacements.InheritanceGraph(checker$1).expensivePopulate(info.sourceFiles);
768
- group_replacements.checkIncompatiblePatterns(inheritanceGraph, checker$1, groupedAstVisitor, knownQueries, () => knownQueries.getAllClassesWithQueries());
769
- groupedAstVisitor.execute();
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
- group_replacements.checkInheritanceOfKnownFields(inheritanceGraph, metaReader, knownQueries, {
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
- if (!isMigratedQuery(descriptor)) {
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
- updateFileState(filesWithMigratedQueries, sf, extractedQuery.kind);
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) => isMigratedQuery(field),
792
- shouldMigrateReferencesToClass: (clazz) => !!knownQueries.getQueryFieldsOfClass(clazz)?.some((q) => isMigratedQuery(q)),
1010
+ shouldMigrateReferencesToField: (field) => !knownQueries.isFieldIncompatible(field),
1011
+ shouldMigrateReferencesToClass: (clazz) => !!knownQueries
1012
+ .getQueryFieldsOfClass(clazz)
1013
+ ?.some((q) => !knownQueries.isFieldIncompatible(q)),
793
1014
  };
794
- group_replacements.migrateTypeScriptReferences(referenceMigrationHost, referenceResult.references, checker$1, info);
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 filesWithMigratedQueries) {
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 && !filesWithUnrelatedQueryListImports.has(file)) {
1038
+ if (!seenIncompatibleMultiQuery && !filesWithQueryListOutsideOfDeclarations.has(file)) {
818
1039
  importManager.removeImport(file, 'QueryList', '@angular/core');
819
1040
  }
820
1041
  }
821
- group_replacements.applyImportManagerChanges(importManager, replacements, sourceFiles, info);
822
- return replacements;
1042
+ combine_units.applyImportManagerChanges(importManager, replacements, sourceFiles, info);
1043
+ return { replacements, knownQueries };
823
1044
  }
824
1045
  async stats(globalMetadata) {
825
- // TODO: Add statistics.
826
- return { counters: {} };
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 input migration.');
1103
+ throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run signal queries migration.');
847
1104
  }
848
- const fs = new group_replacements.DevkitMigrationFilesystem(tree);
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 merged = await migration.merge(unitResults);
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(merged, info);
883
- const changesPerFile = group_replacements.groupReplacementsByFile(replacements);
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