@angular/core 19.0.0-next.6 → 19.0.0-next.8

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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/fesm2022/core.mjs +648 -273
  3. package/fesm2022/core.mjs.map +1 -1
  4. package/fesm2022/primitives/event-dispatch.mjs +1 -1
  5. package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
  6. package/fesm2022/primitives/signals.mjs +1 -1
  7. package/fesm2022/primitives/signals.mjs.map +1 -1
  8. package/fesm2022/rxjs-interop.mjs +25 -4
  9. package/fesm2022/rxjs-interop.mjs.map +1 -1
  10. package/fesm2022/testing.mjs +12 -11
  11. package/fesm2022/testing.mjs.map +1 -1
  12. package/index.d.ts +232 -148
  13. package/package.json +1 -1
  14. package/primitives/event-dispatch/index.d.ts +1 -1
  15. package/primitives/signals/index.d.ts +1 -1
  16. package/rxjs-interop/index.d.ts +3 -1
  17. package/schematics/bundles/{checker-dcf9a14e.js → checker-e68dd7ce.js} +34 -13
  18. package/schematics/bundles/{compiler_host-6026cdf8.js → compiler_host-9a4d0c2b.js} +2 -2
  19. package/schematics/bundles/control-flow-migration.js +3 -3
  20. package/schematics/bundles/explicit-standalone-flag.js +5 -5
  21. package/schematics/bundles/group_replacements-472b2387.js +31445 -0
  22. package/schematics/bundles/{imports-44987700.js → imports-4ac08251.js} +1 -1
  23. package/schematics/bundles/inject-migration.js +28 -32
  24. package/schematics/bundles/leading_space-d190b83b.js +30 -0
  25. package/schematics/bundles/{nodes-b12e919a.js → nodes-0e7d45ca.js} +2 -2
  26. package/schematics/bundles/pending-tasks.js +103 -0
  27. package/schematics/bundles/{program-4dc8c0fa.js → program-105283c5.js} +1865 -1489
  28. package/schematics/bundles/{project_tsconfig_paths-6c9cde78.js → project_tsconfig_paths-e9ccccbf.js} +1 -1
  29. package/schematics/bundles/route-lazy-loading.js +4 -4
  30. package/schematics/bundles/signal-input-migration.js +706 -31790
  31. package/schematics/bundles/signal-queries-migration.js +905 -0
  32. package/schematics/bundles/standalone-migration.js +12 -12
  33. package/schematics/collection.json +6 -0
  34. package/schematics/migrations.json +5 -0
  35. package/schematics/ng-generate/signal-input-migration/schema.json +5 -0
  36. package/schematics/ng-generate/signal-queries-migration/schema.json +19 -0
  37. package/testing/index.d.ts +3 -1
