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

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 (32) hide show
  1. package/fesm2022/core.mjs +378 -130
  2. package/fesm2022/core.mjs.map +1 -1
  3. package/fesm2022/primitives/event-dispatch.mjs +1 -1
  4. package/fesm2022/primitives/event-dispatch.mjs.map +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 +25 -4
  8. package/fesm2022/rxjs-interop.mjs.map +1 -1
  9. package/fesm2022/testing.mjs +12 -11
  10. package/fesm2022/testing.mjs.map +1 -1
  11. package/index.d.ts +166 -97
  12. package/package.json +1 -1
  13. package/primitives/event-dispatch/index.d.ts +1 -1
  14. package/primitives/signals/index.d.ts +1 -1
  15. package/rxjs-interop/index.d.ts +3 -1
  16. package/schematics/bundles/{checker-dcf9a14e.js → checker-637eee78.js} +22 -11
  17. package/schematics/bundles/{compiler_host-6026cdf8.js → compiler_host-1e62b899.js} +2 -2
  18. package/schematics/bundles/control-flow-migration.js +3 -3
  19. package/schematics/bundles/explicit-standalone-flag.js +3 -3
  20. package/schematics/bundles/imports-44987700.js +1 -1
  21. package/schematics/bundles/inject-migration.js +8 -28
  22. package/schematics/bundles/leading_space-6e7a8ec6.js +30 -0
  23. package/schematics/bundles/nodes-b12e919a.js +1 -1
  24. package/schematics/bundles/pending-tasks.js +103 -0
  25. package/schematics/bundles/{program-4dc8c0fa.js → program-893e3fe7.js} +1810 -1476
  26. package/schematics/bundles/project_tsconfig_paths-6c9cde78.js +1 -1
  27. package/schematics/bundles/route-lazy-loading.js +3 -3
  28. package/schematics/bundles/signal-input-migration.js +516 -283
  29. package/schematics/bundles/standalone-migration.js +9 -9
  30. package/schematics/migrations.json +5 -0
  31. package/schematics/ng-generate/signal-input-migration/schema.json +5 -0
  32. package/testing/index.d.ts +3 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  /**
3
- * @license Angular v19.0.0-next.6
3
+ * @license Angular v19.0.0-next.7
4
4
  * (c) 2010-2024 Google LLC. https://angular.io/
5
5
  * License: MIT
6
6
  */
@@ -11,10 +11,11 @@ Object.defineProperty(exports, '__esModule', { value: true });
11
11
  var schematics = require('@angular-devkit/schematics');
12
12
  var ts = require('typescript');
13
13
  var os = require('os');
14
- var checker = require('./checker-dcf9a14e.js');
15
- var program = require('./program-4dc8c0fa.js');
14
+ var checker = require('./checker-637eee78.js');
15
+ var program = require('./program-893e3fe7.js');
16
16
  require('path');
17
17
  var assert = require('assert');
18
+ var leading_space = require('./leading_space-6e7a8ec6.js');
18
19
  var project_tsconfig_paths = require('./project_tsconfig_paths-6c9cde78.js');
19
20
  var core = require('@angular-devkit/core');
20
21
  var path = require('node:path');
@@ -140,10 +141,7 @@ function createFileSystemTsReadDirectoryFn(fs) {
140
141
  }
141
142
  }
142
143
  return { files, directories };
143
- }, (p) => fs.resolve(p), (p) => {
144
- const resolvedPath = fs.resolve(p);
145
- return fs.exists(resolvedPath) && fs.stat(resolvedPath).isDirectory();
146
- });
144
+ }, (p) => fs.resolve(p), (p) => directoryExists(p));
147
145
  };
148
146
  }
149
147
 
@@ -261,19 +259,25 @@ function getExtendedConfigPathWorker(configFile, extendsValue, host, fs) {
261
259
  return null;
262
260
  }
263
261
 
264
- /** Reasons why an input cannot be migrated. */
262
+ /**
263
+ * Reasons why an input cannot be migrated.
264
+ *
265
+ * Higher values of incompatibility reasons indicate a more significant
266
+ * incompatibility reason. Lower ones may be overridden by higher ones.
267
+ * */
265
268
  var InputIncompatibilityReason;
266
269
  (function (InputIncompatibilityReason) {
267
- InputIncompatibilityReason[InputIncompatibilityReason["SkippedViaConfigFilter"] = 0] = "SkippedViaConfigFilter";
268
- InputIncompatibilityReason[InputIncompatibilityReason["Accessor"] = 1] = "Accessor";
269
- InputIncompatibilityReason[InputIncompatibilityReason["WriteAssignment"] = 2] = "WriteAssignment";
270
- InputIncompatibilityReason[InputIncompatibilityReason["OverriddenByDerivedClass"] = 3] = "OverriddenByDerivedClass";
271
- InputIncompatibilityReason[InputIncompatibilityReason["RedeclaredViaDerivedClassInputsArray"] = 4] = "RedeclaredViaDerivedClassInputsArray";
272
- InputIncompatibilityReason[InputIncompatibilityReason["TypeConflictWithBaseClass"] = 5] = "TypeConflictWithBaseClass";
273
- InputIncompatibilityReason[InputIncompatibilityReason["ParentIsIncompatible"] = 6] = "ParentIsIncompatible";
274
- InputIncompatibilityReason[InputIncompatibilityReason["SpyOnThatOverwritesField"] = 7] = "SpyOnThatOverwritesField";
275
- InputIncompatibilityReason[InputIncompatibilityReason["PotentiallyNarrowedInTemplateButNoSupportYet"] = 8] = "PotentiallyNarrowedInTemplateButNoSupportYet";
276
- InputIncompatibilityReason[InputIncompatibilityReason["RequiredInputButNoGoodExplicitTypeExtractable"] = 9] = "RequiredInputButNoGoodExplicitTypeExtractable";
270
+ InputIncompatibilityReason[InputIncompatibilityReason["OverriddenByDerivedClass"] = 1] = "OverriddenByDerivedClass";
271
+ InputIncompatibilityReason[InputIncompatibilityReason["RedeclaredViaDerivedClassInputsArray"] = 2] = "RedeclaredViaDerivedClassInputsArray";
272
+ InputIncompatibilityReason[InputIncompatibilityReason["TypeConflictWithBaseClass"] = 3] = "TypeConflictWithBaseClass";
273
+ InputIncompatibilityReason[InputIncompatibilityReason["ParentIsIncompatible"] = 4] = "ParentIsIncompatible";
274
+ InputIncompatibilityReason[InputIncompatibilityReason["SpyOnThatOverwritesField"] = 5] = "SpyOnThatOverwritesField";
275
+ InputIncompatibilityReason[InputIncompatibilityReason["PotentiallyNarrowedInTemplateButNoSupportYet"] = 6] = "PotentiallyNarrowedInTemplateButNoSupportYet";
276
+ InputIncompatibilityReason[InputIncompatibilityReason["RequiredInputButNoGoodExplicitTypeExtractable"] = 7] = "RequiredInputButNoGoodExplicitTypeExtractable";
277
+ InputIncompatibilityReason[InputIncompatibilityReason["WriteAssignment"] = 8] = "WriteAssignment";
278
+ InputIncompatibilityReason[InputIncompatibilityReason["Accessor"] = 9] = "Accessor";
279
+ InputIncompatibilityReason[InputIncompatibilityReason["OutsideOfMigrationScope"] = 10] = "OutsideOfMigrationScope";
280
+ InputIncompatibilityReason[InputIncompatibilityReason["SkippedViaConfigFilter"] = 11] = "SkippedViaConfigFilter";
277
281
  })(InputIncompatibilityReason || (InputIncompatibilityReason = {}));
278
282
  /** Reasons why a whole class and its inputs cannot be migrated. */
279
283
  var ClassIncompatibilityReason;
@@ -287,6 +291,104 @@ function isInputMemberIncompatibility(value) {
287
291
  value.context !== undefined &&
288
292
  InputIncompatibilityReason.hasOwnProperty(value.reason));
289
293
  }
