@angular/core 19.0.0-next.7 → 19.0.0-next.9

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 (33) hide show
  1. package/LICENSE +21 -0
  2. package/fesm2022/core.mjs +363 -156
  3. package/fesm2022/core.mjs.map +1 -1
  4. package/fesm2022/primitives/event-dispatch.mjs +1 -1
  5. package/fesm2022/primitives/signals.mjs +1 -1
  6. package/fesm2022/primitives/signals.mjs.map +1 -1
  7. package/fesm2022/rxjs-interop.mjs +1 -1
  8. package/fesm2022/testing.mjs +6 -4
  9. package/fesm2022/testing.mjs.map +1 -1
  10. package/index.d.ts +107 -69
  11. package/package.json +1 -1
  12. package/primitives/event-dispatch/index.d.ts +1 -1
  13. package/primitives/signals/index.d.ts +5 -1
  14. package/rxjs-interop/index.d.ts +1 -1
  15. package/schematics/bundles/{checker-637eee78.js → checker-3b2ea20f.js} +136 -78
  16. package/schematics/bundles/{compiler_host-1e62b899.js → compiler_host-b4ba5a28.js} +2 -2
  17. package/schematics/bundles/control-flow-migration.js +3 -3
  18. package/schematics/bundles/explicit-standalone-flag.js +5 -5
  19. package/schematics/bundles/group_replacements-e1b5cbf8.js +31571 -0
  20. package/schematics/bundles/{imports-44987700.js → imports-4ac08251.js} +1 -1
  21. package/schematics/bundles/inject-migration.js +24 -8
  22. package/schematics/bundles/{leading_space-6e7a8ec6.js → leading_space-d190b83b.js} +1 -1
  23. package/schematics/bundles/{nodes-b12e919a.js → nodes-0e7d45ca.js} +2 -2
  24. package/schematics/bundles/pending-tasks.js +5 -5
  25. package/schematics/bundles/{program-893e3fe7.js → program-6534a30a.js} +120 -32
  26. package/schematics/bundles/{project_tsconfig_paths-6c9cde78.js → project_tsconfig_paths-e9ccccbf.js} +1 -1
  27. package/schematics/bundles/route-lazy-loading.js +4 -4
  28. package/schematics/bundles/signal-input-migration.js +386 -31727
  29. package/schematics/bundles/signal-queries-migration.js +924 -0
  30. package/schematics/bundles/standalone-migration.js +8 -8
  31. package/schematics/collection.json +6 -0
  32. package/schematics/ng-generate/signal-queries-migration/schema.json +19 -0
  33. package/testing/index.d.ts +3 -1
