@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.
- package/fesm2022/core.mjs +378 -130
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +1 -1
- package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
- package/fesm2022/primitives/signals.mjs +1 -1
- package/fesm2022/primitives/signals.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +25 -4
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/testing.mjs +12 -11
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +166 -97
- package/package.json +1 -1
- package/primitives/event-dispatch/index.d.ts +1 -1
- package/primitives/signals/index.d.ts +1 -1
- package/rxjs-interop/index.d.ts +3 -1
- package/schematics/bundles/{checker-dcf9a14e.js → checker-637eee78.js} +22 -11
- package/schematics/bundles/{compiler_host-6026cdf8.js → compiler_host-1e62b899.js} +2 -2
- package/schematics/bundles/control-flow-migration.js +3 -3
- package/schematics/bundles/explicit-standalone-flag.js +3 -3
- package/schematics/bundles/imports-44987700.js +1 -1
- package/schematics/bundles/inject-migration.js +8 -28
- package/schematics/bundles/leading_space-6e7a8ec6.js +30 -0
- package/schematics/bundles/nodes-b12e919a.js +1 -1
- package/schematics/bundles/pending-tasks.js +103 -0
- package/schematics/bundles/{program-4dc8c0fa.js → program-893e3fe7.js} +1810 -1476
- package/schematics/bundles/project_tsconfig_paths-6c9cde78.js +1 -1
- package/schematics/bundles/route-lazy-loading.js +3 -3
- package/schematics/bundles/signal-input-migration.js +516 -283
- package/schematics/bundles/standalone-migration.js +9 -9
- package/schematics/migrations.json +5 -0
- package/schematics/ng-generate/signal-input-migration/schema.json +5 -0
- package/testing/index.d.ts +3 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.0.0-next.
|
|
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-
|
|
15
|
-
var program = require('./program-
|
|
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
|
-
/**
|
|
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["
|
|
268
|
-
InputIncompatibilityReason[InputIncompatibilityReason["
|
|
269
|
-
InputIncompatibilityReason[InputIncompatibilityReason["
|
|
270
|
-
InputIncompatibilityReason[InputIncompatibilityReason["
|
|
271
|
-
InputIncompatibilityReason[InputIncompatibilityReason["
|
|
272
|
-
InputIncompatibilityReason[InputIncompatibilityReason["
|
|
273
|
-
InputIncompatibilityReason[InputIncompatibilityReason["
|
|
274
|
-
InputIncompatibilityReason[InputIncompatibilityReason["
|
|
275
|
-
InputIncompatibilityReason[InputIncompatibilityReason["
|
|
276
|
-
InputIncompatibilityReason[InputIncompatibilityReason["
|
|
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.
|
|
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
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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({
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
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 (
|
|
30554
|
-
!
|
|
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 =
|
|
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 += (
|
|
30643
|
-
lastTime =
|
|
30800
|
+
perfCounters.template += (currentTimeInMs() - lastTime) / 1000;
|
|
30801
|
+
lastTime = currentTimeInMs();
|
|
30644
30802
|
identifyHostBindingReferences(node, programInfo, checker, reflector, result, knownFields);
|
|
30645
|
-
perfCounters.hostBindings += (
|
|
30646
|
-
lastTime =
|
|
30803
|
+
perfCounters.hostBindings += (currentTimeInMs() - lastTime) / 1000;
|
|
30804
|
+
lastTime = currentTimeInMs();
|
|
30647
30805
|
}
|
|
30648
|
-
lastTime =
|
|
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 += (
|
|
30657
|
-
lastTime =
|
|
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 += (
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
30978
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
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
|
-
|
|
31192
|
-
|
|
31193
|
-
|
|
31194
|
-
|
|
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
|
-
|
|
31201
|
-
|
|
31202
|
-
|
|
31203
|
-
|
|
31204
|
-
|
|
31205
|
-
|
|
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
|
-
|
|
31208
|
-
|
|
31209
|
-
|
|
31210
|
-
|
|
31211
|
-
|
|
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
|
-
|
|
31214
|
-
|
|
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 (
|
|
31228
|
-
|
|
31229
|
-
|
|
31230
|
-
|
|
31231
|
-
|
|
31232
|
-
|
|
31233
|
-
|
|
31234
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
32408
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
32473
|
-
|
|
32474
|
-
|
|
32475
|
-
|
|
32476
|
-
|
|
32477
|
-
|
|
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
|
|
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(`
|
|
32939
|
+
context.logger.info(`Scanning for inputs: ${tsconfigPath}..`);
|
|
32707
32940
|
unitResults.push(await migration.analyze(info));
|
|
32708
32941
|
}
|
|
32709
32942
|
context.logger.info(``);
|