294
+ /** Picks the more significant input compatibility. */
295
+ function pickInputIncompatibility(a, b) {
296
+ if (b === null) {
297
+ return a;
298
+ }
299
+ if (a.reason < b.reason) {
300
+ return b;
301
+ }
302
+ return a;
303
+ }
304
+
305
+ /**
306
+ * Gets human-readable message information for the given input incompatibility.
307
+ * This text will be used by the language service, or CLI-based migration.
308
+ */
309
+ function getMessageForInputIncompatibility(reason) {
310
+ switch (reason) {
311
+ case InputIncompatibilityReason.Accessor:
312
+ return {
313
+ short: 'Accessor inputs cannot be migrated as they are too complex.',
314
+ extra: 'The migration potentially requires usage of `effect` or `computed`, but ' +
315
+ 'the intent is unclear. The migration cannot safely migrate.',
316
+ };
317
+ case InputIncompatibilityReason.OverriddenByDerivedClass:
318
+ return {
319
+ short: 'The input cannot be migrated because the field is overridden by a subclass.',
320
+ extra: 'The field in the subclass is not an input, so migrating would break your build.',
321
+ };
322
+ case InputIncompatibilityReason.ParentIsIncompatible:
323
+ return {
324
+ short: 'This input is inherited from a superclass, but the parent cannot be migrated.',
325
+ extra: 'Migrating this input would cause your build to fail.',
326
+ };
327
+ case InputIncompatibilityReason.PotentiallyNarrowedInTemplateButNoSupportYet:
328
+ return {
329
+ short: 'This input is used in a control flow expression (e.g. `@if` or `*ngIf`) and ' +
330
+ 'migrating would break narrowing currently.',
331
+ extra: `In the future, Angular intends to support narrowing of signals.`,
332
+ };
333
+ case InputIncompatibilityReason.RedeclaredViaDerivedClassInputsArray:
334
+ return {
335
+ short: 'The input is overridden by a subclass that cannot be migrated.',
336
+ extra: 'The subclass re-declares this input via the `inputs` array in @Directive/@Component. ' +
337
+ 'Migrating this input would break your build because the subclass input cannot be migrated.',
338
+ };
339
+ case InputIncompatibilityReason.RequiredInputButNoGoodExplicitTypeExtractable:
340
+ return {
341
+ short: `Input is required, but the migration cannot determine a good type for the input.`,
342
+ extra: 'Consider adding an explicit type to make the migration possible.',
343
+ };
344
+ case InputIncompatibilityReason.SkippedViaConfigFilter:
345
+ return {
346
+ short: `This input is not part of the current migration scope.`,
347
+ extra: 'Skipped via migration config.',
348
+ };
349
+ case InputIncompatibilityReason.SpyOnThatOverwritesField:
350
+ return {
351
+ short: 'A jasmine `spyOn` call spies on this input. This breaks with signal inputs.',
352
+ extra: `Migration cannot safely migrate as "spyOn" writes to the input. Signal inputs are readonly.`,
353
+ };
354
+ case InputIncompatibilityReason.TypeConflictWithBaseClass:
355
+ return {
356
+ short: 'This input overrides a field from a superclass, while the superclass field is not migrated.',
357
+ extra: 'Migrating the input would break your build because of a type conflict then.',
358
+ };
359
+ case InputIncompatibilityReason.WriteAssignment:
360
+ return {
361
+ short: 'Your application code writes to the input. This prevents migration.',
362
+ extra: 'Signal inputs are readonly, so migrating would break your build.',
363
+ };
364
+ case InputIncompatibilityReason.OutsideOfMigrationScope:
365
+ return {
366
+ short: 'This input is not part of any source files in your project.',
367
+ extra: 'The migration excludes inputs if no source file declaring the input was seen.',
368
+ };
369
+ }
370
+ }
371
+ /**
372
+ * Gets human-readable message information for the given input class incompatibility.
373
+ * This text will be used by the language service, or CLI-based migration.
374
+ */
375
+ function getMessageForClassIncompatibility(reason) {
376
+ switch (reason) {
377
+ case ClassIncompatibilityReason.InputOwningClassReferencedInClassProperty:
378
+ return {
379
+ short: 'Class of this input is referenced in the signature of another class.',
380
+ extra: 'The other class is likely typed to expect a non-migrated field, so ' +
381
+ 'migration is skipped to not break your build.',
382
+ };
383
+ case ClassIncompatibilityReason.ClassManuallyInstantiated:
384
+ return {
385
+ short: 'Class of this input is manually instantiated. ' +
386
+ 'This is discouraged and prevents migration',
387
+ extra: 'Signal inputs require a DI injection context. Manually instantiating ' +
388
+ 'breaks this requirement in some cases, so the migration is skipped.',
389
+ };
390
+ }
391
+ }
290
392
 
291
393
  /**
292
394
  * Class that holds information about a given directive and its input fields.
@@ -321,7 +423,11 @@ class DirectiveInfo {
321
423
  * then the member is as well.
322
424
  */
323
425
  isInputMemberIncompatible(input) {
324
- return this.incompatible !== null || this.memberIncompatibility.has(input.key);
426
+ return this.getInputMemberIncompatibility(input) !== null;
427
+ }
428
+ /** Get incompatibility of the given member, if it's incompatible for migration. */
429
+ getInputMemberIncompatibility(input) {
430
+ return this.memberIncompatibility.get(input.key) ?? this.incompatible ?? null;
325
431
  }
326
432
  }
327
433
 
@@ -345,9 +451,11 @@ function isInputContainerNode(node) {
345
451
  getMemberName(node) !== null);
346
452
  }
347
453
 
454
+ /** Code of the error raised by TypeScript when a tsconfig doesn't match any files. */
455
+ const NO_INPUTS_ERROR_CODE = 18003;
348
456
  /**
349
457
  * Parses the configuration of the given TypeScript project and creates
350
- * an instance of the Angular compiler for for the project.
458
+ * an instance of the Angular compiler for the project.
351
459
  */