@@ -0,0 +1,905 @@
1
+ 'use strict';
2
+ /**
3
+ * @license Angular v19.0.0-next.8
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-472b2387.js');
14
+ require('os');
15
+ var ts = require('typescript');
16
+ var checker = require('./checker-e68dd7ce.js');
17
+ var program = require('./program-105283c5.js');
18
+ require('path');
19
+ var assert = require('assert');
20
+ require('@angular-devkit/core');
21
+ require('node:path');
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
+ * 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
+ /**
143
+ * Extracts the type `T` of expressions referencing `QueryList<T>`.
144
+ */
145
+ function extractQueryListType(node) {
146
+ // Initializer variant of `new QueryList<T>()`.
147
+ if (ts__default["default"].isNewExpression(node) &&
148
+ ts__default["default"].isIdentifier(node.expression) &&
149
+ node.expression.text === 'QueryList') {
150
+ return node.typeArguments?.[0];
151
+ }
152
+ // Type variant of `: QueryList<T>`.
153
+ if (ts__default["default"].isTypeReferenceNode(node) &&
154
+ ts__default["default"].isIdentifier(node.typeName) &&
155
+ node.typeName.text === 'QueryList') {
156
+ return node.typeArguments?.[0];
157
+ }
158
+ return undefined;
159
+ }
160
+
161
+ /**
162
+ * A few notes on changes:
163
+ *
164
+ * @ViewChild()
165
+ * --> static is gone!
166
+ * --> read stays
167
+ *
168
+ * @ViewChildren()
169
+ * --> emitDistinctChangesOnly is gone!
170
+ * --> read stays
171
+ *
172
+ * @ContentChild()
173
+ * --> descendants stays
174
+ * --> read stays
175
+ * --> static is gone!
176
+ *
177
+ * @ContentChildren()
178
+ * --> descendants stays
179
+ * --> read stays
180
+ * --> emitDistinctChangesOnly is gone!
181
+ */
182
+ function computeReplacementsToMigrateQuery(node, metadata, importManager, info, printer) {
183
+ const sf = node.getSourceFile();
184
+ let newQueryFn = importManager.addImport({
185
+ requestedFile: sf,
186
+ exportModuleSpecifier: '@angular/core',
187
+ exportSymbolName: metadata.kind,
188
+ });
189
+ // The default value for descendants is `true`, except for `ContentChildren`.
190
+ const defaultDescendants = metadata.kind !== 'contentChildren';
191
+ const optionProperties = [];
192
+ const args = [
193
+ metadata.args[0], // Locator.
194
+ ];
195
+ let type = node.type;
196
+ // For multi queries, attempt to unwrap `QueryList` types, or infer the
197
+ // type from the initializer, if possible.
198
+ if (!metadata.queryInfo.first) {
199
+ if (type === undefined && node.initializer !== undefined) {
200
+ type = extractQueryListType(node.initializer);
201
+ }
202
+ else if (type !== undefined) {
203
+ type = extractQueryListType(type);
204
+ }
205
+ }
206
+ if (metadata.queryInfo.read !== null) {
207
+ assert__default["default"](metadata.queryInfo.read instanceof checker.WrappedNodeExpr);
208
+ optionProperties.push(ts__default["default"].factory.createPropertyAssignment('read', metadata.queryInfo.read.node));
209
+ }
210
+ if (metadata.queryInfo.descendants !== defaultDescendants) {
211
+ optionProperties.push(ts__default["default"].factory.createPropertyAssignment('descendants', metadata.queryInfo.descendants ? ts__default["default"].factory.createTrue() : ts__default["default"].factory.createFalse()));
212
+ }
213
+ if (optionProperties.length > 0) {
214
+ args.push(ts__default["default"].factory.createObjectLiteralExpression(optionProperties));
215
+ }
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) {
221
+ newQueryFn = ts__default["default"].factory.createPropertyAccessExpression(newQueryFn, 'required');
222
+ }
223
+ // If this query is still nullable (i.e. not required), attempt to remove
224
+ // 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);
227
+ }
228
+ const locatorType = Array.isArray(metadata.queryInfo.predicate)
229
+ ? null
230
+ : 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)`.
234
+ if (type !== undefined &&
235
+ readType instanceof checker.WrappedNodeExpr &&
236
+ ts__default["default"].isIdentifier(readType.node) &&
237
+ ts__default["default"].isTypeReferenceNode(type) &&
238
+ 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);
244
+ return [
245
+ new group_replacements.Replacement(group_replacements.projectFile(node.getSourceFile(), info), new group_replacements.TextUpdate({
246
+ position: node.getStart(),
247
+ end: node.getEnd(),
248
+ toInsert: printer.printNode(ts__default["default"].EmitHint.Unspecified, updated, sf),
249
+ })),
250
+ ];
251
+ }
252
+
253
+ /**
254
+ * Attempts to get a class field descriptor if the given symbol
255
+ * points to a class field.
256
+ */
257
+ function getClassFieldDescriptorForSymbol(symbol, info) {
258
+ if (symbol?.valueDeclaration === undefined ||
259
+ !ts__default["default"].isPropertyDeclaration(symbol.valueDeclaration)) {
260
+ return null;
261
+ }
262
+ const key = getUniqueIDForClassProperty(symbol.valueDeclaration, info);
263
+ if (key === null) {
264
+ return null;
265
+ }
266
+ return {
267
+ key,
268
+ node: symbol.valueDeclaration,
269
+ };
270
+ }
271
+ /**
272
+ * Gets a unique ID for the given class property.
273
+ *
274
+ * This is useful for matching class fields across compilation units.
275
+ * E.g. a reference may point to the field via `.d.ts`, while the other
276
+ * may reference it via actual `.ts` sources. IDs for the same fields
277
+ * would then match identity.
278
+ */
279
+ function getUniqueIDForClassProperty(property, info) {
280
+ if (!ts__default["default"].isClassDeclaration(property.parent) || property.parent.name === undefined) {
281
+ return null;
282
+ }
283
+ const id = group_replacements.projectFile(property.getSourceFile(), info).id.replace(/\.d\.ts$/, '.ts');
284
+ // Note: If a class is nested, there could be an ID clash.
285
+ // This is highly unlikely though, and this is not a problem because
286
+ // in such cases, there is even less chance there are any references to
287
+ // a non-exported classes; in which case, cross-compilation unit references
288
+ // likely can't exist anyway.
289
+ return `${id}-${property.parent.name.text}-${property.name.getText()}`;
290
+ }
291
+
292
+ /**
293
+ * Determines if the given node refers to a decorator-based query, and
294
+ * returns its resolved metadata if possible.
295
+ */
296
+ function extractSourceQueryDefinition(node, reflector, evaluator, info) {
297
+ if (!ts__default["default"].isPropertyDeclaration(node) ||
298
+ !ts__default["default"].isClassDeclaration(node.parent) ||
299
+ node.parent.name === undefined ||
300
+ !ts__default["default"].isIdentifier(node.name)) {
301
+ return null;
302
+ }
303
+ const decorators = reflector.getDecoratorsOfDeclaration(node) ?? [];
304
+ const ngDecorators = checker.getAngularDecorators(decorators, program.queryDecoratorNames, /* isCore */ false);
305
+ if (ngDecorators.length === 0) {
306
+ return null;
307
+ }
308
+ const decorator = ngDecorators[0];
309
+ const id = getUniqueIDForClassProperty(node, info);
310
+ if (id === null) {
311
+ return null;
312
+ }
313
+ let kind;
314
+ if (decorator.name === 'ViewChild') {
315
+ kind = 'viewChild';
316
+ }
317
+ else if (decorator.name === 'ViewChildren') {
318
+ kind = 'viewChildren';
319
+ }
320
+ else if (decorator.name === 'ContentChild') {
321
+ kind = 'contentChild';
322
+ }
323
+ else if (decorator.name === 'ContentChildren') {
324
+ kind = 'contentChildren';
325
+ }
326
+ else {
327
+ throw new Error('Unexpected query decorator detected.');
328
+ }
329
+ const queryInfo = program.extractDecoratorQueryMetadata(node, decorator.name, decorator.args ?? [], node.name.text, reflector, evaluator);
330
+ return {
331
+ id,
332
+ kind,
333
+ args: decorator.args ?? [],
334
+ queryInfo,
335
+ node,
336
+ };
337
+ }
338
+
339
+ class KnownQueries {
340
+ constructor(info, globalMetadata) {
341
+ this.info = info;
342
+ this.globalMetadata = globalMetadata;
343
+ this.classToQueryFields = new Map();
344
+ this.knownQueryIDs = new Set();
345
+ }
346
+ isFieldIncompatible(descriptor) {
347
+ return this.globalMetadata.problematicQueries[descriptor.key] !== undefined;
348
+ }
349
+ markFieldIncompatible(field) {
350
+ this.globalMetadata.problematicQueries[field.key] = true;
351
+ }
352
+ markClassIncompatible(node) {
353
+ this.classToQueryFields.get(node)?.forEach((f) => {
354
+ this.globalMetadata.problematicQueries[f.key] = true;
355
+ });
356
+ }
357
+ registerQueryField(queryField, id) {
358
+ if (!this.classToQueryFields.has(queryField.parent)) {
359
+ this.classToQueryFields.set(queryField.parent, []);
360
+ }
361
+ this.classToQueryFields.get(queryField.parent).push({
362
+ key: id,
363
+ node: queryField,
364
+ });
365
+ this.knownQueryIDs.add(id);
366
+ }
367
+ attemptRetrieveDescriptorFromSymbol(symbol) {
368
+ const descriptor = getClassFieldDescriptorForSymbol(symbol, this.info);
369
+ if (descriptor !== null && this.knownQueryIDs.has(descriptor.key)) {
370
+ return descriptor;
371
+ }
372
+ return null;
373
+ }
374
+ shouldTrackClassReference(clazz) {
375
+ return this.classToQueryFields.has(clazz);
376
+ }
377
+ getQueryFieldsOfClass(clazz) {
378
+ return this.classToQueryFields.get(clazz);
379
+ }
380
+ getAllClassesWithQueries() {
381
+ return Array.from(this.classToQueryFields.keys()).filter((c) => ts__default["default"].isClassDeclaration(c));
382
+ }
383
+ captureKnownFieldInheritanceRelationship(derived, parent) {
384
+ if (this.isFieldIncompatible(parent) || this.isFieldIncompatible(derived)) {
385
+ this.markFieldIncompatible(parent);
386
+ this.markFieldIncompatible(derived);
387
+ }
388
+ }
389
+ captureUnknownDerivedField(field) {
390
+ this.markFieldIncompatible(field);
391
+ }
392
+ captureUnknownParentField(field) {
393
+ this.markFieldIncompatible(field);
394
+ }
395
+ }
396
+
397
+ /** Converts an initializer query API name to its decorator-equivalent. */
398
+ function queryFunctionNameToDecorator(name) {
399
+ if (name === 'viewChild') {
400
+ return 'ViewChild';
401
+ }
402
+ else if (name === 'viewChildren') {
403
+ return 'ViewChildren';
404
+ }
405
+ else if (name === 'contentChild') {
406
+ return 'ContentChild';
407
+ }
408
+ else if (name === 'contentChildren') {
409
+ return 'ContentChildren';
410
+ }
411
+ throw new Error(`Unexpected query function name: ${name}`);
412
+ }
413
+
414
+ /**
415
+ * Gets whether the given field is accessed via the
416
+ * given reference.
417
+ *
418
+ * E.g. whether `<my-read>.toArray` is detected.
419
+ */
420
+ function checkTsReferenceAccessesField(ref, fieldName) {
421
+ const accessNode = group_replacements.traverseAccess(ref.from.node);
422
+ // Check if the reference is part of a property access.
423
+ if (!ts__default["default"].isPropertyAccessExpression(accessNode.parent) ||
424
+ !ts__default["default"].isIdentifier(accessNode.parent.name)) {
425
+ return null;
426
+ }
427
+ // Check if the reference is refers to the given field name.
428
+ if (accessNode.parent.name.text !== fieldName) {
429
+ return null;
430
+ }
431
+ return accessNode.parent;
432
+ }
433
+ /**
434
+ * Gets whether the given read is used to access
435
+ * the specified field.
436
+ *
437
+ * E.g. whether `<my-read>.toArray` is detected.
438
+ */
439
+ function checkNonTsReferenceAccessesField(ref, fieldName) {
440
+ const readFromPath = ref.from.readAstPath.at(-1);
441
+ const parentRead = ref.from.readAstPath.at(-2);
442
+ if (ref.from.read !== readFromPath) {
443
+ return null;
444
+ }
445
+ if (!(parentRead instanceof checker.PropertyRead) || parentRead.name !== fieldName) {
446
+ return null;
447
+ }
448
+ return parentRead;
449
+ }
450
+ /**
451
+ * Gets whether the given reference is accessed to call the
452
+ * specified function on it.
453
+ *
454
+ * E.g. whether `<my-read>.toArray()` is detected.
455
+ */
456
+ function checkTsReferenceCallsField(ref, fieldName) {
457
+ const propertyAccess = checkTsReferenceAccessesField(ref, fieldName);
458
+ if (propertyAccess === null) {
459
+ return null;
460
+ }
461
+ if (ts__default["default"].isCallExpression(propertyAccess.parent) &&
462
+ propertyAccess.parent.expression === propertyAccess) {
463
+ return propertyAccess.parent;
464
+ }
465
+ return null;
466
+ }
467
+ /**
468
+ * Gets whether the given reference is accessed to call the
469
+ * specified function on it.
470
+ *
471
+ * E.g. whether `<my-read>.toArray()` is detected.
472
+ */
473
+ function checkNonTsReferenceCallsField(ref, fieldName) {
474
+ const propertyAccess = checkNonTsReferenceAccessesField(ref, fieldName);
475
+ if (propertyAccess === null) {
476
+ return null;
477
+ }
478
+ const accessIdx = ref.from.readAstPath.indexOf(propertyAccess);
479
+ if (accessIdx === -1) {
480
+ return null;
481
+ }
482
+ const potentialCall = ref.from.readAstPath[accessIdx - 1];
483
+ if (potentialCall === undefined || !(potentialCall instanceof checker.Call)) {
484
+ return null;
485
+ }
486
+ return potentialCall;
487
+ }
488
+
489
+ function removeQueryListToArrayCall(ref, info, globalMetadata, replacements) {
490
+ if (!group_replacements.isHostBindingReference(ref) && !group_replacements.isTemplateReference(ref) && !group_replacements.isTsReference(ref)) {
491
+ return;
492
+ }
493
+ if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
494
+ return;
495
+ }
496
+ // TS references.
497
+ if (group_replacements.isTsReference(ref)) {
498
+ const toArrayCallExpr = checkTsReferenceCallsField(ref, 'toArray');
499
+ if (toArrayCallExpr === null) {
500
+ return;
501
+ }
502
+ const toArrayExpr = toArrayCallExpr.expression;
503
+ replacements.push(new group_replacements.Replacement(group_replacements.projectFile(toArrayExpr.getSourceFile(), info), new group_replacements.TextUpdate({
504
+ // Delete from expression end to call end. E.g. `.toArray(<..>)`.
505
+ position: toArrayExpr.expression.getEnd(),
506
+ end: toArrayCallExpr.getEnd(),
507
+ toInsert: '',
508
+ })));
509
+ return;
510
+ }
511
+ // Template and host binding references.
512
+ const callExpr = checkNonTsReferenceCallsField(ref, 'toArray');
513
+ if (callExpr === null) {
514
+ return;
515
+ }
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({
519
+ // Delete from expression end to call end. E.g. `.toArray(<..>)`.
520
+ position: offset + callExpr.receiver.receiver.sourceSpan.end,
521
+ end: offset + callExpr.sourceSpan.end,
522
+ toInsert: '',
523
+ })));
524
+ }
525
+
526
+ function replaceQueryListGetCall(ref, info, globalMetadata, replacements) {
527
+ if (!group_replacements.isHostBindingReference(ref) && !group_replacements.isTemplateReference(ref) && !group_replacements.isTsReference(ref)) {
528
+ return;
529
+ }
530
+ if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
531
+ return;
532
+ }
533
+ if (group_replacements.isTsReference(ref)) {
534
+ const getCallExpr = checkTsReferenceCallsField(ref, 'get');
535
+ if (getCallExpr === null) {
536
+ return;
537
+ }
538
+ const getExpr = getCallExpr.expression;
539
+ replacements.push(new group_replacements.Replacement(group_replacements.projectFile(getExpr.getSourceFile(), info), new group_replacements.TextUpdate({
540
+ position: getExpr.name.getStart(),
541
+ end: getExpr.name.getEnd(),
542
+ toInsert: 'at',
543
+ })));
544
+ return;
545
+ }
546
+ // Template and host binding references.
547
+ const callExpr = checkNonTsReferenceCallsField(ref, 'get');
548
+ if (callExpr === null) {
549
+ return;
550
+ }
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({
554
+ position: offset + callExpr.receiver.nameSpan.start,
555
+ end: offset + callExpr.receiver.nameSpan.end,
556
+ toInsert: 'at',
557
+ })));
558
+ }
559
+
560
+ const problematicQueryListMethods = [
561
+ 'dirty',
562
+ 'changes',
563
+ 'setDirty',
564
+ 'reset',
565
+ 'notifyOnChanges',
566
+ 'destroy',
567
+ ];
568
+ function checkForIncompatibleQueryListAccesses(ref, result) {
569
+ if (group_replacements.isTsReference(ref)) {
570
+ for (const problematicFn of problematicQueryListMethods) {
571
+ const access = checkTsReferenceAccessesField(ref, problematicFn);
572
+ if (access !== null) {
573
+ result.potentialProblematicReferenceForMultiQueries[ref.target.key] = true;
574
+ return;
575
+ }
576
+ }
577
+ }
578
+ if (group_replacements.isHostBindingReference(ref) || group_replacements.isTemplateReference(ref)) {
579
+ for (const problematicFn of problematicQueryListMethods) {
580
+ const access = checkNonTsReferenceAccessesField(ref, problematicFn);
581
+ if (access !== null) {
582
+ result.potentialProblematicReferenceForMultiQueries[ref.target.key] = true;
583
+ return;
584
+ }
585
+ }
586
+ }
587
+ }
588
+
589
+ const mapping = new Map([
590
+ ['first', 'at(0)!'],
591
+ ['last', 'at(-1)!'],
592
+ ]);
593
+ function replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, replacements) {
594
+ if (!group_replacements.isHostBindingReference(ref) && !group_replacements.isTemplateReference(ref) && !group_replacements.isTsReference(ref)) {
595
+ return;
596
+ }
597
+ if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
598
+ return;
599
+ }
600
+ if (group_replacements.isTsReference(ref)) {
601
+ const expr = checkTsReferenceAccessesField(ref, 'first') ?? checkTsReferenceAccessesField(ref, 'last');
602
+ if (expr === null) {
603
+ return;
604
+ }
605
+ replacements.push(new group_replacements.Replacement(group_replacements.projectFile(expr.getSourceFile(), info), new group_replacements.TextUpdate({
606
+ position: expr.name.getStart(),
607
+ end: expr.name.getEnd(),
608
+ toInsert: mapping.get(expr.name.text),
609
+ })));
610
+ return;
611
+ }
612
+ // Template and host binding references.
613
+ const expr = checkNonTsReferenceAccessesField(ref, 'first') ?? checkNonTsReferenceAccessesField(ref, 'last');
614
+ if (expr === null) {
615
+ return;
616
+ }
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({
620
+ position: offset + expr.nameSpan.start,
621
+ end: offset + expr.nameSpan.end,
622
+ toInsert: mapping.get(expr.name),
623
+ })));
624
+ }
625
+
626
+ class SignalQueriesMigration extends group_replacements.TsurgeComplexMigration {
627
+ constructor(config = {}) {
628
+ super();
629
+ this.config = config;
630
+ }
631
+ async analyze(info) {
632
+ 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
+ // Pre-Analyze the program and get access to the template type checker.
636
+ const { templateTypeChecker } = info.ngCompiler['ensureAnalyzed']();
637
+ const { sourceFiles, program: program$1 } = info;
638
+ const checker$1 = program$1.getTypeChecker();
639
+ const reflector = new checker.TypeScriptReflectionHost(checker$1);
640
+ const evaluator = new program.PartialEvaluator(reflector, checker$1, null);
641
+ const res = {
642
+ knownQueryFields: {},
643
+ potentialProblematicQueries: {},
644
+ potentialProblematicReferenceForMultiQueries: {},
645
+ };
646
+ const groupedAstVisitor = new group_replacements.GroupedTsAstVisitor(sourceFiles);
647
+ const referenceResult = { references: [] };
648
+ const findQueryDefinitionsVisitor = (node) => {
649
+ const extractedQuery = extractSourceQueryDefinition(node, reflector, evaluator, info);
650
+ if (extractedQuery !== null) {
651
+ res.knownQueryFields[extractedQuery.id] = {
652
+ fieldName: extractedQuery.queryInfo.propertyName,
653
+ isMulti: extractedQuery.queryInfo.first === false,
654
+ };
655
+ }
656
+ };
657
+ 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);
669
+ groupedAstVisitor.execute();
670
+ // Determine incompatible queries based on problematic references
671
+ // we saw in TS code, templates or host bindings.
672
+ for (const ref of referenceResult.references) {
673
+ if (group_replacements.isTsReference(ref) && ref.from.isWrite) {
674
+ res.potentialProblematicQueries[ref.target.key] = true;
675
+ }
676
+ if ((group_replacements.isTemplateReference(ref) || group_replacements.isHostBindingReference(ref)) && ref.from.isWrite) {
677
+ res.potentialProblematicQueries[ref.target.key] = true;
678
+ }
679
+ // TODO: Remove this when we support signal narrowing in templates.
680
+ // https://github.com/angular/angular/pull/55456.
681
+ if (group_replacements.isTemplateReference(ref) && ref.from.isLikelyPartOfNarrowing) {
682
+ res.potentialProblematicQueries[ref.target.key] = true;
683
+ }
684
+ // Check for other incompatible query list accesses.
685
+ checkForIncompatibleQueryListAccesses(ref, res);
686
+ }
687
+ return group_replacements.confirmAsSerializable(res);
688
+ }
689
+ async merge(units) {
690
+ const merged = {
691
+ knownQueryFields: {},
692
+ problematicQueries: {},
693
+ };
694
+ for (const unit of units) {
695
+ for (const [id, value] of Object.entries(unit.knownQueryFields)) {
696
+ merged.knownQueryFields[id] = value;
697
+ }
698
+ for (const id of Object.keys(unit.potentialProblematicQueries)) {
699
+ merged.problematicQueries[id] = true;
700
+ }
701
+ }
702
+ for (const unit of units) {
703
+ for (const id of Object.keys(unit.potentialProblematicReferenceForMultiQueries)) {
704
+ if (merged.knownQueryFields[id]?.isMulti) {
705
+ merged.problematicQueries[id] = true;
706
+ }
707
+ }
708
+ }
709
+ return group_replacements.confirmAsSerializable(merged);
710
+ }
711
+ async migrate(globalMetadata, info) {
712
+ assert__default["default"](info.ngCompiler !== null, 'Expected queries migration to have an Angular program.');
713
+ // Pre-Analyze the program and get access to the template type checker.
714
+ const { templateTypeChecker, metaReader } = await info.ngCompiler['ensureAnalyzed']();
715
+ const { program: program$1, sourceFiles } = info;
716
+ const checker$1 = program$1.getTypeChecker();
717
+ const reflector = new checker.TypeScriptReflectionHost(checker$1);
718
+ const evaluator = new program.PartialEvaluator(reflector, checker$1, null);
719
+ const replacements = [];
720
+ const importManager = new checker.ImportManager();
721
+ const printer = ts__default["default"].createPrinter();
722
+ const filesWithMigratedQueries = new Map();
723
+ const filesWithIncompleteMigration = new Map();
724
+ const filesWithUnrelatedQueryListImports = new WeakSet();
725
+ const knownQueries = new KnownQueries(info, globalMetadata);
726
+ const referenceResult = { references: [] };
727
+ 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
+ // Detect all queries in this unit.
733
+ const queryWholeProgramVisitor = (node) => {
734
+ // Detect all SOURCE queries and migrate them, if possible.
735
+ const extractedQuery = extractSourceQueryDefinition(node, reflector, evaluator, info);
736
+ if (extractedQuery !== null) {
737
+ knownQueries.registerQueryField(extractedQuery.node, extractedQuery.id);
738
+ sourceQueries.push(extractedQuery);
739
+ return;
740
+ }
741
+ // Detect OTHER queries, inside `.d.ts`. Needed for reference resolution below.
742
+ if (ts__default["default"].isPropertyDeclaration(node)) {
743
+ const classFieldID = getUniqueIDForClassProperty(node, info);
744
+ if (classFieldID !== null && globalMetadata.knownQueryFields[classFieldID] !== undefined) {
745
+ knownQueries.registerQueryField(node, classFieldID);
746
+ return;
747
+ }
748
+ }
749
+ // Detect potential usages of `QueryList` outside of queries or imports.
750
+ // Those prevent us from removing the import later.
751
+ if (ts__default["default"].isIdentifier(node) &&
752
+ node.text === 'QueryList' &&
753
+ ts__default["default"].findAncestor(node, ts__default["default"].isImportDeclaration) === undefined) {
754
+ filesWithUnrelatedQueryListImports.add(node.getSourceFile());
755
+ }
756
+ ts__default["default"].forEachChild(node, queryWholeProgramVisitor);
757
+ };
758
+ for (const sf of info.fullProgramSourceFiles) {
759
+ ts__default["default"].forEachChild(sf, queryWholeProgramVisitor);
760
+ }
761
+ // Set of all queries in the program. Useful for speeding up reference
762
+ // lookups below.
763
+ const fieldNamesToConsiderForReferenceLookup = new Set(Object.values(globalMetadata.knownQueryFields).map((f) => f.fieldName));
764
+ // 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();
770
+ // Check inheritance.
771
+ group_replacements.checkInheritanceOfKnownFields(inheritanceGraph, metaReader, knownQueries, {
772
+ getFieldsForClass: (n) => knownQueries.getQueryFieldsOfClass(n) ?? [],
773
+ isClassWithKnownFields: (clazz) => knownQueries.getQueryFieldsOfClass(clazz) !== undefined,
774
+ });
775
+ // Migrate declarations.
776
+ for (const extractedQuery of sourceQueries) {
777
+ const node = extractedQuery.node;
778
+ const sf = node.getSourceFile();
779
+ const descriptor = { key: extractedQuery.id, node: extractedQuery.node };
780
+ if (!isMigratedQuery(descriptor)) {
781
+ updateFileState(filesWithIncompleteMigration, sf, extractedQuery.kind);
782
+ continue;
783
+ }
784
+ updateFileState(filesWithMigratedQueries, sf, extractedQuery.kind);
785
+ replacements.push(...computeReplacementsToMigrateQuery(node, extractedQuery, importManager, info, printer));
786
+ }
787
+ // Migrate references.
788
+ const referenceMigrationHost = {
789
+ printer,
790
+ replacements,
791
+ shouldMigrateReferencesToField: (field) => isMigratedQuery(field),
792
+ shouldMigrateReferencesToClass: (clazz) => !!knownQueries.getQueryFieldsOfClass(clazz)?.some((q) => isMigratedQuery(q)),
793
+ };
794
+ group_replacements.migrateTypeScriptReferences(referenceMigrationHost, referenceResult.references, checker$1, info);
795
+ migrateTemplateReferences(referenceMigrationHost, referenceResult.references);
796
+ migrateHostBindings(referenceMigrationHost, referenceResult.references, info);
797
+ migrateTypeScriptTypeReferences(referenceMigrationHost, referenceResult.references, importManager, info);
798
+ // Fix problematic calls, like `QueryList#toArray`, or `QueryList#get`.
799
+ for (const ref of referenceResult.references) {
800
+ removeQueryListToArrayCall(ref, info, globalMetadata, replacements);
801
+ replaceQueryListGetCall(ref, info, globalMetadata, replacements);
802
+ replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, replacements);
803
+ }
804
+ // Remove imports if possible.
805
+ for (const [file, types] of filesWithMigratedQueries) {
806
+ let seenIncompatibleMultiQuery = false;
807
+ for (const type of types) {
808
+ const incompatibleQueryTypesForFile = filesWithIncompleteMigration.get(file);
809
+ // Query type is fully migrated. No incompatible queries in file.
810
+ if (!incompatibleQueryTypesForFile?.has(type)) {
811
+ importManager.removeImport(file, queryFunctionNameToDecorator(type), '@angular/core');
812
+ }
813
+ else if (type === 'viewChildren' || type === 'contentChildren') {
814
+ seenIncompatibleMultiQuery = true;
815
+ }
816
+ }
817
+ if (!seenIncompatibleMultiQuery && !filesWithUnrelatedQueryListImports.has(file)) {
818
+ importManager.removeImport(file, 'QueryList', '@angular/core');
819
+ }
820
+ }
821
+ group_replacements.applyImportManagerChanges(importManager, replacements, sourceFiles, info);
822
+ return replacements;
823
+ }
824
+ async stats(globalMetadata) {
825
+ // TODO: Add statistics.
826
+ return { counters: {} };
827
+ }
828
+ }
829
+ /**
830
+ * Updates the given map to capture the given query type.
831
+ * The map may track migrated queries in a file, or query types
832
+ * that couldn't be migrated.
833
+ */
834
+ function updateFileState(stateMap, node, queryType) {
835
+ const file = node.getSourceFile();
836
+ if (!stateMap.has(file)) {
837
+ stateMap.set(file, new Set());
838
+ }
839
+ stateMap.get(file).add(queryType);
840
+ }
841
+
842
+ function migrate(options) {
843
+ return async (tree, context) => {
844
+ const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(tree);
845
+ if (!buildPaths.length && !testPaths.length) {
846
+ throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run signal input migration.');
847
+ }
848
+ const fs = new group_replacements.DevkitMigrationFilesystem(tree);
849
+ checker.setFileSystem(fs);
850
+ const migration = new SignalQueriesMigration({
851
+ shouldMigrateQuery: (_query, file) => {
852
+ return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
853
+ !/(^|\/)node_modules\//.test(file.rootRelativePath));
854
+ },
855
+ });
856
+ const analysisPath = fs.resolve(options.analysisDir);
857
+ const unitResults = [];
858
+ const programInfos = [...buildPaths, ...testPaths].map((tsconfigPath) => {
859
+ context.logger.info(`Preparing analysis for: ${tsconfigPath}..`);
860
+ const baseInfo = migration.createProgram(tsconfigPath, fs);
861
+ const info = migration.prepareProgram(baseInfo);
862
+ // Support restricting the analysis to subfolders for larger projects.
863
+ if (analysisPath !== '/') {
864
+ info.sourceFiles = info.sourceFiles.filter((sf) => sf.fileName.startsWith(analysisPath));
865
+ info.fullProgramSourceFiles = info.fullProgramSourceFiles.filter((sf) => sf.fileName.startsWith(analysisPath));
866
+ }
867
+ return { info, tsconfigPath };
868
+ });
869
+ // Analyze phase. Treat all projects as compilation units as
870
+ // this allows us to support references between those.
871
+ for (const { info, tsconfigPath } of programInfos) {
872
+ context.logger.info(`Scanning for queries: ${tsconfigPath}..`);
873
+ unitResults.push(await migration.analyze(info));
874
+ }
875
+ context.logger.info(``);
876
+ context.logger.info(`Processing analysis data between targets..`);
877
+ context.logger.info(``);
878
+ const merged = await migration.merge(unitResults);
879
+ const replacementsPerFile = new Map();
880
+ for (const { info, tsconfigPath } of programInfos) {
881
+ context.logger.info(`Migrating: ${tsconfigPath}..`);
882
+ const replacements = await migration.migrate(merged, info);
883
+ const changesPerFile = group_replacements.groupReplacementsByFile(replacements);
884
+ for (const [file, changes] of changesPerFile) {
885
+ if (!replacementsPerFile.has(file)) {
886
+ replacementsPerFile.set(file, changes);
887
+ }
888
+ }
889
+ }
890
+ context.logger.info(`Applying changes..`);
891
+ for (const [file, changes] of replacementsPerFile) {
892
+ const recorder = tree.beginUpdate(file);
893
+ for (const c of changes) {
894
+ recorder
895
+ .remove(c.data.position, c.data.end - c.data.position)
896
+ .insertLeft(c.data.position, c.data.toInsert);
897
+ }
898
+ tree.commitUpdate(recorder);
899
+ }
900
+ context.logger.info('');
901
+ context.logger.info(`Successfully migrated to signal queries 🎉`);
902
+ };
903
+ }
904
+
905
+ exports.migrate = migrate;