@@ -0,0 +1,924 @@
1
+ 'use strict';
2
+ /**
3
+ * @license Angular v19.0.0-next.9
4
+ * (c) 2010-2024 Google LLC. https://angular.io/
5
+ * License: MIT
6
+ */
7
+ 'use strict';
8
+
9
+ Object.defineProperty(exports, '__esModule', { value: true });
10
+
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-e1b5cbf8.js');
14
+ require('os');
15
+ var ts = require('typescript');
16
+ var checker = require('./checker-3b2ea20f.js');
17
+ var program = require('./program-6534a30a.js');
18
+ require('path');
19
+ var assert = require('assert');
20
+ require('@angular-devkit/core');
21
+ require('node:path/posix');
22
+ require('fs');
23
+ require('module');
24
+ require('url');
25
+
26
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
27
+
28
+ var ts__default = /*#__PURE__*/_interopDefaultLegacy(ts);
29
+ var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert);
30
+
31
+ /**
32
+ * Phase that migrates Angular host binding references to
33
+ * unwrap signals.
34
+ */
35
+ function migrateHostBindings(host, references, info) {
36
+ const seenReferences = new WeakMap();
37
+ for (const reference of references) {
38
+ // This pass only deals with host binding references.
39
+ if (!group_replacements.isHostBindingReference(reference)) {
40
+ continue;
41
+ }
42
+ // Skip references to incompatible inputs.
43
+ if (!host.shouldMigrateReferencesToField(reference.target)) {
44
+ continue;
45
+ }
46
+ const bindingField = reference.from.hostPropertyNode;
47
+ const expressionOffset = bindingField.getStart() + 1; // account for quotes.
48
+ const readEndPos = expressionOffset + reference.from.read.sourceSpan.end;
49
+ // Skip duplicate references. Can happen if the host object is shared.
50
+ if (seenReferences.get(bindingField)?.has(readEndPos)) {
51
+ continue;
52
+ }
53
+ if (seenReferences.has(bindingField)) {
54
+ seenReferences.get(bindingField).add(readEndPos);
55
+ }
56
+ else {
57
+ seenReferences.set(bindingField, new Set([readEndPos]));
58
+ }
59
+ // Expand shorthands like `{bla}` to `{bla: bla()}`.
60
+ const appendText = reference.from.isObjectShorthandExpression
61
+ ? `: ${reference.from.read.name}()`
62
+ : `()`;
63
+ host.replacements.push(new group_replacements.Replacement(group_replacements.projectFile(bindingField.getSourceFile(), info), new group_replacements.TextUpdate({ position: readEndPos, end: readEndPos, toInsert: appendText })));
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Phase that migrates Angular template references to
69
+ * unwrap signals.
70
+ */
71
+ function migrateTemplateReferences(host, references) {
72
+ const seenFileReferences = new Set();
73
+ for (const reference of references) {
74
+ // This pass only deals with HTML template references.
75
+ if (!group_replacements.isTemplateReference(reference)) {
76
+ continue;
77
+ }
78
+ // Skip references to incompatible inputs.
79
+ if (!host.shouldMigrateReferencesToField(reference.target)) {
80
+ continue;
81
+ }
82
+ // Skip duplicate references. E.g. if a template is shared.
83
+ const fileReferenceId = `${reference.from.templateFile.id}:${reference.from.read.sourceSpan.end}`;
84
+ if (seenFileReferences.has(fileReferenceId)) {
85
+ continue;
86
+ }
87
+ seenFileReferences.add(fileReferenceId);
88
+ // Expand shorthands like `{bla}` to `{bla: bla()}`.
89
+ const appendText = reference.from.isObjectShorthandExpression
90
+ ? `: ${reference.from.read.name}()`
91
+ : `()`;
92
+ host.replacements.push(new group_replacements.Replacement(reference.from.templateFile, new group_replacements.TextUpdate({
93
+ position: reference.from.read.sourceSpan.end,
94
+ end: reference.from.read.sourceSpan.end,
95
+ toInsert: appendText,
96
+ })));
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Extracts the type `T` of expressions referencing `QueryList<T>`.
102
+ */
103
+ function extractQueryListType(node) {
104
+ // Initializer variant of `new QueryList<T>()`.
105
+ if (ts__default["default"].isNewExpression(node) &&
106
+ ts__default["default"].isIdentifier(node.expression) &&
107
+ node.expression.text === 'QueryList') {
108
+ return node.typeArguments?.[0];
109
+ }
110
+ // Type variant of `: QueryList<T>`.
111
+ if (ts__default["default"].isTypeReferenceNode(node) &&
112
+ ts__default["default"].isIdentifier(node.typeName) &&
113
+ node.typeName.text === 'QueryList') {
114
+ return node.typeArguments?.[0];
115
+ }
116
+ return undefined;
117
+ }
118
+
119
+ /**
120
+ * A few notes on changes:
121
+ *
122
+ * @ViewChild()
123
+ * --> static is gone!
124
+ * --> read stays
125
+ *
126
+ * @ViewChildren()
127
+ * --> emitDistinctChangesOnly is gone!
128
+ * --> read stays
129
+ *
130
+ * @ContentChild()
131
+ * --> descendants stays
132
+ * --> read stays
133
+ * --> static is gone!
134
+ *
135
+ * @ContentChildren()
136
+ * --> descendants stays
137
+ * --> read stays
138
+ * --> emitDistinctChangesOnly is gone!
139
+ */
140
+ function computeReplacementsToMigrateQuery(node, metadata, importManager, info, printer) {
141
+ const sf = node.getSourceFile();
142
+ let newQueryFn = importManager.addImport({
143
+ requestedFile: sf,
144
+ exportModuleSpecifier: '@angular/core',
145
+ exportSymbolName: metadata.kind,
146
+ });
147
+ // The default value for descendants is `true`, except for `ContentChildren`.
148
+ const defaultDescendants = metadata.kind !== 'contentChildren';
149
+ const optionProperties = [];
150
+ const args = [
151
+ metadata.args[0], // Locator.
152
+ ];
153
+ let type = node.type;
154
+ // For multi queries, attempt to unwrap `QueryList` types, or infer the
155
+ // type from the initializer, if possible.
156
+ if (!metadata.queryInfo.first) {
157
+ if (type === undefined && node.initializer !== undefined) {
158
+ type = extractQueryListType(node.initializer);
159
+ }
160
+ else if (type !== undefined) {
161
+ type = extractQueryListType(type);
162
+ }
163
+ }
164
+ if (metadata.queryInfo.read !== null) {
165
+ assert__default["default"](metadata.queryInfo.read instanceof checker.WrappedNodeExpr);
166
+ optionProperties.push(ts__default["default"].factory.createPropertyAssignment('read', metadata.queryInfo.read.node));
167
+ }
168
+ if (metadata.queryInfo.descendants !== defaultDescendants) {
169
+ optionProperties.push(ts__default["default"].factory.createPropertyAssignment('descendants', metadata.queryInfo.descendants ? ts__default["default"].factory.createTrue() : ts__default["default"].factory.createFalse()));
170
+ }
171
+ if (optionProperties.length > 0) {
172
+ args.push(ts__default["default"].factory.createObjectLiteralExpression(optionProperties));
173
+ }
174
+ // TODO: Can we consult, based on references and non-null assertions?
175
+ const isIndicatedAsRequired = node.exclamationToken !== undefined;
176
+ // If the query is required already via some indicators, and this is a "single"
177
+ // query, use the available `.required` method.
178
+ if (isIndicatedAsRequired && metadata.queryInfo.first) {
179
+ newQueryFn = ts__default["default"].factory.createPropertyAccessExpression(newQueryFn, 'required');
180
+ }
181
+ // If this query is still nullable (i.e. not required), attempt to remove
182
+ // explicit `undefined` types if possible.
183
+ if (!isIndicatedAsRequired && type !== undefined && ts__default["default"].isUnionTypeNode(type)) {
184
+ type = group_replacements.removeFromUnionIfPossible(type, (v) => v.kind !== ts__default["default"].SyntaxKind.UndefinedKeyword);
185
+ }
186
+ const locatorType = Array.isArray(metadata.queryInfo.predicate)
187
+ ? null
188
+ : metadata.queryInfo.predicate.expression;
189
+ const readType = metadata.queryInfo.read ?? locatorType;
190
+ // If the type and the read type are matching, we can rely on the TS generic
191
+ // signature rather than repeating e.g. `viewChild<Button>(Button)`.
192
+ if (type !== undefined &&
193
+ readType instanceof checker.WrappedNodeExpr &&
194
+ ts__default["default"].isIdentifier(readType.node) &&
195
+ ts__default["default"].isTypeReferenceNode(type) &&
196
+ ts__default["default"].isIdentifier(type.typeName) &&
197
+ type.typeName.text === readType.node.text) {
198
+ type = undefined;
199
+ }
200
+ const call = ts__default["default"].factory.createCallExpression(newQueryFn, type ? [type] : undefined, args);
201
+ const updated = ts__default["default"].factory.createPropertyDeclaration([ts__default["default"].factory.createModifier(ts__default["default"].SyntaxKind.ReadonlyKeyword)], node.name, undefined, undefined, call);
202
+ return [
203
+ new group_replacements.Replacement(group_replacements.projectFile(node.getSourceFile(), info), new group_replacements.TextUpdate({
204
+ position: node.getStart(),
205
+ end: node.getEnd(),
206
+ toInsert: printer.printNode(ts__default["default"].EmitHint.Unspecified, updated, sf),
207
+ })),
208
+ ];
209
+ }
210
+
211
+ /**
212
+ * Attempts to get a class field descriptor if the given symbol
213
+ * points to a class field.
214
+ */
215
+ function getClassFieldDescriptorForSymbol(symbol, info) {
216
+ if (symbol?.valueDeclaration === undefined ||
217
+ !ts__default["default"].isPropertyDeclaration(symbol.valueDeclaration)) {
218
+ return null;
219
+ }
220
+ const key = getUniqueIDForClassProperty(symbol.valueDeclaration, info);
221
+ if (key === null) {
222
+ return null;
223
+ }
224
+ return {
225
+ key,
226
+ node: symbol.valueDeclaration,
227
+ };
228
+ }
229
+ /**
230
+ * Gets a unique ID for the given class property.
231
+ *
232
+ * This is useful for matching class fields across compilation units.
233
+ * E.g. a reference may point to the field via `.d.ts`, while the other
234
+ * may reference it via actual `.ts` sources. IDs for the same fields
235
+ * would then match identity.
236
+ */
237
+ function getUniqueIDForClassProperty(property, info) {
238
+ if (!ts__default["default"].isClassDeclaration(property.parent) || property.parent.name === undefined) {
239
+ return null;
240
+ }
241
+ const id = group_replacements.projectFile(property.getSourceFile(), info).id.replace(/\.d\.ts$/, '.ts');
242
+ // Note: If a class is nested, there could be an ID clash.
243
+ // This is highly unlikely though, and this is not a problem because
244
+ // in such cases, there is even less chance there are any references to
245
+ // a non-exported classes; in which case, cross-compilation unit references
246
+ // likely can't exist anyway.
247
+ return `${id}-${property.parent.name.text}-${property.name.getText()}`;
248
+ }
249
+
250
+ /**
251
+ * Determines if the given node refers to a decorator-based query, and
252
+ * returns its resolved metadata if possible.
253
+ */
254
+ function extractSourceQueryDefinition(node, reflector, evaluator, info) {
255
+ if (!ts__default["default"].isPropertyDeclaration(node) ||
256
+ !ts__default["default"].isClassDeclaration(node.parent) ||
257
+ node.parent.name === undefined ||
258
+ !ts__default["default"].isIdentifier(node.name)) {
259
+ return null;
260
+ }
261
+ const decorators = reflector.getDecoratorsOfDeclaration(node) ?? [];
262
+ const ngDecorators = checker.getAngularDecorators(decorators, program.queryDecoratorNames, /* isCore */ false);
263
+ if (ngDecorators.length === 0) {
264
+ return null;
265
+ }
266
+ const decorator = ngDecorators[0];
267
+ const id = getUniqueIDForClassProperty(node, info);
268
+ if (id === null) {
269
+ return null;
270
+ }
271
+ let kind;
272
+ if (decorator.name === 'ViewChild') {
273
+ kind = 'viewChild';
274
+ }
275
+ else if (decorator.name === 'ViewChildren') {
276
+ kind = 'viewChildren';
277
+ }
278
+ else if (decorator.name === 'ContentChild') {
279
+ kind = 'contentChild';
280
+ }
281
+ else if (decorator.name === 'ContentChildren') {
282
+ kind = 'contentChildren';
283
+ }
284
+ else {
285
+ throw new Error('Unexpected query decorator detected.');
286
+ }
287
+ const queryInfo = program.extractDecoratorQueryMetadata(node, decorator.name, decorator.args ?? [], node.name.text, reflector, evaluator);
288
+ return {
289
+ id,
290
+ kind,
291
+ args: decorator.args ?? [],
292
+ queryInfo,
293
+ node,
294
+ };
295
+ }
296
+
297
+ class KnownQueries {
298
+ constructor(info, globalMetadata) {
299
+ this.info = info;
300
+ this.globalMetadata = globalMetadata;
301
+ this.classToQueryFields = new Map();
302
+ this.knownQueryIDs = new Map();
303
+ }
304
+ isFieldIncompatible(descriptor) {
305
+ return this.globalMetadata.problematicQueries[descriptor.key] !== undefined;
306
+ }
307
+ markFieldIncompatible(field) {
308
+ this.globalMetadata.problematicQueries[field.key] = true;
309
+ }
310
+ markClassIncompatible(node) {
311
+ this.classToQueryFields.get(node)?.forEach((f) => {
312
+ this.globalMetadata.problematicQueries[f.key] = true;
313
+ });
314
+ }
315
+ registerQueryField(queryField, id) {
316
+ if (!this.classToQueryFields.has(queryField.parent)) {
317
+ this.classToQueryFields.set(queryField.parent, []);
318
+ }
319
+ this.classToQueryFields.get(queryField.parent).push({
320
+ key: id,
321
+ node: queryField,
322
+ });
323
+ this.knownQueryIDs.set(id, { key: id, node: queryField });
324
+ }
325
+ attemptRetrieveDescriptorFromSymbol(symbol) {
326
+ const descriptor = getClassFieldDescriptorForSymbol(symbol, this.info);
327
+ if (descriptor !== null && this.knownQueryIDs.has(descriptor.key)) {
328
+ return descriptor;
329
+ }
330
+ return null;
331
+ }
332
+ shouldTrackClassReference(clazz) {
333
+ return this.classToQueryFields.has(clazz);
334
+ }
335
+ getQueryFieldsOfClass(clazz) {
336
+ return this.classToQueryFields.get(clazz);
337
+ }
338
+ getAllClassesWithQueries() {
339
+ return Array.from(this.classToQueryFields.keys()).filter((c) => ts__default["default"].isClassDeclaration(c));
340
+ }
341
+ captureKnownFieldInheritanceRelationship(derived, parent) {
342
+ if (this.isFieldIncompatible(parent) || this.isFieldIncompatible(derived)) {
343
+ this.markFieldIncompatible(parent);
344
+ this.markFieldIncompatible(derived);
345
+ }
346
+ }
347
+ captureUnknownDerivedField(field) {
348
+ this.markFieldIncompatible(field);
349
+ }
350
+ captureUnknownParentField(field) {
351
+ this.markFieldIncompatible(field);
352
+ }
353
+ }
354
+
355
+ /** Converts an initializer query API name to its decorator-equivalent. */
356
+ function queryFunctionNameToDecorator(name) {
357
+ if (name === 'viewChild') {
358
+ return 'ViewChild';
359
+ }
360
+ else if (name === 'viewChildren') {
361
+ return 'ViewChildren';
362
+ }
363
+ else if (name === 'contentChild') {
364
+ return 'ContentChild';
365
+ }
366
+ else if (name === 'contentChildren') {
367
+ return 'ContentChildren';
368
+ }
369
+ throw new Error(`Unexpected query function name: ${name}`);
370
+ }
371
+
372
+ /**
373
+ * Gets whether the given field is accessed via the
374
+ * given reference.
375
+ *
376
+ * E.g. whether `<my-read>.toArray` is detected.
377
+ */
378
+ function checkTsReferenceAccessesField(ref, fieldName) {
379
+ const accessNode = group_replacements.traverseAccess(ref.from.node);
380
+ // Check if the reference is part of a property access.
381
+ if (!ts__default["default"].isPropertyAccessExpression(accessNode.parent) ||
382
+ !ts__default["default"].isIdentifier(accessNode.parent.name)) {
383
+ return null;
384
+ }
385
+ // Check if the reference is refers to the given field name.
386
+ if (accessNode.parent.name.text !== fieldName) {
387
+ return null;
388
+ }
389
+ return accessNode.parent;
390
+ }
391
+ /**
392
+ * Gets whether the given read is used to access
393
+ * the specified field.
394
+ *
395
+ * E.g. whether `<my-read>.toArray` is detected.
396
+ */
397
+ function checkNonTsReferenceAccessesField(ref, fieldName) {
398
+ const readFromPath = ref.from.readAstPath.at(-1);
399
+ const parentRead = ref.from.readAstPath.at(-2);
400
+ if (ref.from.read !== readFromPath) {
401
+ return null;
402
+ }
403
+ if (!(parentRead instanceof checker.PropertyRead) || parentRead.name !== fieldName) {
404
+ return null;
405
+ }
406
+ return parentRead;
407
+ }
408
+ /**
409
+ * Gets whether the given reference is accessed to call the
410
+ * specified function on it.
411
+ *
412
+ * E.g. whether `<my-read>.toArray()` is detected.
413
+ */
414
+ function checkTsReferenceCallsField(ref, fieldName) {
415
+ const propertyAccess = checkTsReferenceAccessesField(ref, fieldName);
416
+ if (propertyAccess === null) {
417
+ return null;
418
+ }
419
+ if (ts__default["default"].isCallExpression(propertyAccess.parent) &&
420
+ propertyAccess.parent.expression === propertyAccess) {
421
+ return propertyAccess.parent;
422
+ }
423
+ return null;
424
+ }
425
+ /**
426
+ * Gets whether the given reference is accessed to call the
427
+ * specified function on it.
428
+ *
429
+ * E.g. whether `<my-read>.toArray()` is detected.
430
+ */
431
+ function checkNonTsReferenceCallsField(ref, fieldName) {
432
+ const propertyAccess = checkNonTsReferenceAccessesField(ref, fieldName);
433
+ if (propertyAccess === null) {
434
+ return null;
435
+ }
436
+ const accessIdx = ref.from.readAstPath.indexOf(propertyAccess);
437
+ if (accessIdx === -1) {
438
+ return null;
439
+ }
440
+ const potentialCall = ref.from.readAstPath[accessIdx - 1];
441
+ if (potentialCall === undefined || !(potentialCall instanceof checker.Call)) {
442
+ return null;
443
+ }
444
+ return potentialCall;
445
+ }
446
+
447
+ function removeQueryListToArrayCall(ref, info, globalMetadata, replacements) {
448
+ if (!group_replacements.isHostBindingReference(ref) && !group_replacements.isTemplateReference(ref) && !group_replacements.isTsReference(ref)) {
449
+ return;
450
+ }
451
+ if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
452
+ return;
453
+ }
454
+ // TS references.
455
+ if (group_replacements.isTsReference(ref)) {
456
+ const toArrayCallExpr = checkTsReferenceCallsField(ref, 'toArray');
457
+ if (toArrayCallExpr === null) {
458
+ return;
459
+ }
460
+ const toArrayExpr = toArrayCallExpr.expression;
461
+ replacements.push(new group_replacements.Replacement(group_replacements.projectFile(toArrayExpr.getSourceFile(), info), new group_replacements.TextUpdate({
462
+ // Delete from expression end to call end. E.g. `.toArray(<..>)`.
463
+ position: toArrayExpr.expression.getEnd(),
464
+ end: toArrayCallExpr.getEnd(),
465
+ toInsert: '',
466
+ })));
467
+ return;
468
+ }
469
+ // Template and host binding references.
470
+ const callExpr = checkNonTsReferenceCallsField(ref, 'toArray');
471
+ if (callExpr === null) {
472
+ return;
473
+ }
474
+ const file = group_replacements.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
475
+ const offset = group_replacements.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
476
+ replacements.push(new group_replacements.Replacement(file, new group_replacements.TextUpdate({
477
+ // Delete from expression end to call end. E.g. `.toArray(<..>)`.
478
+ position: offset + callExpr.receiver.receiver.sourceSpan.end,
479
+ end: offset + callExpr.sourceSpan.end,
480
+ toInsert: '',
481
+ })));
482
+ }
483
+
484
+ function replaceQueryListGetCall(ref, info, globalMetadata, replacements) {
485
+ if (!group_replacements.isHostBindingReference(ref) && !group_replacements.isTemplateReference(ref) && !group_replacements.isTsReference(ref)) {
486
+ return;
487
+ }
488
+ if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
489
+ return;
490
+ }
491
+ if (group_replacements.isTsReference(ref)) {
492
+ const getCallExpr = checkTsReferenceCallsField(ref, 'get');
493
+ if (getCallExpr === null) {
494
+ return;
495
+ }
496
+ const getExpr = getCallExpr.expression;
497
+ replacements.push(new group_replacements.Replacement(group_replacements.projectFile(getExpr.getSourceFile(), info), new group_replacements.TextUpdate({
498
+ position: getExpr.name.getStart(),
499
+ end: getExpr.name.getEnd(),
500
+ toInsert: 'at',
501
+ })));
502
+ return;
503
+ }
504
+ // Template and host binding references.
505
+ const callExpr = checkNonTsReferenceCallsField(ref, 'get');
506
+ if (callExpr === null) {
507
+ return;
508
+ }
509
+ const file = group_replacements.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
510
+ const offset = group_replacements.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
511
+ replacements.push(new group_replacements.Replacement(file, new group_replacements.TextUpdate({
512
+ position: offset + callExpr.receiver.nameSpan.start,
513
+ end: offset + callExpr.receiver.nameSpan.end,
514
+ toInsert: 'at',
515
+ })));
516
+ }
517
+
518
+ const problematicQueryListMethods = [
519
+ 'dirty',
520
+ 'changes',
521
+ 'setDirty',
522
+ 'reset',
523
+ 'notifyOnChanges',
524
+ 'destroy',
525
+ ];
526
+ function checkForIncompatibleQueryListAccesses(ref, result) {
527
+ if (group_replacements.isTsReference(ref)) {
528
+ for (const problematicFn of problematicQueryListMethods) {
529
+ const access = checkTsReferenceAccessesField(ref, problematicFn);
530
+ if (access !== null) {
531
+ result.potentialProblematicReferenceForMultiQueries[ref.target.key] = true;
532
+ return;
533
+ }
534
+ }
535
+ }
536
+ if (group_replacements.isHostBindingReference(ref) || group_replacements.isTemplateReference(ref)) {
537
+ for (const problematicFn of problematicQueryListMethods) {
538
+ const access = checkNonTsReferenceAccessesField(ref, problematicFn);
539
+ if (access !== null) {
540
+ result.potentialProblematicReferenceForMultiQueries[ref.target.key] = true;
541
+ return;
542
+ }
543
+ }
544
+ }
545
+ }
546
+
547
+ const mapping = new Map([
548
+ ['first', 'at(0)!'],
549
+ ['last', 'at(-1)!'],
550
+ ]);
551
+ function replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, replacements) {
552
+ if (!group_replacements.isHostBindingReference(ref) && !group_replacements.isTemplateReference(ref) && !group_replacements.isTsReference(ref)) {
553
+ return;
554
+ }
555
+ if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
556
+ return;
557
+ }
558
+ if (group_replacements.isTsReference(ref)) {
559
+ const expr = checkTsReferenceAccessesField(ref, 'first') ?? checkTsReferenceAccessesField(ref, 'last');
560
+ if (expr === null) {
561
+ return;
562
+ }
563
+ replacements.push(new group_replacements.Replacement(group_replacements.projectFile(expr.getSourceFile(), info), new group_replacements.TextUpdate({
564
+ position: expr.name.getStart(),
565
+ end: expr.name.getEnd(),
566
+ toInsert: mapping.get(expr.name.text),
567
+ })));
568
+ return;
569
+ }
570
+ // Template and host binding references.
571
+ const expr = checkNonTsReferenceAccessesField(ref, 'first') ?? checkNonTsReferenceAccessesField(ref, 'last');
572
+ if (expr === null) {
573
+ return;
574
+ }
575
+ const file = group_replacements.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
576
+ const offset = group_replacements.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
577
+ replacements.push(new group_replacements.Replacement(file, new group_replacements.TextUpdate({
578
+ position: offset + expr.nameSpan.start,
579
+ end: offset + expr.nameSpan.end,
580
+ toInsert: mapping.get(expr.name),
581
+ })));
582
+ }
583
+
584
+ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
585
+ constructor(config = {}) {
586
+ super();
587
+ this.config = config;
588
+ }
589
+ async analyze(info) {
590
+ assert__default["default"](info.ngCompiler !== null, 'Expected queries migration to have an Angular program.');
591
+ // Pre-Analyze the program and get access to the template type checker.
592
+ const { templateTypeChecker } = info.ngCompiler['ensureAnalyzed']();
593
+ const { sourceFiles, program: program$1 } = info;
594
+ const checker$1 = program$1.getTypeChecker();
595
+ const reflector = new checker.TypeScriptReflectionHost(checker$1);
596
+ const evaluator = new program.PartialEvaluator(reflector, checker$1, null);
597
+ const res = {
598
+ knownQueryFields: {},
599
+ potentialProblematicQueries: {},
600
+ potentialProblematicReferenceForMultiQueries: {},
601
+ reusableAnalysisReferences: null,
602
+ };
603
+ const groupedAstVisitor = new group_replacements.GroupedTsAstVisitor(sourceFiles);
604
+ const referenceResult = { references: [] };
605
+ const classesWithFilteredQueries = new WeakSet();
606
+ const filteredQueriesForCompilationUnit = new Map();
607
+ const findQueryDefinitionsVisitor = (node) => {
608
+ const extractedQuery = extractSourceQueryDefinition(node, reflector, evaluator, info);
609
+ if (extractedQuery !== null) {
610
+ const descriptor = {
611
+ key: extractedQuery.id,
612
+ node: extractedQuery.node,
613
+ };
614
+ const containingFile = group_replacements.projectFile(descriptor.node.getSourceFile(), info);
615
+ if (this.config.shouldMigrateQuery === undefined ||
616
+ this.config.shouldMigrateQuery(descriptor, containingFile)) {
617
+ classesWithFilteredQueries.add(extractedQuery.node.parent);
618
+ filteredQueriesForCompilationUnit.set(extractedQuery.id, {
619
+ fieldName: extractedQuery.queryInfo.propertyName,
620
+ });
621
+ }
622
+ res.knownQueryFields[extractedQuery.id] = {
623
+ fieldName: extractedQuery.queryInfo.propertyName,
624
+ isMulti: extractedQuery.queryInfo.first === false,
625
+ };
626
+ }
627
+ };
628
+ groupedAstVisitor.register(findQueryDefinitionsVisitor);
629
+ if (this.config.assumeNonBatch) {
630
+ // In non-batch, we need to find queries before, so we can perform
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, {
640
+ // Note: We don't support cross-target migration of `Partial<T>` usages.
641
+ // This is an acceptable limitation for performance reasons.
642
+ shouldTrackClassReference: (node) => classesWithFilteredQueries.has(node),
643
+ attemptRetrieveDescriptorFromSymbol: (s) => {
644
+ const descriptor = getClassFieldDescriptorForSymbol(s, info);
645
+ // If we are executing in upgraded analysis phase mode, we know all
646
+ // of the queries since there aren't any other compilation units.
647
+ // Ignore references to non-query class fields.
648
+ if (this.config.assumeNonBatch &&
649
+ descriptor !== null &&
650
+ !filteredQueriesForCompilationUnit.has(descriptor.key)) {
651
+ return null;
652
+ }
653
+ // TODO: Also consider skipping if we know this cannot be a query.
654
+ // e.g. missing class decorators or some other checks.
655
+ // In batch mode, we eagerly, rather expensively, track all references.
656
+ // We don't know yet if something refers to a different query or not, so we
657
+ // eagerly detect such and later filter those problematic references that
658
+ // turned out to refer to queries (once we have the global metadata).
659
+ return descriptor;
660
+ },
661
+ },
662
+ // In non-batch mode, we know what inputs exist and can optimize the reference
663
+ // resolution significantly (for e.g. VSCode integration)— as we know what
664
+ // field names may be used to reference potential queries.
665
+ this.config.assumeNonBatch
666
+ ? new Set(Array.from(filteredQueriesForCompilationUnit.values()).map((f) => f.fieldName))
667
+ : null, referenceResult).visitor);
668
+ groupedAstVisitor.execute();
669
+ // Determine incompatible queries based on problematic references
670
+ // we saw in TS code, templates or host bindings.
671
+ for (const ref of referenceResult.references) {
672
+ if (group_replacements.isTsReference(ref) && ref.from.isWrite) {
673
+ res.potentialProblematicQueries[ref.target.key] = true;
674
+ }
675
+ if ((group_replacements.isTemplateReference(ref) || group_replacements.isHostBindingReference(ref)) && ref.from.isWrite) {
676
+ res.potentialProblematicQueries[ref.target.key] = true;
677
+ }
678
+ // TODO: Remove this when we support signal narrowing in templates.
679
+ // https://github.com/angular/angular/pull/55456.
680
+ if (group_replacements.isTemplateReference(ref) && ref.from.isLikelyPartOfNarrowing) {
681
+ res.potentialProblematicQueries[ref.target.key] = true;
682
+ }
683
+ // Check for other incompatible query list accesses.
684
+ checkForIncompatibleQueryListAccesses(ref, res);
685
+ }
686
+ if (this.config.assumeNonBatch) {
687
+ res.reusableAnalysisReferences = referenceResult.references;
688
+ }
689
+ return group_replacements.confirmAsSerializable(res);
690
+ }
691
+ async merge(units) {
692
+ const merged = {
693
+ knownQueryFields: {},
694
+ problematicQueries: {},
695
+ reusableAnalysisReferences: null,
696
+ };
697
+ for (const unit of units) {
698
+ for (const [id, value] of Object.entries(unit.knownQueryFields)) {
699
+ merged.knownQueryFields[id] = value;
700
+ }
701
+ for (const id of Object.keys(unit.potentialProblematicQueries)) {
702
+ merged.problematicQueries[id] = true;
703
+ }
704
+ if (unit.reusableAnalysisReferences !== null) {
705
+ assert__default["default"](units.length === 1, 'Expected migration to not run in batch mode');
706
+ merged.reusableAnalysisReferences = unit.reusableAnalysisReferences;
707
+ }
708
+ }
709
+ for (const unit of units) {
710
+ for (const id of Object.keys(unit.potentialProblematicReferenceForMultiQueries)) {
711
+ if (merged.knownQueryFields[id]?.isMulti) {
712
+ merged.problematicQueries[id] = true;
713
+ }
714
+ }
715
+ }
716
+ return group_replacements.confirmAsSerializable(merged);
717
+ }
718
+ async migrate(globalMetadata, info) {
719
+ assert__default["default"](info.ngCompiler !== null, 'Expected queries migration to have an Angular program.');
720
+ // Pre-Analyze the program and get access to the template type checker.
721
+ const { templateTypeChecker, metaReader } = info.ngCompiler['ensureAnalyzed']();
722
+ const { program: program$1, sourceFiles } = info;
723
+ const checker$1 = program$1.getTypeChecker();
724
+ const reflector = new checker.TypeScriptReflectionHost(checker$1);
725
+ const evaluator = new program.PartialEvaluator(reflector, checker$1, null);
726
+ const replacements = [];
727
+ const importManager = new checker.ImportManager();
728
+ const printer = ts__default["default"].createPrinter();
729
+ const filesWithSourceQueries = new Map();
730
+ const filesWithIncompleteMigration = new Map();
731
+ const filesWithQueryListOutsideOfDeclarations = new WeakSet();
732
+ const knownQueries = new KnownQueries(info, globalMetadata);
733
+ const referenceResult = { references: [] };
734
+ 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
+ // Detect all queries in this unit.
740
+ const queryWholeProgramVisitor = (node) => {
741
+ // Detect all SOURCE queries and migrate them, if possible.
742
+ const extractedQuery = extractSourceQueryDefinition(node, reflector, evaluator, info);
743
+ if (extractedQuery !== null) {
744
+ knownQueries.registerQueryField(extractedQuery.node, extractedQuery.id);
745
+ sourceQueries.push(extractedQuery);
746
+ return;
747
+ }
748
+ // Detect OTHER queries, inside `.d.ts`. Needed for reference resolution below.
749
+ if (ts__default["default"].isPropertyDeclaration(node)) {
750
+ const classFieldID = getUniqueIDForClassProperty(node, info);
751
+ if (classFieldID !== null && globalMetadata.knownQueryFields[classFieldID] !== undefined) {
752
+ knownQueries.registerQueryField(node, classFieldID);
753
+ return;
754
+ }
755
+ }
756
+ // Detect potential usages of `QueryList` outside of queries or imports.
757
+ // Those prevent us from removing the import later.
758
+ if (ts__default["default"].isIdentifier(node) &&
759
+ node.text === 'QueryList' &&
760
+ ts__default["default"].findAncestor(node, ts__default["default"].isImportDeclaration) === undefined) {
761
+ filesWithQueryListOutsideOfDeclarations.add(node.getSourceFile());
762
+ }
763
+ ts__default["default"].forEachChild(node, queryWholeProgramVisitor);
764
+ };
765
+ this.config.reportProgressFn?.(40, 'Tracking query declarations..');
766
+ for (const sf of info.fullProgramSourceFiles) {
767
+ ts__default["default"].forEachChild(sf, queryWholeProgramVisitor);
768
+ }
769
+ // Set of all queries in the program. Useful for speeding up reference
770
+ // lookups below.
771
+ const fieldNamesToConsiderForReferenceLookup = new Set(Object.values(globalMetadata.knownQueryFields).map((f) => f.fieldName));
772
+ // Find all references.
773
+ const groupedAstVisitor = new group_replacements.GroupedTsAstVisitor(sourceFiles);
774
+ // Re-use previous reference result if available, instead of
775
+ // looking for references which is quite expensive.
776
+ if (globalMetadata.reusableAnalysisReferences !== null) {
777
+ referenceResult.references = globalMetadata.reusableAnalysisReferences;
778
+ }
779
+ else {
780
+ groupedAstVisitor.register(group_replacements.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, info.ngCompiler['resourceManager'], evaluator, templateTypeChecker, knownQueries, fieldNamesToConsiderForReferenceLookup, referenceResult).visitor);
781
+ }
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
+ // Check inheritance.
787
+ this.config.reportProgressFn?.(70, 'Checking for inheritance patterns..');
788
+ group_replacements.checkInheritanceOfKnownFields(inheritanceGraph, metaReader, knownQueries, {
789
+ getFieldsForClass: (n) => knownQueries.getQueryFieldsOfClass(n) ?? [],
790
+ isClassWithKnownFields: (clazz) => knownQueries.getQueryFieldsOfClass(clazz) !== undefined,
791
+ });
792
+ this.config.reportProgressFn?.(80, 'Migrating queries..');
793
+ // Migrate declarations.
794
+ for (const extractedQuery of sourceQueries) {
795
+ const node = extractedQuery.node;
796
+ const sf = node.getSourceFile();
797
+ const descriptor = { key: extractedQuery.id, node: extractedQuery.node };
798
+ if (!isMigratedQuery(descriptor)) {
799
+ updateFileState(filesWithSourceQueries, sf, extractedQuery.kind);
800
+ updateFileState(filesWithIncompleteMigration, sf, extractedQuery.kind);
801
+ continue;
802
+ }
803
+ updateFileState(filesWithSourceQueries, sf, extractedQuery.kind);
804
+ replacements.push(...computeReplacementsToMigrateQuery(node, extractedQuery, importManager, info, printer));
805
+ }
806
+ // Migrate references.
807
+ const referenceMigrationHost = {
808
+ printer,
809
+ replacements,
810
+ shouldMigrateReferencesToField: (field) => isMigratedQuery(field),
811
+ shouldMigrateReferencesToClass: (clazz) => !!knownQueries.getQueryFieldsOfClass(clazz)?.some((q) => isMigratedQuery(q)),
812
+ };
813
+ group_replacements.migrateTypeScriptReferences(referenceMigrationHost, referenceResult.references, checker$1, info);
814
+ migrateTemplateReferences(referenceMigrationHost, referenceResult.references);
815
+ migrateHostBindings(referenceMigrationHost, referenceResult.references, info);
816
+ group_replacements.migrateTypeScriptTypeReferences(referenceMigrationHost, referenceResult.references, importManager, info);
817
+ // Fix problematic calls, like `QueryList#toArray`, or `QueryList#get`.
818
+ for (const ref of referenceResult.references) {
819
+ removeQueryListToArrayCall(ref, info, globalMetadata, replacements);
820
+ replaceQueryListGetCall(ref, info, globalMetadata, replacements);
821
+ replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, replacements);
822
+ }
823
+ // Remove imports if possible.
824
+ for (const [file, types] of filesWithSourceQueries) {
825
+ let seenIncompatibleMultiQuery = false;
826
+ for (const type of types) {
827
+ const incompatibleQueryTypesForFile = filesWithIncompleteMigration.get(file);
828
+ // Query type is fully migrated. No incompatible queries in file.
829
+ if (!incompatibleQueryTypesForFile?.has(type)) {
830
+ importManager.removeImport(file, queryFunctionNameToDecorator(type), '@angular/core');
831
+ }
832
+ else if (type === 'viewChildren' || type === 'contentChildren') {
833
+ seenIncompatibleMultiQuery = true;
834
+ }
835
+ }
836
+ if (!seenIncompatibleMultiQuery && !filesWithQueryListOutsideOfDeclarations.has(file)) {
837
+ importManager.removeImport(file, 'QueryList', '@angular/core');
838
+ }
839
+ }
840
+ group_replacements.applyImportManagerChanges(importManager, replacements, sourceFiles, info);
841
+ return { replacements, knownQueries };
842
+ }
843
+ async stats(globalMetadata) {
844
+ // TODO: Add statistics.
845
+ return { counters: {} };
846
+ }
847
+ }
848
+ /**
849
+ * Updates the given map to capture the given query type.
850
+ * The map may track migrated queries in a file, or query types
851
+ * that couldn't be migrated.
852
+ */
853
+ function updateFileState(stateMap, node, queryType) {
854
+ const file = node.getSourceFile();
855
+ if (!stateMap.has(file)) {
856
+ stateMap.set(file, new Set());
857
+ }
858
+ stateMap.get(file).add(queryType);
859
+ }
860
+
861
+ function migrate(options) {
862
+ return async (tree, context) => {
863
+ const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(tree);
864
+ if (!buildPaths.length && !testPaths.length) {
865
+ throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run signal input migration.');
866
+ }
867
+ const fs = new group_replacements.DevkitMigrationFilesystem(tree);
868
+ checker.setFileSystem(fs);
869
+ const migration = new SignalQueriesMigration({
870
+ shouldMigrateQuery: (_query, file) => {
871
+ return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
872
+ !/(^|\/)node_modules\//.test(file.rootRelativePath));
873
+ },
874
+ });
875
+ const analysisPath = fs.resolve(options.analysisDir);
876
+ const unitResults = [];
877
+ const programInfos = [...buildPaths, ...testPaths].map((tsconfigPath) => {
878
+ context.logger.info(`Preparing analysis for: ${tsconfigPath}..`);
879
+ const baseInfo = migration.createProgram(tsconfigPath, fs);
880
+ const info = migration.prepareProgram(baseInfo);
881
+ // Support restricting the analysis to subfolders for larger projects.
882
+ if (analysisPath !== '/') {
883
+ info.sourceFiles = info.sourceFiles.filter((sf) => sf.fileName.startsWith(analysisPath));
884
+ info.fullProgramSourceFiles = info.fullProgramSourceFiles.filter((sf) => sf.fileName.startsWith(analysisPath));
885
+ }
886
+ return { info, tsconfigPath };
887
+ });
888
+ // Analyze phase. Treat all projects as compilation units as
889
+ // this allows us to support references between those.
890
+ for (const { info, tsconfigPath } of programInfos) {
891
+ context.logger.info(`Scanning for queries: ${tsconfigPath}..`);
892
+ unitResults.push(await migration.analyze(info));
893
+ }
894
+ context.logger.info(``);
895
+ context.logger.info(`Processing analysis data between targets..`);
896
+ context.logger.info(``);
897
+ const merged = await migration.merge(unitResults);
898
+ const replacementsPerFile = new Map();
899
+ for (const { info, tsconfigPath } of programInfos) {
900
+ context.logger.info(`Migrating: ${tsconfigPath}..`);
901
+ const { replacements } = await migration.migrate(merged, info);
902
+ const changesPerFile = group_replacements.groupReplacementsByFile(replacements);
903
+ for (const [file, changes] of changesPerFile) {
904
+ if (!replacementsPerFile.has(file)) {
905
+ replacementsPerFile.set(file, changes);
906
+ }
907
+ }
908
+ }
909
+ context.logger.info(`Applying changes..`);
910
+ for (const [file, changes] of replacementsPerFile) {
911
+ const recorder = tree.beginUpdate(file);
912
+ for (const c of changes) {
913
+ recorder
914
+ .remove(c.data.position, c.data.end - c.data.position)
915
+ .insertLeft(c.data.position, c.data.toInsert);
916
+ }
917
+ tree.commitUpdate(recorder);
918
+ }
919
+ context.logger.info('');
920
+ context.logger.info(`Successfully migrated to signal queries 🎉`);
921
+ };
922
+ }
923
+
924
+ exports.migrate = migrate;