352
460
  function createNgtscProgram(absoluteTsconfigPath, fs, optionOverrides = {}) {
353
461
  if (fs === undefined) {
@@ -355,9 +463,11 @@ function createNgtscProgram(absoluteTsconfigPath, fs, optionOverrides = {}) {
355
463
  checker.setFileSystem(fs);
356
464
  }
357
465
  const tsconfig = readConfiguration(absoluteTsconfigPath, {}, fs);
358
- if (tsconfig.errors.length > 0) {
359
- throw new Error(`Tsconfig could not be parsed or is invalid:\n\n` +
360
- `${tsconfig.errors.map((e) => e.messageText)}`);
466
+ // Skip the "No inputs found..." error since we don't want to interrupt the migration if a
467
+ // tsconfig doesn't match a file. This will result in an empty `Program` which is still valid.
468
+ const errors = tsconfig.errors.filter((diag) => diag.code !== NO_INPUTS_ERROR_CODE);
469
+ if (errors.length) {
470
+ throw new Error(`Tsconfig could not be parsed or is invalid:\n\n` + `${errors.map((e) => e.messageText)}`);
361
471
  }
362
472
  const tsHost = new NgtscCompilerHost(fs, tsconfig.options);
363
473
  const ngtscProgram = new program.NgtscProgram(tsconfig.rootNames, {
@@ -560,8 +670,6 @@ class KnownInputs {
560
670
  * Known inputs from the whole program.
561
671
  */
562
672
  this.knownInputIds = new Map();
563
- // TODO: perf comment
564
- this.fieldNamesToConsiderForReferenceLookup = new Set();
565
673
  /** Known container classes of inputs. */
566
674
  this._allClasses = new Set();
567
675
  /** Maps classes to their directive info. */
@@ -598,6 +706,7 @@ class KnownInputs {
598
706
  metadata: data.metadata,
599
707
  descriptor: data.descriptor,
600
708
  container: directiveInfo,
709
+ extendsFrom: null,
601
710
  isIncompatible: () => directiveInfo.isInputMemberIncompatible(data.descriptor),
602
711
  };
603
712
  directiveInfo.inputFields.set(data.descriptor.key, {
@@ -606,9 +715,6 @@ class KnownInputs {
606
715
  });
607
716
  this.knownInputIds.set(data.descriptor.key, inputInfo);
608
717
  this._allClasses.add(data.node.parent);
609
- if (this.config.shouldMigrateInput?.(inputInfo) ?? true) {
610
- this.fieldNamesToConsiderForReferenceLookup.add(data.descriptor.node.name.text);
611
- }
612
718
  }
613
719
  /** Whether the given input is incompatible for migration. */
614
720
  isFieldIncompatible(descriptor) {
@@ -619,6 +725,12 @@ class KnownInputs {
619
725
  if (!this.knownInputIds.has(input.key)) {
620
726
  throw new Error(`Input cannot be marked as incompatible because it's not registered.`);
621
727
  }
728
+ const inputInfo = this.knownInputIds.get(input.key);
729
+ const existingIncompatibility = inputInfo.container.getInputMemberIncompatibility(input);
730
+ // Ensure an existing more significant incompatibility is not overridden.
731
+ if (existingIncompatibility !== null && isInputMemberIncompatibility(existingIncompatibility)) {
732
+ incompatibility = pickInputIncompatibility(existingIncompatibility, incompatibility);
733
+ }
622
734
  this.knownInputIds
623
735
  .get(input.key)
624
736
  .container.memberIncompatibility.set(input.key, incompatibility);
@@ -636,6 +748,24 @@ class KnownInputs {
636
748
  shouldTrackClassReference(clazz) {
637
749
  return this.isInputContainingClass(clazz);
638
750
  }
751
+ captureKnownFieldInheritanceRelationship(derived, parent) {
752
+ if (!this.has(derived)) {
753
+ throw new Error(`Expected input to exist in registry: ${derived.key}`);
754
+ }
755
+ this.get(derived).extendsFrom = parent;
756
+ }
757
+ captureUnknownDerivedField(field) {
758
+ this.markFieldIncompatible(field, {
759
+ context: null,
760
+ reason: InputIncompatibilityReason.OverriddenByDerivedClass,
761
+ });
762
+ }
763
+ captureUnknownParentField(field) {
764
+ this.markFieldIncompatible(field, {
765
+ context: null,
766
+ reason: InputIncompatibilityReason.TypeConflictWithBaseClass,
767
+ });
768
+ }
639
769
  }
640
770
 
641
771
  /**
@@ -860,6 +990,7 @@ function prepareAndCheckForConversion(node, metadata, checker, options) {
860
990
  ts__default["default"].factory.createKeywordTypeNode(ts__default["default"].SyntaxKind.UndefinedKeyword),
861
991
  ]);
862
992
  }
993
+ let leadingTodoText = null;
863
994
  // If the input does not have an initial value, and strict property initialization
864
995
  // is disabled, while strict null checks are enabled; then we know that `undefined`
865
996
  // cannot be used as initial value, nor do we want to expand the input's type magically.
@@ -872,6 +1003,9 @@ function prepareAndCheckForConversion(node, metadata, checker, options) {
872
1003
  node.exclamationToken === undefined &&
873
1004
  metadata.required === false &&
874
1005
  !checker.isTypeAssignableTo(checker.getUndefinedType(), checker.getTypeFromTypeNode(node.type))) {
1006
+ leadingTodoText =
1007
+ 'Input is initialized to `undefined` but type does not allow this value. ' +
1008
+ 'This worked with `@Input` because your project uses `--strictPropertyInitialization=false`.';
875
1009
  isUndefinedInitialValue = false;
876
1010
  initialValue = ts__default["default"].factory.createNonNullExpression(ts__default["default"].factory.createIdentifier('undefined'));
877
1011
  }
@@ -914,6 +1048,7 @@ function prepareAndCheckForConversion(node, metadata, checker, options) {
914
1048
  preferShorthandIfPossible,
915
1049
  originalInputDecorator: metadata.inputDecorator,
916
1050
  initialValue: isUndefinedInitialValue ? undefined : initialValue,
1051
+ leadingTodoText,
917
1052
  };
918
1053
  }
919
1054
 
@@ -12504,7 +12639,10 @@ function formatValue(value) {
12504
12639
  // Self-closing tags use a special form that concatenates the start and close tag values.
12505
12640
  if (value.flags & I18nParamValueFlags.OpenTag &&
12506
12641
  value.flags & I18nParamValueFlags.CloseTag) {
12507
- return `${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.CloseTag })}${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.OpenTag })}`;
12642
+ return `${formatValue({
12643
+ ...value,
12644
+ flags: value.flags & ~I18nParamValueFlags.CloseTag,
12645
+ })}${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.OpenTag })}`;
12508
12646
  }
12509
12647
  // If there are no special flags, just return the raw value.
12510
12648
  if (value.flags === I18nParamValueFlags.None) {
@@ -12896,13 +13034,17 @@ class IcuSerializerVisitor {
12896
13034
  visitTagPlaceholder(ph) {
12897
13035
  return ph.isVoid
12898
13036
  ? this.formatPh(ph.startName)
12899
- : `${this.formatPh(ph.startName)}${ph.children.map((child) => child.visit(this)).join('')}${this.formatPh(ph.closeName)}`;
13037
+ : `${this.formatPh(ph.startName)}${ph.children
13038
+ .map((child) => child.visit(this))
13039
+ .join('')}${this.formatPh(ph.closeName)}`;
12900
13040
  }
12901
13041
  visitPlaceholder(ph) {
12902
13042
  return this.formatPh(ph.name);
12903
13043
  }
12904
13044
  visitBlockPlaceholder(ph) {
12905
- return `${this.formatPh(ph.startName)}${ph.children.map((child) => child.visit(this)).join('')}${this.formatPh(ph.closeName)}`;
13045
+ return `${this.formatPh(ph.startName)}${ph.children
13046
+ .map((child) => child.visit(this))
13047
+ .join('')}${this.formatPh(ph.closeName)}`;
12906
13048
  }
12907
13049
  visitIcuPlaceholder(ph, context) {
12908
13050
  return this.formatPh(ph.name);
@@ -20198,13 +20340,17 @@ class GetMsgSerializerVisitor {
20198
20340
  visitTagPlaceholder(ph) {
20199
20341
  return ph.isVoid
20200
20342
  ? this.formatPh(ph.startName)
20201
- : `${this.formatPh(ph.startName)}${ph.children.map((child) => child.visit(this)).join('')}${this.formatPh(ph.closeName)}`;
20343
+ : `${this.formatPh(ph.startName)}${ph.children
20344
+ .map((child) => child.visit(this))
20345
+ .join('')}${this.formatPh(ph.closeName)}`;
20202
20346
  }
20203
20347
  visitPlaceholder(ph) {
20204
20348
  return this.formatPh(ph.name);
20205
20349
  }
20206
20350
  visitBlockPlaceholder(ph) {
20207
- return `${this.formatPh(ph.startName)}${ph.children.map((child) => child.visit(this)).join('')}${this.formatPh(ph.closeName)}`;
20351
+ return `${this.formatPh(ph.startName)}${ph.children
20352
+ .map((child) => child.visit(this))
20353
+ .join('')}${this.formatPh(ph.closeName)}`;
20208
20354
  }
20209
20355
  visitIcuPlaceholder(ph, context) {
20210
20356
  return this.formatPh(ph.name);
@@ -24315,7 +24461,7 @@ function wrapI18nIcus(job) {
24315
24461
  * Copyright Google LLC All Rights Reserved.
24316
24462
  *
24317
24463
  * Use of this source code is governed by an MIT-style license that can be
24318
- * found in the LICENSE file at https://angular.io/license
24464
+ * found in the LICENSE file at https://angular.dev/license
24319
24465
  */
24320
24466
  /**
24321
24467
  * Removes any `storeLet` calls that aren't referenced outside of the current view.
@@ -24398,7 +24544,7 @@ function generateLocalLetReferences(job) {
24398
24544
  * Copyright Google LLC All Rights Reserved.
24399
24545
  *
24400
24546
  * Use of this source code is governed by an MIT-style license that can be
24401
- * found in the LICENSE file at https://angular.io/license
24547
+ * found in the LICENSE file at https://angular.dev/license
24402
24548
  */
24403
24549
  const phases = [
24404
24550
  { kind: CompilationJobKind.Tmpl, fn: removeContentSelectors },
@@ -29874,7 +30020,7 @@ function publishFacade(global) {
29874
30020
  * @description
29875
30021
  * Entry point for all public APIs of the compiler package.
29876
30022
  */
29877
- new Version('19.0.0-next.6');
30023
+ new Version('19.0.0-next.7');
29878
30024
 
29879
30025
  var _VisitorMode;
29880
30026
  (function (_VisitorMode) {
@@ -30079,9 +30225,12 @@ class TemplateExpressionReferenceVisitor extends RecursiveAstVisitor {
30079
30225
  checkTemplateExpression(activeNode, expressionNode) {
30080
30226
  this.detectedInputReferences = [];
30081
30227
  this.activeTmplAstNode = activeNode;
30082
- expressionNode.visit(this);
30228
+ expressionNode.visit(this, []);
30083
30229
  return this.detectedInputReferences;
30084
30230
  }
30231
+ visit(ast, context) {
30232
+ super.visit(ast, [...context, ast]);
30233
+ }
30085
30234
  // Keep track when we are inside an object shorthand expression. This is
30086
30235
  // necessary as we need to expand the shorthand to invoke a potential new signal.
30087
30236
  // E.g. `{bla}` may be transformed to `{bla: bla()}`.
@@ -30091,35 +30240,34 @@ class TemplateExpressionReferenceVisitor extends RecursiveAstVisitor {
30091
30240
  ast.values[idx].visit(this, context);
30092
30241
  this.isInsideObjectShorthandExpression = false;
30093
30242
  }
30094
- super.visitLiteralMap(ast, context);
30095
30243
  }
30096
- visitPropertyRead(ast) {
30097
- this._inspectPropertyAccess(ast);
30098
- super.visitPropertyRead(ast, null);
30244
+ visitPropertyRead(ast, context) {
30245
+ this._inspectPropertyAccess(ast, context);
30246
+ super.visitPropertyRead(ast, context);
30099
30247
  }
30100
- visitSafePropertyRead(ast) {
30101
- this._inspectPropertyAccess(ast);
30102
- super.visitPropertyRead(ast, null);
30248
+ visitSafePropertyRead(ast, context) {
30249
+ this._inspectPropertyAccess(ast, context);
30250
+ super.visitPropertyRead(ast, context);
30103
30251
  }
30104
- visitPropertyWrite(ast) {
30105
- this._inspectPropertyAccess(ast);
30106
- super.visitPropertyWrite(ast, null);
30252
+ visitPropertyWrite(ast, context) {
30253
+ this._inspectPropertyAccess(ast, context);
30254
+ super.visitPropertyWrite(ast, context);
30107
30255
  }
30108
30256
  /**
30109
30257
  * Inspects the property access and attempts to resolve whether they access
30110
30258
  * a known field. If so, the result is captured.
30111
30259
  */
30112
- _inspectPropertyAccess(ast) {
30260
+ _inspectPropertyAccess(ast, astPath) {
30113
30261
  const isWrite = !!(ast instanceof PropertyWrite ||
30114
30262
  (this.activeTmplAstNode && isTwoWayBindingNode(this.activeTmplAstNode)));
30115
- this._checkAccessViaTemplateTypeCheckBlock(ast, isWrite) ||
30116
- this._checkAccessViaOwningComponentClassType(ast, isWrite);
30263
+ this._checkAccessViaTemplateTypeCheckBlock(ast, isWrite, astPath) ||
30264
+ this._checkAccessViaOwningComponentClassType(ast, isWrite, astPath);
30117
30265
  }
30118
30266
  /**
30119
30267
  * Checks whether the node refers to an input using the TCB information.
30120
30268
  * Type check block may not exist for e.g. test components, so this can return `null`.
30121
30269
  */
30122
- _checkAccessViaTemplateTypeCheckBlock(ast, isWrite) {
30270
+ _checkAccessViaTemplateTypeCheckBlock(ast, isWrite, astPath) {
30123
30271
  // There might be no template type checker. E.g. if we check host bindings.
30124
30272
  if (this.templateTypeChecker === null) {
30125
30273
  return false;
@@ -30139,6 +30287,7 @@ class TemplateExpressionReferenceVisitor extends RecursiveAstVisitor {
30139
30287
  targetNode: targetInput.node,
30140
30288
  targetField: targetInput,
30141
30289
  read: ast,
30290
+ readAstPath: astPath,
30142
30291
  context: this.activeTmplAstNode,
30143
30292
  isLikelyNarrowed: false,
30144
30293
  isObjectShorthandExpression: this.isInsideObjectShorthandExpression,
@@ -30153,7 +30302,7 @@ class TemplateExpressionReferenceVisitor extends RecursiveAstVisitor {
30153
30302
  * It attempts to resolve references by traversing accesses of the "component class" type.
30154
30303
  * e.g. `this.bla` is resolved via `CompType#bla` and further.
30155
30304
  */
30156
- _checkAccessViaOwningComponentClassType(ast, isWrite) {
30305
+ _checkAccessViaOwningComponentClassType(ast, isWrite, astPath) {
30157
30306
  // We might check host bindings, which can never point to template variables or local refs.
30158
30307
  const expressionTemplateTarget = this.templateTypeChecker === null
30159
30308
  ? null
@@ -30176,6 +30325,7 @@ class TemplateExpressionReferenceVisitor extends RecursiveAstVisitor {
30176
30325
  targetNode: matchingTarget.node,
30177
30326
  targetField: matchingTarget,
30178
30327
  read: ast,
30328
+ readAstPath: astPath,
30179
30329
  context: this.activeTmplAstNode,
30180
30330
  isLikelyNarrowed: false,
30181
30331
  isObjectShorthandExpression: this.isInsideObjectShorthandExpression,
@@ -30326,6 +30476,7 @@ function identifyHostBindingReferences(node, programInfo, checker$1, reflector,
30326
30476
  kind: ReferenceKind.InHostBinding,
30327
30477
  from: {
30328
30478
  read: ref.read,
30479
+ readAstPath: ref.readAstPath,
30329
30480
  isObjectShorthandExpression: ref.isObjectShorthandExpression,
30330
30481
  isWrite: ref.isWrite,
30331
30482
  file: projectFile(ref.context.getSourceFile(), programInfo),
@@ -30425,6 +30576,7 @@ function identifyTemplateReferences(programInfo, node, reflector, checker$1, eva
30425
30576
  kind: ReferenceKind.InTemplate,
30426
30577
  from: {
30427
30578
  read: res.read,
30579
+ readAstPath: res.readAstPath,
30428
30580
  node: res.context,
30429
30581
  isObjectShorthandExpression: res.isObjectShorthandExpression,
30430
30582
  originatingTsFile: projectFile(node.getSourceFile(), programInfo),
@@ -30546,12 +30698,16 @@ const writeBinaryOperators = [
30546
30698
  /**
30547
30699
  * Checks whether given TypeScript reference refers to an Angular input, and captures
30548
30700
  * the reference if possible.
30701
+ *
30702
+ * @param fieldNamesToConsiderForReferenceLookup List of field names that should be
30703
+ * respected when expensively looking up references to known fields.
30704
+ * May be null if all identifiers should be inspected.
30549
30705
  */
30550
- function identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, advisors) {
30706
+ function identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, fieldNamesToConsiderForReferenceLookup, advisors) {
30551
30707
  // Skip all identifiers that never can point to a migrated field.
30552
30708
  // TODO: Capture these assumptions and performance optimizations in the design doc.
30553
- if (knownFields.fieldNamesToConsiderForReferenceLookup !== null &&
30554
- !knownFields.fieldNamesToConsiderForReferenceLookup.has(node.text)) {
30709
+ if (fieldNamesToConsiderForReferenceLookup !== null &&
30710
+ !fieldNamesToConsiderForReferenceLookup.has(node.text)) {
30555
30711
  return;
30556
30712
  }
30557
30713
  let target = undefined;
@@ -30626,7 +30782,7 @@ function identifyPotentialTypeScriptReference(node, programInfo, checker, knownF
30626
30782
  * - Angular templates (inline or external)
30627
30783
  * - Host binding expressions.
30628
30784
  */
30629
- function createFindAllSourceFileReferencesVisitor(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, knownFields, result) {
30785
+ function createFindAllSourceFileReferencesVisitor(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, knownFields, fieldNamesToConsiderForReferenceLookup, result) {
30630
30786
  const debugElComponentInstanceTracker = new DebugElementComponentInstance(checker);
30631
30787
  const partialDirectiveCatalystTracker = new PartialDirectiveTypeInCatalystTests(checker, knownFields);
30632
30788
  const perfCounters = {
@@ -30635,26 +30791,28 @@ function createFindAllSourceFileReferencesVisitor(programInfo, checker, reflecto
30635
30791
  tsReferences: 0,
30636
30792
  tsTypes: 0,
30637
30793
  };
30794
+ // Schematic NodeJS execution may not have `global.performance` defined.
30795
+ const currentTimeInMs = () => typeof global.performance !== 'undefined' ? global.performance.now() : Date.now();
30638
30796
  const visitor = (node) => {
30639
- let lastTime = performance.now();
30797
+ let lastTime = currentTimeInMs();
30640
30798
  if (ts__default["default"].isClassDeclaration(node)) {
30641
30799
  identifyTemplateReferences(programInfo, node, reflector, checker, evaluator, templateTypeChecker, resourceLoader, programInfo.userOptions, result, knownFields);
30642
- perfCounters.template += (performance.now() - lastTime) / 1000;
30643
- lastTime = performance.now();
30800
+ perfCounters.template += (currentTimeInMs() - lastTime) / 1000;
30801
+ lastTime = currentTimeInMs();
30644
30802
  identifyHostBindingReferences(node, programInfo, checker, reflector, result, knownFields);
30645
- perfCounters.hostBindings += (performance.now() - lastTime) / 1000;
30646
- lastTime = performance.now();
30803
+ perfCounters.hostBindings += (currentTimeInMs() - lastTime) / 1000;
30804
+ lastTime = currentTimeInMs();
30647
30805
  }
30648
- lastTime = performance.now();
30806
+ lastTime = currentTimeInMs();
30649
30807
  // find references, but do not capture input declarations itself.
30650
30808
  if (ts__default["default"].isIdentifier(node) &&
30651
30809
  !(isInputContainerNode(node.parent) && node.parent.name === node)) {
30652
- identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, {
30810
+ identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, fieldNamesToConsiderForReferenceLookup, {
30653
30811
  debugElComponentInstanceTracker,
30654
30812
  });
30655
30813
  }
30656
- perfCounters.tsReferences += (performance.now() - lastTime) / 1000;
30657
- lastTime = performance.now();
30814
+ perfCounters.tsReferences += (currentTimeInMs() - lastTime) / 1000;
30815
+ lastTime = currentTimeInMs();
30658
30816
  // Detect `Partial<T>` references.
30659
30817
  // Those are relevant to be tracked as they may be updated in Catalyst to
30660
30818
  // unwrap signal inputs. Commonly people use `Partial` in Catalyst to type
@@ -30672,7 +30830,7 @@ function createFindAllSourceFileReferencesVisitor(programInfo, checker, reflecto
30672
30830
  target: partialDirectiveInCatalyst.targetClass,
30673
30831
  });
30674
30832
  }
30675
- perfCounters.tsTypes += (performance.now() - lastTime) / 1000;
30833
+ perfCounters.tsTypes += (currentTimeInMs() - lastTime) / 1000;
30676
30834
  };
30677
30835
  return {
30678
30836
  visitor,
@@ -30692,8 +30850,8 @@ function createFindAllSourceFileReferencesVisitor(programInfo, checker, reflecto
30692
30850
  * In addition, spying onto an input may be problematic- so we skip migrating
30693
30851
  * such.
30694
30852
  */
30695
- function pass2_IdentifySourceFileReferences(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, groupedTsAstVisitor, knownInputs, result) {
30696
- groupedTsAstVisitor.register(createFindAllSourceFileReferencesVisitor(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, knownInputs, result).visitor);
30853
+ function pass2_IdentifySourceFileReferences(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, groupedTsAstVisitor, knownInputs, result, fieldNamesToConsiderForReferenceLookup) {
30854
+ groupedTsAstVisitor.register(createFindAllSourceFileReferencesVisitor(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, knownInputs, fieldNamesToConsiderForReferenceLookup, result).visitor);
30697
30855
  }
30698
30856
 
30699
30857
  /** Gets all types that are inherited (implemented or extended). */
@@ -30722,14 +30880,18 @@ class InheritanceGraph {
30722
30880
  constructor(checker) {
30723
30881
  this.checker = checker;
30724
30882
  /** Maps nodes to their parent nodes. */
30725
- this.classParents = new Map();
30883
+ this.classToParents = new Map();
30726
30884
  /** Maps nodes to their derived nodes. */
30727
30885
  this.parentToChildren = new Map();
30886
+ /** All classes seen participating in inheritance chains. */
30887
+ this.allClassesInInheritance = new Set();
30728
30888
  }
30729
30889
  /** Registers a given class in the graph. */
30730
30890
  registerClass(clazz, parents) {
30731
- this.classParents.set(clazz, parents);
30891
+ this.classToParents.set(clazz, parents);
30892
+ this.allClassesInInheritance.add(clazz);
30732
30893
  for (const parent of parents) {
30894
+ this.allClassesInInheritance.add(parent);
30733
30895
  if (!this.parentToChildren.has(parent)) {
30734
30896
  this.parentToChildren.set(parent, []);
30735
30897
  }
@@ -30743,7 +30905,7 @@ class InheritanceGraph {
30743
30905
  * @returns Symbols of the inherited or derived members, if they exist.
30744
30906
  */
30745
30907
  checkOverlappingMembers(clazz, member, memberName) {
30746
- const inheritedTypes = (this.classParents.get(clazz) ?? []).map((c) => this.checker.getTypeAtLocation(c));
30908
+ const inheritedTypes = (this.classToParents.get(clazz) ?? []).map((c) => this.checker.getTypeAtLocation(c));
30747
30909
  const derivedLeafs = this._traceDerivedChainToLeafs(clazz).map((c) => this.checker.getTypeAtLocation(c));
30748
30910
  const inheritedMember = inheritedTypes
30749
30911
  .map((t) => t.getProperty(memberName))
@@ -30874,12 +31036,19 @@ function executeAnalysisPhase(host, knownInputs, result, { sourceFiles, fullProg
30874
31036
  // e.g. `ngtypecheck` files.
30875
31037
  !checker.isShim(sf) &&
30876
31038
  pass1__IdentifySourceFileAndDeclarationInputs(sf, host, typeChecker, reflector, dtsMetadataReader, evaluator, refEmitter, knownInputs, result));
31039
+ const fieldNamesToConsiderForReferenceLookup = new Set();
31040
+ for (const input of knownInputs.knownInputIds.values()) {
31041
+ if (host.config.shouldMigrateInput?.(input) === false) {
31042
+ continue;
31043
+ }
31044
+ fieldNamesToConsiderForReferenceLookup.add(input.descriptor.node.name.text);
31045
+ }
30877
31046
  // A graph starting with source files is sufficient. We will resolve into
30878
31047
  // declaration files if a source file depends on such.
30879
31048
  const inheritanceGraph = new InheritanceGraph(typeChecker).expensivePopulate(sourceFiles);
30880
31049
  const pass2And3SourceFileVisitor = new GroupedTsAstVisitor(sourceFiles);
30881
31050
  // Register pass 2. Find all source file references.
30882
- pass2_IdentifySourceFileReferences(host.programInfo, typeChecker, reflector, resourceLoader, evaluator, templateTypeChecker, pass2And3SourceFileVisitor, knownInputs, result);
31051
+ pass2_IdentifySourceFileReferences(host.programInfo, typeChecker, reflector, resourceLoader, evaluator, templateTypeChecker, pass2And3SourceFileVisitor, knownInputs, result, fieldNamesToConsiderForReferenceLookup);
30883
31052
  // Register pass 3. Check incompatible patterns pass.
30884
31053
  pass3__checkIncompatiblePatterns(inheritanceGraph, typeChecker, pass2And3SourceFileVisitor, knownInputs);
30885
31054
  // Perform Pass 2 and Pass 3, efficiently in one pass.
@@ -30915,44 +31084,6 @@ function executeAnalysisPhase(host, knownInputs, result, { sourceFiles, fullProg
30915
31084
  return { inheritanceGraph };
30916
31085
  }
30917
31086
 
30918
- /**
30919
- * Sorts the inheritance graph topologically, so that
30920
- * classes without incoming edges are returned first.
30921
- *
30922
- * I.e. The returned list is sorted, so that dependencies
30923
- * of a given class are guaranteed to be included at
30924
- * an earlier position than the inspected class.
30925
- *
30926
- * This sort is helpful for detecting inheritance problems
30927
- * for the migration in simpler ways, without having to
30928
- * check in both directions (base classes, and derived classes).
30929
- */
30930
- function topologicalSort(graph) {
30931
- // All classes without incoming edges.
30932
- const S = Array.from(graph.classParents.keys()).filter((n) => !graph.parentToChildren.has(n) || graph.parentToChildren.get(n).length === 0);
30933
- const result = [];
30934
- const classParents = new Map(graph.classParents);
30935
- const parentToChildren = new Map(graph.parentToChildren);
30936
- while (S.length) {
30937
- const node = S.pop();
30938
- result.push(node);
30939
- for (const next of classParents.get(node) ?? []) {
30940
- // Remove edge from "node -> next".
30941
- classParents.set(node, classParents.get(node).filter((n) => n !== next));
30942
- // Remove edge from "next -> node". Do not modify original array as it might
30943
- // be the one from the original graph
30944
- const newParentToChildrenForNext = [...parentToChildren.get(next)];
30945
- newParentToChildrenForNext.splice(newParentToChildrenForNext.indexOf(node), 1);
30946
- parentToChildren.set(next, newParentToChildrenForNext);
30947
- // if there are no incoming edges for `next`. add it to `S`.
30948
- if (parentToChildren.get(next).length === 0) {
30949
- S.push(next);
30950
- }
30951
- }
30952
- }
30953
- return result;
30954
- }
30955
-
30956
31087
  /**
30957
31088
  * Phase that propagates incompatibilities to derived classes or
30958
31089
  * base classes. For example, consider:
@@ -30974,14 +31105,8 @@ function topologicalSort(graph) {
30974
31105
  * would then have other derived classes as well, it would propagate the status.
30975
31106
  */
30976
31107
  function checkInheritanceOfKnownFields(inheritanceGraph, metaRegistry, fields, opts) {
30977
- // Sort topologically and iterate super classes first, so that we can trivially
30978
- // propagate incompatibility statuses (and other checks) without having to check
30979
- // in both directions (derived classes, or base classes). This simplifies the logic
30980
- // further down in this function significantly.
30981
- const topologicalSortedClasses = topologicalSort(inheritanceGraph)
30982
- .filter((t) => ts__default["default"].isClassDeclaration(t) && opts.isClassWithKnownFields(t))
30983
- .reverse();
30984
- for (const inputClass of topologicalSortedClasses) {
31108
+ const allInputClasses = Array.from(inheritanceGraph.allClassesInInheritance).filter((t) => ts__default["default"].isClassDeclaration(t) && opts.isClassWithKnownFields(t));
31109
+ for (const inputClass of allInputClasses) {
30985
31110
  // Note: Class parents of `inputClass` were already checked by
30986
31111
  // the previous iterations (given the reverse topological sort)—
30987
31112
  // hence it's safe to assume that incompatibility of parent classes will
@@ -31009,22 +31134,18 @@ function checkInheritanceOfKnownFields(inheritanceGraph, metaRegistry, fields, o
31009
31134
  if (fieldDescr.node.name !== undefined &&
31010
31135
  (ts__default["default"].isIdentifier(fieldDescr.node.name) || ts__default["default"].isStringLiteralLike(fieldDescr.node.name)) &&
31011
31136
  inputFieldNamesFromMetadataArray.has(fieldDescr.node.name.text)) {
31012
- fields.markFieldIncompatible(fieldDescr, {
31013
- context: null,
31014
- reason: InputIncompatibilityReason.RedeclaredViaDerivedClassInputsArray,
31015
- });
31137
+ fields.captureUnknownDerivedField(fieldDescr);
31016
31138
  }
31017
31139
  for (const derived of derivedMembers) {
31018
31140
  const derivedInput = fields.attemptRetrieveDescriptorFromSymbol(derived);
31019
31141
  if (derivedInput !== null) {
31142
+ // Note: We always track dependencies from the child to the parent,
31143
+ // so skip here for now.
31020
31144
  continue;
31021
31145
  }
31022
31146
  // If we discover a derived, non-input member, then it will cause
31023
31147
  // conflicts, and we mark the current input as incompatible.
31024
- fields.markFieldIncompatible(fieldDescr, {
31025
- context: derived.valueDeclaration ?? inputNode,
31026
- reason: InputIncompatibilityReason.OverriddenByDerivedClass,
31027
- });
31148
+ fields.captureUnknownDerivedField(fieldDescr);
31028
31149
  continue inputCheck;
31029
31150
  }
31030
31151
  // If there is no parent, we are done. Otherwise, check the parent
@@ -31035,21 +31156,10 @@ function checkInheritanceOfKnownFields(inheritanceGraph, metaRegistry, fields, o
31035
31156
  const inheritedMemberInput = fields.attemptRetrieveDescriptorFromSymbol(inherited);
31036
31157
  // Parent is not an input, and hence will conflict..
31037
31158
  if (inheritedMemberInput === null) {
31038
- fields.markFieldIncompatible(fieldDescr, {
31039
- context: inherited.valueDeclaration ?? inputNode,
31040
- reason: InputIncompatibilityReason.TypeConflictWithBaseClass,
31041
- });
31042
- continue;
31043
- }
31044
- // Parent is incompatible, so this input also needs to be.
31045
- // It cannot be migrated.
31046
- if (fields.isFieldIncompatible(inheritedMemberInput)) {
31047
- fields.markFieldIncompatible(fieldDescr, {
31048
- context: inheritedMemberInput.node,
31049
- reason: InputIncompatibilityReason.ParentIsIncompatible,
31050
- });
31159
+ fields.captureUnknownParentField(fieldDescr);
31051
31160
  continue;
31052
31161
  }
31162
+ fields.captureKnownFieldInheritanceRelationship(fieldDescr, inheritedMemberInput);
31053
31163
  }
31054
31164
  }
31055
31165
  }
@@ -31085,134 +31195,156 @@ function pass4__checkInheritanceOfInputs(inheritanceGraph, metaRegistry, knownIn
31085
31195
  });
31086
31196
  }
31087
31197
 
31088
- /** Type of incompatibility. */
31089
- var IncompatibilityType;
31090
- (function (IncompatibilityType) {
31091
- IncompatibilityType[IncompatibilityType["VIA_CLASS"] = 0] = "VIA_CLASS";
31092
- IncompatibilityType[IncompatibilityType["VIA_INPUT"] = 1] = "VIA_INPUT";
31093
- })(IncompatibilityType || (IncompatibilityType = {}));
31094
-
31095
- function getCompilationUnitMetadata(knownInputs, result) {
31198
+ function getCompilationUnitMetadata(knownInputs) {
31096
31199
  const struct = {
31097
31200
  knownInputs: Array.from(knownInputs.knownInputIds.entries()).reduce((res, [inputClassFieldIdStr, info]) => {
31098
- const classIncompatibility = info.container.incompatible !== null
31099
- ? { kind: IncompatibilityType.VIA_CLASS, reason: info.container.incompatible }
31100
- : null;
31201
+ const classIncompatibility = info.container.incompatible !== null ? info.container.incompatible : null;
31101
31202
  const memberIncompatibility = info.container.memberIncompatibility.has(inputClassFieldIdStr)
31102
- ? {
31103
- kind: IncompatibilityType.VIA_INPUT,
31104
- reason: info.container.memberIncompatibility.get(inputClassFieldIdStr).reason,
31105
- }
31203
+ ? info.container.memberIncompatibility.get(inputClassFieldIdStr).reason
31106
31204
  : null;
31107
- const incompatibility = classIncompatibility ?? memberIncompatibility ?? null;
31108
31205
  // Note: Trim off the `context` as it cannot be serialized with e.g. TS nodes.
31109
31206
  return {
31110
31207
  ...res,
31111
31208
  [inputClassFieldIdStr]: {
31112
- isIncompatible: incompatibility,
31209
+ owningClassIncompatibility: classIncompatibility,
31210
+ memberIncompatibility,
31211
+ seenAsSourceInput: info.metadata.inSourceFile,
31212
+ extendsFrom: info.extendsFrom?.key ?? null,
31113
31213
  },
31114
31214
  };
31115
31215
  }, {}),
31116
- references: result.references.map((r) => {
31117
- if (isTsReference(r)) {
31118
- return {
31119
- kind: r.kind,
31120
- target: r.target.key,
31121
- from: {
31122
- file: r.from.file,
31123
- node: { positionEndInFile: r.from.node.getEnd() },
31124
- isWrite: r.from.isWrite,
31125
- isPartOfElementBinding: r.from.isPartOfElementBinding,
31126
- },
31127
- };
31128
- }
31129
- else if (isHostBindingReference(r)) {
31130
- return {
31131
- kind: r.kind,
31132
- target: r.target.key,
31133
- from: {
31134
- file: r.from.file,
31135
- hostPropertyNode: { positionEndInFile: r.from.hostPropertyNode.getEnd() },
31136
- isObjectShorthandExpression: r.from.isObjectShorthandExpression,
31137
- isWrite: r.from.isWrite,
31138
- read: { positionEndInFile: r.from.read.sourceSpan.end },
31139
- },
31140
- };
31141
- }
31142
- else if (isTsClassTypeReference(r)) {
31143
- return {
31144
- kind: r.kind,
31145
- target: { positionEndInFile: r.target.getEnd() },
31146
- from: {
31147
- file: r.from.file,
31148
- node: { positionEndInFile: r.from.node.getEnd() },
31149
- },
31150
- isPartOfCatalystFile: r.isPartOfCatalystFile,
31151
- isPartialReference: r.isPartialReference,
31152
- };
31153
- }
31154
- return {
31155
- kind: r.kind,
31156
- target: r.target.key,
31157
- from: {
31158
- originatingTsFile: r.from.originatingTsFile,
31159
- templateFile: r.from.templateFile,
31160
- isObjectShorthandExpression: r.from.isObjectShorthandExpression,
31161
- isLikelyPartOfNarrowing: r.from.isLikelyPartOfNarrowing,
31162
- isWrite: r.from.isWrite,
31163
- node: { positionEndInFile: r.from.node.sourceSpan.end.offset },
31164
- read: { positionEndInFile: r.from.read.sourceSpan.end },
31165
- },
31166
- };
31167
- }),
31168
31216
  };
31169
31217
  return struct;
31170
31218
  }
31171
31219
 
31220
+ /**
31221
+ * Sorts the inheritance graph topologically, so that
31222
+ * nodes without incoming edges are returned first.
31223
+ *
31224
+ * I.e. The returned list is sorted, so that dependencies
31225
+ * of a given class are guaranteed to be included at
31226
+ * an earlier position than the inspected class.
31227
+ *
31228
+ * This sort is helpful for detecting inheritance problems
31229
+ * for the migration in simpler ways, without having to
31230
+ * check in both directions (base classes, and derived classes).
31231
+ */
31232
+ function topologicalSort(graph) {
31233
+ // All nodes without incoming edges.
31234
+ const S = graph.filter((n) => n.incoming.size === 0);
31235
+ const result = [];
31236
+ const invalidatedEdges = new WeakMap();
31237
+ const invalidateEdge = (from, to) => {
31238
+ if (!invalidatedEdges.has(from)) {
31239
+ invalidatedEdges.set(from, new Set());
31240
+ }
31241
+ invalidatedEdges.get(from).add(to);
31242
+ };
31243
+ const filterEdges = (from, edges) => {
31244
+ return Array.from(edges).filter((e) => !invalidatedEdges.has(from) || !invalidatedEdges.get(from).has(e));
31245
+ };
31246
+ while (S.length) {
31247
+ const node = S.pop();
31248
+ result.push(node);
31249
+ for (const next of filterEdges(node, node.outgoing)) {
31250
+ // Remove edge from "node -> next".
31251
+ invalidateEdge(node, next);
31252
+ // Remove edge from "next -> node".
31253
+ invalidateEdge(next, node);
31254
+ // if there are no incoming edges for `next`. add it to `S`.
31255
+ if (filterEdges(next, next.incoming).length === 0) {
31256
+ S.push(next);
31257
+ }
31258
+ }
31259
+ }
31260
+ return result;
31261
+ }
31262
+
31172
31263
  /** Merges a list of compilation units into a combined unit. */
31173
31264
  function mergeCompilationUnitData(metadataFiles) {
31174
31265
  const result = {
31175
31266
  knownInputs: {},
31176
- references: [],
31177
31267
  };
31178
- const seenReferenceFromIds = new Set();
31268
+ const idToGraphNode = new Map();
31269
+ const inheritanceGraph = [];
31270
+ const isNodeIncompatible = (node) => node.info.memberIncompatibility !== null || node.info.owningClassIncompatibility !== null;
31179
31271
  for (const file of metadataFiles) {
31180
31272
  for (const [key, info] of Object.entries(file.knownInputs)) {
31181
31273
  const existing = result.knownInputs[key];
31182
31274
  if (existing === undefined) {
31183
31275
  result.knownInputs[key] = info;
31276
+ const node = {
31277
+ incoming: new Set(),
31278
+ outgoing: new Set(),
31279
+ data: { info, key },
31280
+ };
31281
+ inheritanceGraph.push(node);
31282
+ idToGraphNode.set(key, node);
31283
+ continue;
31184
31284
  }
31185
- else if (existing.isIncompatible === null && info.isIncompatible) {
31186
- // input might not be incompatible in one target, but others might invalidate it.
31187
- // merge the incompatibility state.
31188
- existing.isIncompatible = info.isIncompatible;
31285
+ // Merge metadata.
31286
+ if (existing.extendsFrom === null && info.extendsFrom !== null) {
31287
+ existing.extendsFrom = info.extendsFrom;
31189
31288
  }
31190
- }
31191
- for (const reference of file.references) {
31192
- const referenceId = computeReferenceId(reference);
31193
- if (seenReferenceFromIds.has(referenceId)) {
31194
- continue;
31289
+ if (!existing.seenAsSourceInput && info.seenAsSourceInput) {
31290
+ existing.seenAsSourceInput = true;
31291
+ }
31292
+ // Merge member incompatibility.
31293
+ if (info.memberIncompatibility !== null) {
31294
+ if (existing.memberIncompatibility === null) {
31295
+ existing.memberIncompatibility = info.memberIncompatibility;
31296
+ }
31297
+ else {
31298
+ // Input might not be incompatible in one target, but others might invalidate it.
31299
+ // merge the incompatibility state.
31300
+ existing.memberIncompatibility = pickInputIncompatibility({ reason: info.memberIncompatibility, context: null }, { reason: existing.memberIncompatibility, context: null }).reason;
31301
+ }
31302
+ }
31303
+ // Merge incompatibility of the class owning the input.
31304
+ // Note: This metadata is stored per field for simplicity currently,
31305
+ // but in practice it could be a separate field in the compilation data.
31306
+ if (info.owningClassIncompatibility !== null &&
31307
+ existing.owningClassIncompatibility === null) {
31308
+ existing.owningClassIncompatibility = info.owningClassIncompatibility;
31195
31309
  }
31196
- seenReferenceFromIds.add(referenceId);
31197
- result.references.push(reference);
31198
31310
  }
31199
31311
  }
31200
- return result;
31201
- }
31202
- /** Computes a unique ID for the given reference. */
31203
- function computeReferenceId(reference) {
31204
- if (reference.kind === ReferenceKind.InTemplate) {
31205
- return `${reference.from.templateFile.id}@@${reference.from.read.positionEndInFile}`;
31312
+ for (const [key, info] of Object.entries(result.knownInputs)) {
31313
+ if (info.extendsFrom !== null) {
31314
+ const from = idToGraphNode.get(key);
31315
+ const target = idToGraphNode.get(info.extendsFrom);
31316
+ from.outgoing.add(target);
31317
+ target.incoming.add(from);
31318
+ }
31206
31319
  }
31207
- else if (reference.kind === ReferenceKind.InHostBinding) {
31208
- // `read` position is commonly relative to the host property node position— so we need
31209
- // to make it absolute by incorporating the host node position.
31210
- return (`${reference.from.file.id}@@${reference.from.hostPropertyNode.positionEndInFile}` +
31211
- `@@${reference.from.read.positionEndInFile}`);
31320
+ // Sort topologically and iterate super classes first, so that we can trivially
31321
+ // propagate incompatibility statuses (and other checks) without having to check
31322
+ // in both directions (derived classes, or base classes). This simplifies the
31323
+ // propagation.
31324
+ for (const node of topologicalSort(inheritanceGraph).reverse()) {
31325
+ const existingMemberIncompatibility = node.data.info.memberIncompatibility !== null
31326
+ ? { reason: node.data.info.memberIncompatibility, context: null }
31327
+ : null;
31328
+ for (const parent of node.outgoing) {
31329
+ // If parent is incompatible and not migrated, then this input
31330
+ // cannot be migrated either. Try propagating parent incompatibility then.
31331
+ if (isNodeIncompatible(parent.data)) {
31332
+ node.data.info.memberIncompatibility = pickInputIncompatibility({ reason: InputIncompatibilityReason.ParentIsIncompatible, context: null }, existingMemberIncompatibility).reason;
31333
+ break;
31334
+ }
31335
+ }
31212
31336
  }
31213
- else {
31214
- return `${reference.from.file.id}@@${reference.from.node.positionEndInFile}`;
31337
+ for (const info of Object.values(result.knownInputs)) {
31338
+ // We never saw a source file for this input, globally. Try marking it as incompatible,
31339
+ // so that all references and inheritance checks can propagate accordingly.
31340
+ if (!info.seenAsSourceInput) {
31341
+ const existingMemberIncompatibility = info.memberIncompatibility !== null
31342
+ ? { reason: info.memberIncompatibility, context: null }
31343
+ : null;
31344
+ info.memberIncompatibility = pickInputIncompatibility({ reason: InputIncompatibilityReason.OutsideOfMigrationScope, context: null }, existingMemberIncompatibility).reason;
31345
+ }
31215
31346
  }
31347
+ return result;
31216
31348
  }
31217
31349
 
31218
31350
  function populateKnownInputsFromGlobalData(knownInputs, globalData) {
@@ -31224,16 +31356,14 @@ function populateKnownInputsFromGlobalData(knownInputs, globalData) {
31224
31356
  continue;
31225
31357
  }
31226
31358
  const inputMetadata = knownInputs.get({ key });
31227
- if (!inputMetadata.isIncompatible() && info.isIncompatible) {
31228
- if (info.isIncompatible.kind === IncompatibilityType.VIA_CLASS) {
31229
- knownInputs.markClassIncompatible(inputMetadata.container.clazz, info.isIncompatible.reason);
31230
- }
31231
- else {
31232
- knownInputs.markFieldIncompatible(inputMetadata.descriptor, {
31233
- context: null, // No context serializable.
31234
- reason: info.isIncompatible.reason,
31235
- });
31236
- }
31359
+ if (info.memberIncompatibility) {
31360
+ knownInputs.markFieldIncompatible(inputMetadata.descriptor, {
31361
+ context: null, // No context serializable.
31362
+ reason: info.memberIncompatibility,
31363
+ });
31364
+ }
31365
+ if (info.owningClassIncompatibility) {
31366
+ knownInputs.markClassIncompatible(inputMetadata.container.clazz, info.owningClassIncompatibility);
31237
31367
  }
31238
31368
  }
31239
31369
  }
@@ -31250,15 +31380,54 @@ function removeFromUnionIfPossible(union, filter) {
31250
31380
  return ts__default["default"].factory.updateUnionTypeNode(union, ts__default["default"].factory.createNodeArray(filtered));
31251
31381
  }
31252
31382
 
31383
+ /**
31384
+ * Inserts a leading string for the given node, respecting
31385
+ * indentation of the given anchor node.
31386
+ *
31387
+ * Useful for inserting TODOs.
31388
+ */
31389
+ function insertPrecedingLine(node, info, text) {
31390
+ const leadingSpace = leading_space.getLeadingLineWhitespaceOfNode(node);
31391
+ return new Replacement(projectFile(node.getSourceFile(), info), new TextUpdate({
31392
+ position: node.getStart(),
31393
+ end: node.getStart(),
31394
+ toInsert: `${text}\n${leadingSpace}`,
31395
+ }));
31396
+ }
31397
+
31398
+ /**
31399
+ * Cuts the given string into lines basing around the specified
31400
+ * line length limit. This function breaks the string on a per-word basis.
31401
+ */
31402
+ function cutStringToLineLimit(str, limit) {
31403
+ const words = str.split(' ');
31404
+ const chunks = [];
31405
+ let chunkIdx = 0;
31406
+ while (words.length) {
31407
+ // New line if we exceed limit.
31408
+ if (chunks[chunkIdx] !== undefined && chunks[chunkIdx].length > limit) {
31409
+ chunkIdx++;
31410
+ }
31411
+ // Ensure line is initialized for the given index.
31412
+ if (chunks[chunkIdx] === undefined) {
31413
+ chunks[chunkIdx] = '';
31414
+ }
31415
+ const word = words.shift();
31416
+ const needsSpace = chunks[chunkIdx].length > 0;
31417
+ // Insert word. Add space before, if the line already contains text.
31418
+ chunks[chunkIdx] += `${needsSpace ? ' ' : ''}${word}`;
31419
+ }
31420
+ return chunks;
31421
+ }
31422
+
31253
31423
  // TODO: Consider initializations inside the constructor. Those are not migrated right now
31254
31424
  // though, as they are writes.
31255
31425
  /**
31256
- *
31257
31426
  * Converts an `@Input()` property declaration to a signal input.
31258
31427
  *
31259
- * @returns The transformed property declaration, printed as a string.
31428
+ * @returns Replacements for converting the input.
31260
31429
  */
31261
- function convertToSignalInput(node, { resolvedMetadata: metadata, resolvedType, preferShorthandIfPossible, originalInputDecorator, initialValue, }, checker, importManager, result) {
31430
+ function convertToSignalInput(node, { resolvedMetadata: metadata, resolvedType, preferShorthandIfPossible, originalInputDecorator, initialValue, leadingTodoText, }, info, checker, importManager, result) {
31262
31431
  let optionsLiteral = null;
31263
31432
  // We need an options array for the input because:
31264
31433
  // - the input is either aliased,
@@ -31269,7 +31438,13 @@ function convertToSignalInput(node, { resolvedMetadata: metadata, resolvedType,
31269
31438
  properties.push(ts__default["default"].factory.createPropertyAssignment('alias', ts__default["default"].factory.createStringLiteral(metadata.bindingPropertyName)));
31270
31439
  }
31271
31440
  if (metadata.transform !== null) {
31272
- properties.push(extractTransformOfInput(metadata.transform, resolvedType, checker));
31441
+ const transformRes = extractTransformOfInput(metadata.transform, resolvedType, checker);
31442
+ properties.push(transformRes.node);
31443
+ // Propagate TODO if one was requested from the transform extraction/validation.
31444
+ if (transformRes.leadingTodoText !== null) {
31445
+ leadingTodoText =
31446
+ (leadingTodoText ? `${leadingTodoText} ` : '') + transformRes.leadingTodoText;
31447
+ }
31273
31448
  }
31274
31449
  optionsLiteral = ts__default["default"].factory.createObjectLiteralExpression(properties);
31275
31450
  }
@@ -31323,7 +31498,17 @@ function convertToSignalInput(node, { resolvedMetadata: metadata, resolvedType,
31323
31498
  modifiersWithoutInputDecorator.push(ts__default["default"].factory.createModifier(ts__default["default"].SyntaxKind.ReadonlyKeyword));
31324
31499
  }
31325
31500
  const newNode = ts__default["default"].factory.createPropertyDeclaration(modifiersWithoutInputDecorator, node.name, undefined, undefined, inputInitializer);
31326
- return result.printer.printNode(ts__default["default"].EmitHint.Unspecified, newNode, node.getSourceFile());
31501
+ const newPropertyText = result.printer.printNode(ts__default["default"].EmitHint.Unspecified, newNode, node.getSourceFile());
31502
+ const replacements = [];
31503
+ if (leadingTodoText !== null) {
31504
+ replacements.push(insertPrecedingLine(node, info, '// TODO: Notes from signal input migration:'), ...cutStringToLineLimit(leadingTodoText, 70).map((line) => insertPrecedingLine(node, info, `// ${line}`)));
31505
+ }
31506
+ replacements.push(new Replacement(projectFile(node.getSourceFile(), info), new TextUpdate({
31507
+ position: node.getStart(),
31508
+ end: node.getEnd(),
31509
+ toInsert: newPropertyText,
31510
+ })));
31511
+ return replacements;
31327
31512
  }
31328
31513
  /**
31329
31514
  * Extracts the transform for the given input and returns a property assignment
@@ -31332,10 +31517,11 @@ function convertToSignalInput(node, { resolvedMetadata: metadata, resolvedType,
31332
31517
  function extractTransformOfInput(transform, resolvedType, checker) {
31333
31518
  assert__default["default"](ts__default["default"].isExpression(transform.node), `Expected transform to be an expression.`);
31334
31519
  let transformFn = transform.node;
31520
+ let leadingTodoText = null;
31335
31521
  // If there is an explicit type, check if the transform return type actually works.
31336
31522
  // In some cases, the transform function is not compatible because with decorator inputs,
31337
31523
  // those were not checked. We cast the transform to `any` and add a TODO.
31338
- // TODO: Insert a TODO and capture this in the design doc.
31524
+ // TODO: Capture this in the design doc.
31339
31525
  if (resolvedType !== undefined && !ts__default["default"].isSyntheticExpression(resolvedType)) {
31340
31526
  // Note: If the type is synthetic, we cannot check, and we accept that in the worst case
31341
31527
  // we will create code that is not necessarily compiling. This is unlikely, but notably
@@ -31344,33 +31530,67 @@ function extractTransformOfInput(transform, resolvedType, checker) {
31344
31530
  const transformSignature = transformType.getCallSignatures()[0];
31345
31531
  assert__default["default"](transformSignature !== undefined, 'Expected transform to be an invoke-able.');
31346
31532
  if (!checker.isTypeAssignableTo(checker.getReturnTypeOfSignature(transformSignature), checker.getTypeFromTypeNode(resolvedType))) {
31533
+ leadingTodoText =
31534
+ 'Input type is incompatible with transform. The migration added an `any` cast. ' +
31535
+ 'This worked previously because Angular was unable to check transforms.';
31347
31536
  transformFn = ts__default["default"].factory.createAsExpression(ts__default["default"].factory.createParenthesizedExpression(transformFn), ts__default["default"].factory.createKeywordTypeNode(ts__default["default"].SyntaxKind.AnyKeyword));
31348
31537
  }
31349
31538
  }
31350
- return ts__default["default"].factory.createPropertyAssignment('transform', transformFn);
31539
+ return {
31540
+ node: ts__default["default"].factory.createPropertyAssignment('transform', transformFn),
31541
+ leadingTodoText,
31542
+ };
31543
+ }
31544
+
31545
+ /**
31546
+ * Inserts a TODO for the incompatibility blocking the given node
31547
+ * from being migrated.
31548
+ */
31549
+ function insertTodoForIncompatibility(node, programInfo, input) {
31550
+ const incompatibility = input.container.getInputMemberIncompatibility(input.descriptor);
31551
+ if (incompatibility === null) {
31552
+ return [];
31553
+ }
31554
+ // If an input is skipped via config filter or outside migration scope, do not
31555
+ // insert TODOs, as this could results in lots of unnecessary comments.
31556
+ if (isInputMemberIncompatibility(incompatibility) &&
31557
+ (incompatibility.reason === InputIncompatibilityReason.SkippedViaConfigFilter ||
31558
+ incompatibility.reason === InputIncompatibilityReason.OutsideOfMigrationScope)) {
31559
+ return [];
31560
+ }
31561
+ const message = isInputMemberIncompatibility(incompatibility)
31562
+ ? getMessageForInputIncompatibility(incompatibility.reason).short
31563
+ : getMessageForClassIncompatibility(incompatibility).short;
31564
+ const lines = cutStringToLineLimit(message, 70);
31565
+ return [
31566
+ insertPrecedingLine(node, programInfo, `// TODO: Skipped for migration because:`),
31567
+ ...lines.map((line) => insertPrecedingLine(node, programInfo, `// ${line}`)),
31568
+ ];
31351
31569
  }
31352
31570
 
31353
31571
  /**
31354
31572
  * Phase that migrates `@Input()` declarations to signal inputs and
31355
31573
  * manages imports within the given file.
31356
31574
  */
31357
- function pass6__migrateInputDeclarations(checker, result, knownInputs, importManager, info) {
31575
+ function pass6__migrateInputDeclarations(host, checker, result, knownInputs, importManager, info) {
31358
31576
  let filesWithMigratedInputs = new Set();
31359
31577
  let filesWithIncompatibleInputs = new WeakSet();
31360
31578
  for (const [input, metadata] of result.sourceInputs) {
31361
31579
  const sf = input.node.getSourceFile();
31580
+ const inputInfo = knownInputs.get(input);
31362
31581
  // Do not migrate incompatible inputs.
31363
- if (knownInputs.get(input).isIncompatible() || metadata === null) {
31582
+ if (inputInfo.isIncompatible()) {
31583
+ // Add a TODO for the incompatible input, if desired.
31584
+ if (host.config.insertTodosForSkippedFields) {
31585
+ result.replacements.push(...insertTodoForIncompatibility(input.node, info, inputInfo));
31586
+ }
31364
31587
  filesWithIncompatibleInputs.add(sf);
31365
31588
  continue;
31366
31589
  }
31590
+ assert__default["default"](metadata !== null, `Expected metadata to exist for input isn't marked incompatible.`);
31367
31591
  assert__default["default"](!ts__default["default"].isAccessor(input.node), 'Accessor inputs are incompatible.');
31368
31592
  filesWithMigratedInputs.add(sf);
31369
- result.replacements.push(new Replacement(projectFile(sf, info), new TextUpdate({
31370
- position: input.node.getStart(),
31371
- end: input.node.getEnd(),
31372
- toInsert: convertToSignalInput(input.node, metadata, checker, importManager, result),
31373
- })));
31593
+ result.replacements.push(...convertToSignalInput(input.node, metadata, info, checker, importManager, result));
31374
31594
  }
31375
31595
  for (const file of filesWithMigratedInputs) {
31376
31596
  // All inputs were migrated, so we can safely remove the `Input` symbol.
@@ -32353,7 +32573,7 @@ function executeMigrationPhase(host, knownInputs, result, info) {
32353
32573
  };
32354
32574
  // Migrate passes.
32355
32575
  pass5__migrateTypeScriptReferences(referenceMigrationHost, result.references, typeChecker, info);
32356
- pass6__migrateInputDeclarations(typeChecker, result, knownInputs, importManager, info);
32576
+ pass6__migrateInputDeclarations(host, typeChecker, result, knownInputs, importManager, info);
32357
32577
  pass7__migrateTemplateReferences(referenceMigrationHost, result.references);
32358
32578
  pass8__migrateHostBindings(referenceMigrationHost, result.references, info);
32359
32579
  pass9__migrateTypeScriptTypeReferences(referenceMigrationHost, result.references, importManager, info);
@@ -32362,6 +32582,8 @@ function executeMigrationPhase(host, knownInputs, result, info) {
32362
32582
 
32363
32583
  /** Input reasons that cannot be ignored. */
32364
32584
  const nonIgnorableInputIncompatibilities = [
32585
+ // Outside of scope inputs should not be migrated. E.g. references to inputs in `node_modules/`.
32586
+ InputIncompatibilityReason.OutsideOfMigrationScope,
32365
32587
  // Explicitly filtered inputs cannot be skipped via best effort mode.
32366
32588
  InputIncompatibilityReason.SkippedViaConfigFilter,
32367
32589
  // There is no good output for accessor inputs.
@@ -32404,48 +32626,59 @@ class SignalInputMigration extends TsurgeComplexMigration {
32404
32626
  strictTemplates: true,
32405
32627
  });
32406
32628
  }
32407
- // Extend the program info with the analysis information we need in every phase.
32408
- prepareAnalysisDeps(info) {
32409
- assert__default["default"](info.ngCompiler !== null, 'Expected `NgCompiler` to be configured.');
32410
- const analysisInfo = {
32411
- ...info,
32412
- ...prepareAnalysisInfo(info.program, info.ngCompiler, info.programAbsoluteRootFileNames),
32413
- };
32629
+ prepareProgram(baseInfo) {
32630
+ const info = super.prepareProgram(baseInfo);
32414
32631
  // Optional filter for testing. Allows for simulation of parallel execution
32415
32632
  // even if some tsconfig's have overlap due to sharing of TS sources.
32416
32633
  // (this is commonly not the case in g3 where deps are `.d.ts` files).
32417
32634
  const limitToRootNamesOnly = process.env['LIMIT_TO_ROOT_NAMES_ONLY'] === '1';
32418
- analysisInfo.sourceFiles = analysisInfo.sourceFiles.filter((f) =>
32635
+ const filteredSourceFiles = info.sourceFiles.filter((f) =>
32419
32636
  // Optional replacement filter. Allows parallel execution in case
32420
32637
  // some tsconfig's have overlap due to sharing of TS sources.
32421
32638
  // (this is commonly not the case in g3 where deps are `.d.ts` files).
32422
32639
  !limitToRootNamesOnly || info.programAbsoluteRootFileNames.includes(f.fileName));
32640
+ return {
32641
+ ...info,
32642
+ sourceFiles: filteredSourceFiles,
32643
+ };
32644
+ }
32645
+ // Extend the program info with the analysis information we need in every phase.
32646
+ prepareAnalysisDeps(info) {
32647
+ assert__default["default"](info.ngCompiler !== null, 'Expected `NgCompiler` to be configured.');
32648
+ const analysisInfo = {
32649
+ ...info,
32650
+ ...prepareAnalysisInfo(info.program, info.ngCompiler, info.programAbsoluteRootFileNames),
32651
+ };
32423
32652
  return analysisInfo;
32424
32653
  }
32425
32654
  async analyze(info) {
32426
32655
  const analysisDeps = this.prepareAnalysisDeps(info);
32427
- const { metaRegistry } = analysisDeps;
32428
32656
  const knownInputs = new KnownInputs(info, this.config);
32429
32657
  const result = new MigrationResult();
32430
32658
  const host = createMigrationHost(info, this.config);
32431
32659
  this.config.reportProgressFn?.(10, 'Analyzing project (input usages)..');
32432
32660
  const { inheritanceGraph } = executeAnalysisPhase(host, knownInputs, result, analysisDeps);
32661
+ // Mark filtered inputs before checking inheritance. This ensures filtered
32662
+ // inputs properly influence e.g. inherited or derived inputs that now wouldn't
32663
+ // be safe either (BUT can still be skipped via best effort mode later).
32433
32664
  filterInputsViaConfig(result, knownInputs, this.config);
32665
+ // Analyze inheritance, track edges etc. and later propagate incompatibilities in
32666
+ // the merge stage.
32434
32667
  this.config.reportProgressFn?.(40, 'Checking inheritance..');
32435
- pass4__checkInheritanceOfInputs(inheritanceGraph, metaRegistry, knownInputs);
32668
+ pass4__checkInheritanceOfInputs(inheritanceGraph, analysisDeps.metaRegistry, knownInputs);
32669
+ // Filter best effort incompatibilities, so that the new filtered ones can
32670
+ // be accordingly respected in the merge phase.
32436
32671
  if (this.config.bestEffortMode) {
32437
32672
  filterIncompatibilitiesForBestEffortMode(knownInputs);
32438
32673
  }
32439
- const unitData = getCompilationUnitMetadata(knownInputs, result);
32674
+ const unitData = getCompilationUnitMetadata(knownInputs);
32440
32675
  // Non-batch mode!
32441
32676
  if (this.config.upgradeAnalysisPhaseToAvoidBatch) {
32442
32677
  const merged = await this.merge([unitData]);
32443
- this.config.reportProgressFn?.(60, 'Collecting migration changes..');
32444
32678
  const replacements = await this.migrate(merged, info, {
32445
32679
  knownInputs,
32446
32680
  result,
32447
32681
  host,
32448
- inheritanceGraph,
32449
32682
  analysisDeps,
32450
32683
  });
32451
32684
  this.config.reportProgressFn?.(100, 'Completed migration.');
@@ -32466,18 +32699,16 @@ class SignalInputMigration extends TsurgeComplexMigration {
32466
32699
  const result = nonBatchData?.result ?? new MigrationResult();
32467
32700
  const host = nonBatchData?.host ?? createMigrationHost(info, this.config);
32468
32701
  const analysisDeps = nonBatchData?.analysisDeps ?? this.prepareAnalysisDeps(info);
32469
- let inheritanceGraph;
32470
32702
  // Can't re-use analysis structures, so re-build them.
32471
32703
  if (nonBatchData === undefined) {
32472
- const analysisRes = executeAnalysisPhase(host, knownInputs, result, analysisDeps);
32473
- inheritanceGraph = analysisRes.inheritanceGraph;
32474
- populateKnownInputsFromGlobalData(knownInputs, globalMetadata);
32475
- filterInputsViaConfig(result, knownInputs, this.config);
32476
- pass4__checkInheritanceOfInputs(inheritanceGraph, analysisDeps.metaRegistry, knownInputs);
32477
- if (this.config.bestEffortMode) {
32478
- filterIncompatibilitiesForBestEffortMode(knownInputs);
32479
- }
32704
+ executeAnalysisPhase(host, knownInputs, result, analysisDeps);
32705
+ }
32706
+ // Incorporate global metadata into known inputs.
32707
+ populateKnownInputsFromGlobalData(knownInputs, globalMetadata);
32708
+ if (this.config.bestEffortMode) {
32709
+ filterIncompatibilitiesForBestEffortMode(knownInputs);
32480
32710
  }
32711
+ this.config.reportProgressFn?.(60, 'Collecting migration changes..');
32481
32712
  executeMigrationPhase(host, knownInputs, result, analysisDeps);
32482
32713
  return result.replacements;
32483
32714
  }
@@ -32560,7 +32791,7 @@ class DevkitMigrationFilesystem {
32560
32791
  return true;
32561
32792
  }
32562
32793
  exists(path) {
32563
- return this.tree.exists(path);
32794
+ return statPath(this.tree, path) !== null;
32564
32795
  }
32565
32796
  readFile(path) {
32566
32797
  return this.tree.readText(path);
@@ -32683,6 +32914,7 @@ function migrate(options) {
32683
32914
  checker.setFileSystem(fs);
32684
32915
  const migration = new SignalInputMigration({
32685
32916
  bestEffortMode: options.bestEffortMode,
32917
+ insertTodosForSkippedFields: options.insertTodos,
32686
32918
  shouldMigrateInput: (input) => {
32687
32919
  return (input.file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
32688
32920
  !/(^|\/)node_modules\//.test(input.file.rootRelativePath));
@@ -32691,6 +32923,7 @@ function migrate(options) {
32691
32923
  const analysisPath = fs.resolve(options.analysisDir);
32692
32924
  const unitResults = [];
32693
32925
  const programInfos = [...buildPaths, ...testPaths].map((tsconfigPath) => {
32926
+ context.logger.info(`Preparing analysis for: ${tsconfigPath}..`);
32694
32927
  const baseInfo = migration.createProgram(tsconfigPath, fs);
32695
32928
  const info = migration.prepareProgram(baseInfo);
32696
32929
  // Support restricting the analysis to subfolders for larger projects.
@@ -32703,7 +32936,7 @@ function migrate(options) {
32703
32936
  // Analyze phase. Treat all projects as compilation units as
32704
32937
  // this allows us to support references between those.
32705
32938
  for (const { info, tsconfigPath } of programInfos) {
32706
- context.logger.info(`Analyzing: ${tsconfigPath}..`);
32939
+ context.logger.info(`Scanning for inputs: ${tsconfigPath}..`);
32707
32940
  unitResults.push(await migration.analyze(info));
32708
32941
  }
32709
32942
  context.logger.info(``);