@angular/core 19.0.0-next.9 → 19.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/core.mjs +21506 -19585
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +71 -47
- package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
- package/fesm2022/primitives/signals.mjs +8 -6
- package/fesm2022/primitives/signals.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +90 -10
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/testing.mjs +175 -114
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +556 -89
- package/package.json +1 -1
- package/primitives/event-dispatch/index.d.ts +7 -4
- package/primitives/signals/index.d.ts +3 -1
- package/rxjs-interop/index.d.ts +35 -4
- package/schematics/bundles/{checker-3b2ea20f.js → checker-2451e7c5.js} +2347 -1063
- package/schematics/bundles/{group_replacements-e1b5cbf8.js → combine_units-c52492ab.js} +1767 -2136
- package/schematics/bundles/{compiler_host-b4ba5a28.js → compiler_host-f54f8309.js} +2 -2
- package/schematics/bundles/control-flow-migration.js +3 -3
- package/schematics/bundles/explicit-standalone-flag.js +31 -11
- package/schematics/bundles/{imports-4ac08251.js → imports-44987700.js} +1 -1
- package/schematics/bundles/inject-migration.js +122 -48
- package/schematics/bundles/{leading_space-d190b83b.js → leading_space-6e7a8ec6.js} +1 -1
- package/schematics/bundles/migrate_ts_type_references-ab18a7c3.js +1463 -0
- package/schematics/bundles/{nodes-0e7d45ca.js → ng_decorators-3ad437d2.js} +2 -15
- package/schematics/bundles/nodes-ffdce442.js +27 -0
- package/schematics/bundles/output-migration.js +7450 -0
- package/schematics/bundles/pending-tasks.js +5 -5
- package/schematics/bundles/{program-6534a30a.js → program-58424797.js} +1305 -447
- package/schematics/bundles/{project_tsconfig_paths-e9ccccbf.js → project_tsconfig_paths-6c9cde78.js} +1 -1
- package/schematics/bundles/provide-initializer.js +190 -0
- package/schematics/bundles/route-lazy-loading.js +4 -4
- package/schematics/bundles/signal-input-migration.js +184 -312
- package/schematics/bundles/signal-queries-migration.js +401 -143
- package/schematics/bundles/signals.js +54 -0
- package/schematics/bundles/standalone-migration.js +38 -20
- package/schematics/collection.json +11 -0
- package/schematics/migrations.json +7 -1
- package/schematics/ng-generate/output-migration/schema.json +19 -0
- package/schematics/ng-generate/signal-queries-migration/schema.json +11 -0
- package/schematics/ng-generate/signals/schema.json +65 -0
- package/testing/index.d.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.0.0-
|
|
3
|
+
* @license Angular v19.0.0-rc.0
|
|
4
4
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -9,20 +9,21 @@
|
|
|
9
9
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
10
10
|
|
|
11
11
|
var schematics = require('@angular-devkit/schematics');
|
|
12
|
-
var
|
|
12
|
+
var migrate_ts_type_references = require('./migrate_ts_type_references-ab18a7c3.js');
|
|
13
13
|
var ts = require('typescript');
|
|
14
14
|
require('os');
|
|
15
|
-
var checker = require('./checker-
|
|
16
|
-
var program = require('./program-
|
|
15
|
+
var checker = require('./checker-2451e7c5.js');
|
|
16
|
+
var program = require('./program-58424797.js');
|
|
17
17
|
require('path');
|
|
18
|
+
var combine_units = require('./combine_units-c52492ab.js');
|
|
18
19
|
var assert = require('assert');
|
|
19
|
-
var
|
|
20
|
-
|
|
21
|
-
require('@angular-devkit/core');
|
|
22
|
-
require('node:path/posix');
|
|
20
|
+
var project_tsconfig_paths = require('./project_tsconfig_paths-6c9cde78.js');
|
|
21
|
+
require('./leading_space-6e7a8ec6.js');
|
|
23
22
|
require('fs');
|
|
24
23
|
require('module');
|
|
25
24
|
require('url');
|
|
25
|
+
require('@angular-devkit/core');
|
|
26
|
+
require('node:path/posix');
|
|
26
27
|
|
|
27
28
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
28
29
|
|
|
@@ -33,22 +34,23 @@ var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert);
|
|
|
33
34
|
* Class that holds information about a given directive and its input fields.
|
|
34
35
|
*/
|
|
35
36
|
class DirectiveInfo {
|
|
37
|
+
clazz;
|
|
38
|
+
/**
|
|
39
|
+
* Map of inputs detected in the given class.
|
|
40
|
+
* Maps string-based input ids to the detailed input metadata.
|
|
41
|
+
*/
|
|
42
|
+
inputFields = new Map();
|
|
43
|
+
/** Map of input IDs and their incompatibilities. */
|
|
44
|
+
memberIncompatibility = new Map();
|
|
45
|
+
/**
|
|
46
|
+
* Whether the whole class is incompatible.
|
|
47
|
+
*
|
|
48
|
+
* Class incompatibility precedes individual member incompatibility.
|
|
49
|
+
* All members in the class are considered incompatible.
|
|
50
|
+
*/
|
|
51
|
+
incompatible = null;
|
|
36
52
|
constructor(clazz) {
|
|
37
53
|
this.clazz = clazz;
|
|
38
|
-
/**
|
|
39
|
-
* Map of inputs detected in the given class.
|
|
40
|
-
* Maps string-based input ids to the detailed input metadata.
|
|
41
|
-
*/
|
|
42
|
-
this.inputFields = new Map();
|
|
43
|
-
/** Map of input IDs and their incompatibilities. */
|
|
44
|
-
this.memberIncompatibility = new Map();
|
|
45
|
-
/**
|
|
46
|
-
* Whether the whole class is incompatible.
|
|
47
|
-
*
|
|
48
|
-
* Class incompatibility precedes individual member incompatibility.
|
|
49
|
-
* All members in the class are considered incompatible.
|
|
50
|
-
*/
|
|
51
|
-
this.incompatible = null;
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
56
|
* Checks whether there are any migrated inputs for the
|
|
@@ -78,6 +80,11 @@ class DirectiveInfo {
|
|
|
78
80
|
* the whole migration.
|
|
79
81
|
*/
|
|
80
82
|
class MigrationHost {
|
|
83
|
+
isMigratingCore;
|
|
84
|
+
programInfo;
|
|
85
|
+
config;
|
|
86
|
+
_sourceFiles;
|
|
87
|
+
compilerOptions;
|
|
81
88
|
constructor(isMigratingCore, programInfo, config, sourceFiles) {
|
|
82
89
|
this.isMigratingCore = isMigratingCore;
|
|
83
90
|
this.programInfo = programInfo;
|
|
@@ -100,7 +107,7 @@ function getInputDescriptor(hostOrInfo, node) {
|
|
|
100
107
|
className = node.parent.name?.text ?? '<anonymous>';
|
|
101
108
|
}
|
|
102
109
|
const info = hostOrInfo instanceof MigrationHost ? hostOrInfo.programInfo : hostOrInfo;
|
|
103
|
-
const file =
|
|
110
|
+
const file = combine_units.projectFile(node.getSourceFile(), info);
|
|
104
111
|
// Inputs may be detected in `.d.ts` files. Ensure that if the file IDs
|
|
105
112
|
// match regardless of extension. E.g. `/google3/blaze-out/bin/my_file.ts` should
|
|
106
113
|
// have the same ID as `/google3/my_file.ts`.
|
|
@@ -110,11 +117,6 @@ function getInputDescriptor(hostOrInfo, node) {
|
|
|
110
117
|
node,
|
|
111
118
|
};
|
|
112
119
|
}
|
|
113
|
-
/** Whether the given value is an input descriptor. */
|
|
114
|
-
function isInputDescriptor(v) {
|
|
115
|
-
return (v.key !== undefined &&
|
|
116
|
-
v.node !== undefined);
|
|
117
|
-
}
|
|
118
120
|
|
|
119
121
|
/**
|
|
120
122
|
* Attempts to resolve the known `@Input` metadata for the given
|
|
@@ -124,11 +126,11 @@ function attemptRetrieveInputFromSymbol(programInfo, memberSymbol, knownInputs)
|
|
|
124
126
|
// Even for declared classes from `.d.ts`, the value declaration
|
|
125
127
|
// should exist and point to the property declaration.
|
|
126
128
|
if (memberSymbol.valueDeclaration !== undefined &&
|
|
127
|
-
|
|
129
|
+
combine_units.isInputContainerNode(memberSymbol.valueDeclaration)) {
|
|
128
130
|
const member = memberSymbol.valueDeclaration;
|
|
129
131
|
// If the member itself is an input that is being migrated, we
|
|
130
132
|
// do not need to check, as overriding would be fine then— like before.
|
|
131
|
-
const memberInputDescr =
|
|
133
|
+
const memberInputDescr = combine_units.isInputContainerNode(member)
|
|
132
134
|
? getInputDescriptor(programInfo, member)
|
|
133
135
|
: null;
|
|
134
136
|
return memberInputDescr !== null ? (knownInputs.get(memberInputDescr) ?? null) : null;
|
|
@@ -143,17 +145,19 @@ function attemptRetrieveInputFromSymbol(programInfo, memberSymbol, knownInputs)
|
|
|
143
145
|
* loaded into the program.
|
|
144
146
|
*/
|
|
145
147
|
class KnownInputs {
|
|
148
|
+
programInfo;
|
|
149
|
+
config;
|
|
150
|
+
/**
|
|
151
|
+
* Known inputs from the whole program.
|
|
152
|
+
*/
|
|
153
|
+
knownInputIds = new Map();
|
|
154
|
+
/** Known container classes of inputs. */
|
|
155
|
+
_allClasses = new Set();
|
|
156
|
+
/** Maps classes to their directive info. */
|
|
157
|
+
_classToDirectiveInfo = new Map();
|
|
146
158
|
constructor(programInfo, config) {
|
|
147
159
|
this.programInfo = programInfo;
|
|
148
160
|
this.config = config;
|
|
149
|
-
/**
|
|
150
|
-
* Known inputs from the whole program.
|
|
151
|
-
*/
|
|
152
|
-
this.knownInputIds = new Map();
|
|
153
|
-
/** Known container classes of inputs. */
|
|
154
|
-
this._allClasses = new Set();
|
|
155
|
-
/** Maps classes to their directive info. */
|
|
156
|
-
this._classToDirectiveInfo = new Map();
|
|
157
161
|
}
|
|
158
162
|
/** Whether the given input exists. */
|
|
159
163
|
has(descr) {
|
|
@@ -182,7 +186,7 @@ class KnownInputs {
|
|
|
182
186
|
}
|
|
183
187
|
const directiveInfo = this._classToDirectiveInfo.get(data.node.parent);
|
|
184
188
|
const inputInfo = {
|
|
185
|
-
file:
|
|
189
|
+
file: combine_units.projectFile(data.node.getSourceFile(), this.programInfo),
|
|
186
190
|
metadata: data.metadata,
|
|
187
191
|
descriptor: data.descriptor,
|
|
188
192
|
container: directiveInfo,
|
|
@@ -208,8 +212,8 @@ class KnownInputs {
|
|
|
208
212
|
const inputInfo = this.knownInputIds.get(input.key);
|
|
209
213
|
const existingIncompatibility = inputInfo.container.getInputMemberIncompatibility(input);
|
|
210
214
|
// Ensure an existing more significant incompatibility is not overridden.
|
|
211
|
-
if (existingIncompatibility !== null &&
|
|
212
|
-
incompatibility =
|
|
215
|
+
if (existingIncompatibility !== null && migrate_ts_type_references.isFieldIncompatibility(existingIncompatibility)) {
|
|
216
|
+
incompatibility = migrate_ts_type_references.pickFieldIncompatibility(existingIncompatibility, incompatibility);
|
|
213
217
|
}
|
|
214
218
|
this.knownInputIds
|
|
215
219
|
.get(input.key)
|
|
@@ -237,13 +241,13 @@ class KnownInputs {
|
|
|
237
241
|
captureUnknownDerivedField(field) {
|
|
238
242
|
this.markFieldIncompatible(field, {
|
|
239
243
|
context: null,
|
|
240
|
-
reason:
|
|
244
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.OverriddenByDerivedClass,
|
|
241
245
|
});
|
|
242
246
|
}
|
|
243
247
|
captureUnknownParentField(field) {
|
|
244
248
|
this.markFieldIncompatible(field, {
|
|
245
249
|
context: null,
|
|
246
|
-
reason:
|
|
250
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.TypeConflictWithBaseClass,
|
|
247
251
|
});
|
|
248
252
|
}
|
|
249
253
|
}
|
|
@@ -299,17 +303,15 @@ function prepareAnalysisInfo(userProgram, compiler, programAbsoluteRootPaths) {
|
|
|
299
303
|
* - imports that may need to be updated.
|
|
300
304
|
*/
|
|
301
305
|
class MigrationResult {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
this.inputDecoratorSpecifiers = new Map();
|
|
312
|
-
}
|
|
306
|
+
printer = ts__default["default"].createPrinter({ newLine: ts__default["default"].NewLineKind.LineFeed });
|
|
307
|
+
// May be `null` if the input cannot be converted. This is also
|
|
308
|
+
// signified by an incompatibility- but the input is tracked here as it
|
|
309
|
+
// still is a "source input".
|
|
310
|
+
sourceInputs = new Map();
|
|
311
|
+
references = [];
|
|
312
|
+
// Execution data
|
|
313
|
+
replacements = [];
|
|
314
|
+
inputDecoratorSpecifiers = new Map();
|
|
313
315
|
}
|
|
314
316
|
|
|
315
317
|
/** Attempts to extract metadata of a potential TypeScript `@Input()` declaration. */
|
|
@@ -322,7 +324,7 @@ function extractDecoratorInput(node, host, reflector, metadataReader, evaluator,
|
|
|
322
324
|
* part of a `.d.ts` file.
|
|
323
325
|
*/
|
|
324
326
|
function extractDtsInput(node, metadataReader) {
|
|
325
|
-
if (!
|
|
327
|
+
if (!combine_units.isInputContainerNode(node) ||
|
|
326
328
|
!ts__default["default"].isIdentifier(node.name) ||
|
|
327
329
|
!node.getSourceFile().isDeclarationFile) {
|
|
328
330
|
return null;
|
|
@@ -333,7 +335,18 @@ function extractDtsInput(node, metadataReader) {
|
|
|
333
335
|
!ts__default["default"].isIdentifier(node.parent.name)) {
|
|
334
336
|
return null;
|
|
335
337
|
}
|
|
336
|
-
|
|
338
|
+
let directiveMetadata = null;
|
|
339
|
+
// Getting directive metadata can throw errors when e.g. types referenced
|
|
340
|
+
// in the `.d.ts` aren't resolvable. This seems to be unexpected and shouldn't
|
|
341
|
+
// result in the entire migration to be failing.
|
|
342
|
+
try {
|
|
343
|
+
directiveMetadata = metadataReader.getDirectiveMetadata(new checker.Reference(node.parent));
|
|
344
|
+
}
|
|
345
|
+
catch (e) {
|
|
346
|
+
console.error('Unexpected error. Gracefully ignoring.');
|
|
347
|
+
console.error('Could not parse directive metadata:', e);
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
337
350
|
const inputMapping = directiveMetadata?.inputs.getByClassPropertyName(node.name.text);
|
|
338
351
|
// Signal inputs are never tracked and migrated.
|
|
339
352
|
if (inputMapping?.isSignal) {
|
|
@@ -345,6 +358,8 @@ function extractDtsInput(node, metadataReader) {
|
|
|
345
358
|
...inputMapping,
|
|
346
359
|
inputDecorator: null,
|
|
347
360
|
inSourceFile: false,
|
|
361
|
+
// Inputs from `.d.ts` cannot have any field decorators applied.
|
|
362
|
+
fieldDecorators: [],
|
|
348
363
|
};
|
|
349
364
|
}
|
|
350
365
|
/**
|
|
@@ -352,7 +367,7 @@ function extractDtsInput(node, metadataReader) {
|
|
|
352
367
|
* directly defined inside a source file (`.ts`).
|
|
353
368
|
*/
|
|
354
369
|
function extractSourceCodeInput(node, host, reflector, evaluator, refEmitter) {
|
|
355
|
-
if (!
|
|
370
|
+
if (!combine_units.isInputContainerNode(node) ||
|
|
356
371
|
!ts__default["default"].isIdentifier(node.name) ||
|
|
357
372
|
node.getSourceFile().isDeclarationFile) {
|
|
358
373
|
return null;
|
|
@@ -396,6 +411,7 @@ function extractSourceCodeInput(node, host, reflector, evaluator, refEmitter) {
|
|
|
396
411
|
inSourceFile: true,
|
|
397
412
|
transform: transformResult,
|
|
398
413
|
inputDecorator,
|
|
414
|
+
fieldDecorators: decorators,
|
|
399
415
|
};
|
|
400
416
|
}
|
|
401
417
|
/**
|
|
@@ -435,14 +451,19 @@ function prepareAndCheckForConversion(node, metadata, checker, options) {
|
|
|
435
451
|
if (ts__default["default"].isAccessor(node)) {
|
|
436
452
|
return {
|
|
437
453
|
context: node,
|
|
438
|
-
reason:
|
|
454
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.Accessor,
|
|
439
455
|
};
|
|
440
456
|
}
|
|
441
457
|
assert__default["default"](metadata.inputDecorator !== null, 'Expected an input decorator for inputs that are being migrated.');
|
|
442
458
|
let initialValue = node.initializer;
|
|
443
459
|
let isUndefinedInitialValue = node.initializer === undefined ||
|
|
444
460
|
(ts__default["default"].isIdentifier(node.initializer) && node.initializer.text === 'undefined');
|
|
445
|
-
const
|
|
461
|
+
const strictNullChecksEnabled = options.strict === true || options.strictNullChecks === true;
|
|
462
|
+
const strictPropertyInitialization = options.strict === true || options.strictPropertyInitialization === true;
|
|
463
|
+
// Shorthand should never be used, as would expand the type of `T` to be `T|undefined`.
|
|
464
|
+
// This wouldn't matter with strict null checks disabled, but it can break if this is
|
|
465
|
+
// a library that is later consumed with strict null checks enabled.
|
|
466
|
+
const avoidTypeExpansion = !strictNullChecksEnabled;
|
|
446
467
|
// If an input can be required, due to the non-null assertion on the property,
|
|
447
468
|
// make it required if there is no initializer.
|
|
448
469
|
if (node.exclamationToken !== undefined && initialValue === undefined) {
|
|
@@ -456,7 +477,7 @@ function prepareAndCheckForConversion(node, metadata, checker, options) {
|
|
|
456
477
|
if (!metadata.required &&
|
|
457
478
|
node.type !== undefined &&
|
|
458
479
|
isUndefinedInitialValue &&
|
|
459
|
-
!
|
|
480
|
+
!avoidTypeExpansion) {
|
|
460
481
|
preferShorthandIfPossible = { originalType: node.type };
|
|
461
482
|
}
|
|
462
483
|
// If the input is using `@Input() bla?: string;` with the "optional question mark",
|
|
@@ -474,7 +495,7 @@ function prepareAndCheckForConversion(node, metadata, checker, options) {
|
|
|
474
495
|
if (typeToAdd === undefined) {
|
|
475
496
|
return {
|
|
476
497
|
context: node,
|
|
477
|
-
reason:
|
|
498
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.SignalInput__QuestionMarkButNoGoodExplicitTypeExtractable,
|
|
478
499
|
};
|
|
479
500
|
}
|
|
480
501
|
if (!checker.isTypeAssignableTo(checker.getUndefinedType(), checker.getTypeFromTypeNode(typeToAdd))) {
|
|
@@ -489,8 +510,8 @@ function prepareAndCheckForConversion(node, metadata, checker, options) {
|
|
|
489
510
|
// is disabled, while strict null checks are enabled; then we know that `undefined`
|
|
490
511
|
// cannot be used as initial value, nor do we want to expand the input's type magically.
|
|
491
512
|
// Instead, we detect this case and migrate to `undefined!` which leaves the behavior unchanged.
|
|
492
|
-
|
|
493
|
-
|
|
513
|
+
if (strictNullChecksEnabled &&
|
|
514
|
+
!strictPropertyInitialization &&
|
|
494
515
|
node.initializer === undefined &&
|
|
495
516
|
node.type !== undefined &&
|
|
496
517
|
node.questionToken === undefined &&
|
|
@@ -515,7 +536,7 @@ function prepareAndCheckForConversion(node, metadata, checker, options) {
|
|
|
515
536
|
// the generated type might depend on imports that we cannot add here (nor want).
|
|
516
537
|
return {
|
|
517
538
|
context: node,
|
|
518
|
-
reason:
|
|
539
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.SignalInput__RequiredButNoGoodExplicitTypeExtractable,
|
|
519
540
|
};
|
|
520
541
|
}
|
|
521
542
|
}
|
|
@@ -567,7 +588,7 @@ function pass1__IdentifySourceFileAndDeclarationInputs(sf, host, checker, reflec
|
|
|
567
588
|
const visitor = (node) => {
|
|
568
589
|
const decoratorInput = extractDecoratorInput(node, host, reflector, dtsMetadataReader, evaluator, refEmitter);
|
|
569
590
|
if (decoratorInput !== null) {
|
|
570
|
-
assert__default["default"](
|
|
591
|
+
assert__default["default"](combine_units.isInputContainerNode(node), 'Expected input to be declared on accessor or property.');
|
|
571
592
|
const inputDescr = getInputDescriptor(host, node);
|
|
572
593
|
// track all inputs, even from declarations for reference resolution.
|
|
573
594
|
knownDecoratorInputs.register({ descriptor: inputDescr, metadata: decoratorInput, node });
|
|
@@ -575,7 +596,7 @@ function pass1__IdentifySourceFileAndDeclarationInputs(sf, host, checker, reflec
|
|
|
575
596
|
// these are then later migrated in the migration phase.
|
|
576
597
|
if (decoratorInput.inSourceFile && host.isSourceFileForCurrentMigration(sf)) {
|
|
577
598
|
const conversionPreparation = prepareAndCheckForConversion(node, decoratorInput, checker, host.compilerOptions);
|
|
578
|
-
if (
|
|
599
|
+
if (migrate_ts_type_references.isFieldIncompatibility(conversionPreparation)) {
|
|
579
600
|
knownDecoratorInputs.markFieldIncompatible(inputDescr, conversionPreparation);
|
|
580
601
|
result.sourceInputs.set(inputDescr, null);
|
|
581
602
|
}
|
|
@@ -614,8 +635,17 @@ function pass1__IdentifySourceFileAndDeclarationInputs(sf, host, checker, reflec
|
|
|
614
635
|
* In addition, spying onto an input may be problematic- so we skip migrating
|
|
615
636
|
* such.
|
|
616
637
|
*/
|
|
617
|
-
function pass3__checkIncompatiblePatterns(inheritanceGraph, checker, groupedTsAstVisitor, knownInputs) {
|
|
618
|
-
|
|
638
|
+
function pass3__checkIncompatiblePatterns(host, inheritanceGraph, checker$1, groupedTsAstVisitor, knownInputs) {
|
|
639
|
+
migrate_ts_type_references.checkIncompatiblePatterns(inheritanceGraph, checker$1, groupedTsAstVisitor, knownInputs, () => knownInputs.getAllInputContainingClasses());
|
|
640
|
+
for (const input of knownInputs.knownInputIds.values()) {
|
|
641
|
+
const hostBindingDecorators = checker.getAngularDecorators(input.metadata.fieldDecorators, ['HostBinding'], host.isMigratingCore);
|
|
642
|
+
if (hostBindingDecorators.length > 0) {
|
|
643
|
+
knownInputs.markFieldIncompatible(input.descriptor, {
|
|
644
|
+
context: hostBindingDecorators[0].node,
|
|
645
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.SignalIncompatibleWithHostBinding,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
619
649
|
}
|
|
620
650
|
|
|
621
651
|
/**
|
|
@@ -629,7 +659,7 @@ function pass3__checkIncompatiblePatterns(inheritanceGraph, checker, groupedTsAs
|
|
|
629
659
|
* such.
|
|
630
660
|
*/
|
|
631
661
|
function pass2_IdentifySourceFileReferences(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, groupedTsAstVisitor, knownInputs, result, fieldNamesToConsiderForReferenceLookup) {
|
|
632
|
-
groupedTsAstVisitor.register(
|
|
662
|
+
groupedTsAstVisitor.register(combine_units.createFindAllSourceFileReferencesVisitor(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, knownInputs, fieldNamesToConsiderForReferenceLookup, result).visitor);
|
|
633
663
|
}
|
|
634
664
|
|
|
635
665
|
/**
|
|
@@ -657,26 +687,26 @@ function executeAnalysisPhase(host, knownInputs, result, { sourceFiles, fullProg
|
|
|
657
687
|
}
|
|
658
688
|
// A graph starting with source files is sufficient. We will resolve into
|
|
659
689
|
// declaration files if a source file depends on such.
|
|
660
|
-
const inheritanceGraph = new
|
|
661
|
-
const pass2And3SourceFileVisitor = new
|
|
690
|
+
const inheritanceGraph = new migrate_ts_type_references.InheritanceGraph(typeChecker).expensivePopulate(sourceFiles);
|
|
691
|
+
const pass2And3SourceFileVisitor = new migrate_ts_type_references.GroupedTsAstVisitor(sourceFiles);
|
|
662
692
|
// Register pass 2. Find all source file references.
|
|
663
693
|
pass2_IdentifySourceFileReferences(host.programInfo, typeChecker, reflector, resourceLoader, evaluator, templateTypeChecker, pass2And3SourceFileVisitor, knownInputs, result, fieldNamesToConsiderForReferenceLookup);
|
|
664
694
|
// Register pass 3. Check incompatible patterns pass.
|
|
665
|
-
pass3__checkIncompatiblePatterns(inheritanceGraph, typeChecker, pass2And3SourceFileVisitor, knownInputs);
|
|
695
|
+
pass3__checkIncompatiblePatterns(host, inheritanceGraph, typeChecker, pass2And3SourceFileVisitor, knownInputs);
|
|
666
696
|
// Perform Pass 2 and Pass 3, efficiently in one pass.
|
|
667
697
|
pass2And3SourceFileVisitor.execute();
|
|
668
698
|
// Determine incompatible inputs based on resolved references.
|
|
669
699
|
for (const reference of result.references) {
|
|
670
|
-
if (
|
|
700
|
+
if (combine_units.isTsReference(reference) && reference.from.isWrite) {
|
|
671
701
|
knownInputs.markFieldIncompatible(reference.target, {
|
|
672
|
-
reason:
|
|
702
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.WriteAssignment,
|
|
673
703
|
context: reference.from.node,
|
|
674
704
|
});
|
|
675
705
|
}
|
|
676
|
-
if (
|
|
706
|
+
if (combine_units.isTemplateReference(reference) || combine_units.isHostBindingReference(reference)) {
|
|
677
707
|
if (reference.from.isWrite) {
|
|
678
708
|
knownInputs.markFieldIncompatible(reference.target, {
|
|
679
|
-
reason:
|
|
709
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.WriteAssignment,
|
|
680
710
|
// No TS node context available for template or host bindings.
|
|
681
711
|
context: null,
|
|
682
712
|
});
|
|
@@ -684,10 +714,10 @@ function executeAnalysisPhase(host, knownInputs, result, { sourceFiles, fullProg
|
|
|
684
714
|
}
|
|
685
715
|
// TODO: Remove this when we support signal narrowing in templates.
|
|
686
716
|
// https://github.com/angular/angular/pull/55456.
|
|
687
|
-
if (
|
|
717
|
+
if (combine_units.isTemplateReference(reference)) {
|
|
688
718
|
if (reference.from.isLikelyPartOfNarrowing) {
|
|
689
719
|
knownInputs.markFieldIncompatible(reference.target, {
|
|
690
|
-
reason:
|
|
720
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.PotentiallyNarrowedInTemplateButNoSupportYet,
|
|
691
721
|
context: null,
|
|
692
722
|
});
|
|
693
723
|
}
|
|
@@ -717,7 +747,7 @@ function executeAnalysisPhase(host, knownInputs, result, { sourceFiles, fullProg
|
|
|
717
747
|
* would then have other derived classes as well, it would propagate the status.
|
|
718
748
|
*/
|
|
719
749
|
function pass4__checkInheritanceOfInputs(inheritanceGraph, metaRegistry, knownInputs) {
|
|
720
|
-
|
|
750
|
+
migrate_ts_type_references.checkInheritanceOfKnownFields(inheritanceGraph, metaRegistry, knownInputs, {
|
|
721
751
|
isClassWithKnownFields: (clazz) => knownInputs.isInputContainingClass(clazz),
|
|
722
752
|
getFieldsForClass: (clazz) => {
|
|
723
753
|
const directiveInfo = knownInputs.getDirectiveInfoForClass(clazz);
|
|
@@ -793,25 +823,15 @@ function topologicalSort(graph) {
|
|
|
793
823
|
}
|
|
794
824
|
|
|
795
825
|
/** Merges a list of compilation units into a combined unit. */
|
|
796
|
-
function
|
|
826
|
+
function combineCompilationUnitData(unitA, unitB) {
|
|
797
827
|
const result = {
|
|
798
828
|
knownInputs: {},
|
|
799
829
|
};
|
|
800
|
-
const
|
|
801
|
-
const inheritanceGraph = [];
|
|
802
|
-
const isNodeIncompatible = (node) => node.info.memberIncompatibility !== null || node.info.owningClassIncompatibility !== null;
|
|
803
|
-
for (const file of metadataFiles) {
|
|
830
|
+
for (const file of [unitA, unitB]) {
|
|
804
831
|
for (const [key, info] of Object.entries(file.knownInputs)) {
|
|
805
832
|
const existing = result.knownInputs[key];
|
|
806
833
|
if (existing === undefined) {
|
|
807
834
|
result.knownInputs[key] = info;
|
|
808
|
-
const node = {
|
|
809
|
-
incoming: new Set(),
|
|
810
|
-
outgoing: new Set(),
|
|
811
|
-
data: { info, key },
|
|
812
|
-
};
|
|
813
|
-
inheritanceGraph.push(node);
|
|
814
|
-
idToGraphNode.set(key, node);
|
|
815
835
|
continue;
|
|
816
836
|
}
|
|
817
837
|
// Merge metadata.
|
|
@@ -829,7 +849,7 @@ function mergeCompilationUnitData(metadataFiles) {
|
|
|
829
849
|
else {
|
|
830
850
|
// Input might not be incompatible in one target, but others might invalidate it.
|
|
831
851
|
// merge the incompatibility state.
|
|
832
|
-
existing.memberIncompatibility =
|
|
852
|
+
existing.memberIncompatibility = migrate_ts_type_references.pickFieldIncompatibility({ reason: info.memberIncompatibility, context: null }, { reason: existing.memberIncompatibility, context: null }).reason;
|
|
833
853
|
}
|
|
834
854
|
}
|
|
835
855
|
// Merge incompatibility of the class owning the input.
|
|
@@ -841,7 +861,30 @@ function mergeCompilationUnitData(metadataFiles) {
|
|
|
841
861
|
}
|
|
842
862
|
}
|
|
843
863
|
}
|
|
844
|
-
|
|
864
|
+
return result;
|
|
865
|
+
}
|
|
866
|
+
function convertToGlobalMeta(combinedData) {
|
|
867
|
+
const globalMeta = {
|
|
868
|
+
knownInputs: {},
|
|
869
|
+
};
|
|
870
|
+
const idToGraphNode = new Map();
|
|
871
|
+
const inheritanceGraph = [];
|
|
872
|
+
const isNodeIncompatible = (node) => node.info.memberIncompatibility !== null || node.info.owningClassIncompatibility !== null;
|
|
873
|
+
for (const [key, info] of Object.entries(combinedData.knownInputs)) {
|
|
874
|
+
const existing = globalMeta.knownInputs[key];
|
|
875
|
+
if (existing !== undefined) {
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
const node = {
|
|
879
|
+
incoming: new Set(),
|
|
880
|
+
outgoing: new Set(),
|
|
881
|
+
data: { info, key },
|
|
882
|
+
};
|
|
883
|
+
inheritanceGraph.push(node);
|
|
884
|
+
idToGraphNode.set(key, node);
|
|
885
|
+
globalMeta.knownInputs[key] = info;
|
|
886
|
+
}
|
|
887
|
+
for (const [key, info] of Object.entries(globalMeta.knownInputs)) {
|
|
845
888
|
if (info.extendsFrom !== null) {
|
|
846
889
|
const from = idToGraphNode.get(key);
|
|
847
890
|
const target = idToGraphNode.get(info.extendsFrom);
|
|
@@ -861,22 +904,22 @@ function mergeCompilationUnitData(metadataFiles) {
|
|
|
861
904
|
// If parent is incompatible and not migrated, then this input
|
|
862
905
|
// cannot be migrated either. Try propagating parent incompatibility then.
|
|
863
906
|
if (isNodeIncompatible(parent.data)) {
|
|
864
|
-
node.data.info.memberIncompatibility =
|
|
907
|
+
node.data.info.memberIncompatibility = migrate_ts_type_references.pickFieldIncompatibility({ reason: migrate_ts_type_references.FieldIncompatibilityReason.ParentIsIncompatible, context: null }, existingMemberIncompatibility).reason;
|
|
865
908
|
break;
|
|
866
909
|
}
|
|
867
910
|
}
|
|
868
911
|
}
|
|
869
|
-
for (const info of Object.values(
|
|
912
|
+
for (const info of Object.values(combinedData.knownInputs)) {
|
|
870
913
|
// We never saw a source file for this input, globally. Try marking it as incompatible,
|
|
871
914
|
// so that all references and inheritance checks can propagate accordingly.
|
|
872
915
|
if (!info.seenAsSourceInput) {
|
|
873
916
|
const existingMemberIncompatibility = info.memberIncompatibility !== null
|
|
874
917
|
? { reason: info.memberIncompatibility, context: null }
|
|
875
918
|
: null;
|
|
876
|
-
info.memberIncompatibility =
|
|
919
|
+
info.memberIncompatibility = migrate_ts_type_references.pickFieldIncompatibility({ reason: migrate_ts_type_references.FieldIncompatibilityReason.OutsideOfMigrationScope, context: null }, existingMemberIncompatibility).reason;
|
|
877
920
|
}
|
|
878
921
|
}
|
|
879
|
-
return
|
|
922
|
+
return globalMeta;
|
|
880
923
|
}
|
|
881
924
|
|
|
882
925
|
function populateKnownInputsFromGlobalData(knownInputs, globalData) {
|
|
@@ -900,46 +943,6 @@ function populateKnownInputsFromGlobalData(knownInputs, globalData) {
|
|
|
900
943
|
}
|
|
901
944
|
}
|
|
902
945
|
|
|
903
|
-
/**
|
|
904
|
-
* Inserts a leading string for the given node, respecting
|
|
905
|
-
* indentation of the given anchor node.
|
|
906
|
-
*
|
|
907
|
-
* Useful for inserting TODOs.
|
|
908
|
-
*/
|
|
909
|
-
function insertPrecedingLine(node, info, text) {
|
|
910
|
-
const leadingSpace = leading_space.getLeadingLineWhitespaceOfNode(node);
|
|
911
|
-
return new group_replacements.Replacement(group_replacements.projectFile(node.getSourceFile(), info), new group_replacements.TextUpdate({
|
|
912
|
-
position: node.getStart(),
|
|
913
|
-
end: node.getStart(),
|
|
914
|
-
toInsert: `${text}\n${leadingSpace}`,
|
|
915
|
-
}));
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
* Cuts the given string into lines basing around the specified
|
|
920
|
-
* line length limit. This function breaks the string on a per-word basis.
|
|
921
|
-
*/
|
|
922
|
-
function cutStringToLineLimit(str, limit) {
|
|
923
|
-
const words = str.split(' ');
|
|
924
|
-
const chunks = [];
|
|
925
|
-
let chunkIdx = 0;
|
|
926
|
-
while (words.length) {
|
|
927
|
-
// New line if we exceed limit.
|
|
928
|
-
if (chunks[chunkIdx] !== undefined && chunks[chunkIdx].length > limit) {
|
|
929
|
-
chunkIdx++;
|
|
930
|
-
}
|
|
931
|
-
// Ensure line is initialized for the given index.
|
|
932
|
-
if (chunks[chunkIdx] === undefined) {
|
|
933
|
-
chunks[chunkIdx] = '';
|
|
934
|
-
}
|
|
935
|
-
const word = words.shift();
|
|
936
|
-
const needsSpace = chunks[chunkIdx].length > 0;
|
|
937
|
-
// Insert word. Add space before, if the line already contains text.
|
|
938
|
-
chunks[chunkIdx] += `${needsSpace ? ' ' : ''}${word}`;
|
|
939
|
-
}
|
|
940
|
-
return chunks;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
946
|
// TODO: Consider initializations inside the constructor. Those are not migrated right now
|
|
944
947
|
// though, as they are writes.
|
|
945
948
|
/**
|
|
@@ -981,7 +984,7 @@ function convertToSignalInput(node, { resolvedMetadata: metadata, resolvedType,
|
|
|
981
984
|
// When using the `input()` shorthand, try cutting of `undefined` from potential
|
|
982
985
|
// union types. `undefined` will be automatically included in the type.
|
|
983
986
|
if (ts__default["default"].isUnionTypeNode(resolvedType)) {
|
|
984
|
-
resolvedType =
|
|
987
|
+
resolvedType = migrate_ts_type_references.removeFromUnionIfPossible(resolvedType, (t) => t.kind !== ts__default["default"].SyntaxKind.UndefinedKeyword);
|
|
985
988
|
}
|
|
986
989
|
}
|
|
987
990
|
}
|
|
@@ -1021,9 +1024,9 @@ function convertToSignalInput(node, { resolvedMetadata: metadata, resolvedType,
|
|
|
1021
1024
|
const newPropertyText = result.printer.printNode(ts__default["default"].EmitHint.Unspecified, newNode, node.getSourceFile());
|
|
1022
1025
|
const replacements = [];
|
|
1023
1026
|
if (leadingTodoText !== null) {
|
|
1024
|
-
replacements.push(insertPrecedingLine(node, info, '// TODO: Notes from signal input migration:'), ...cutStringToLineLimit(leadingTodoText, 70).map((line) => insertPrecedingLine(node, info, `// ${line}`)));
|
|
1027
|
+
replacements.push(migrate_ts_type_references.insertPrecedingLine(node, info, '// TODO: Notes from signal input migration:'), ...migrate_ts_type_references.cutStringToLineLimit(leadingTodoText, 70).map((line) => migrate_ts_type_references.insertPrecedingLine(node, info, `// ${line}`)));
|
|
1025
1028
|
}
|
|
1026
|
-
replacements.push(new
|
|
1029
|
+
replacements.push(new combine_units.Replacement(combine_units.projectFile(node.getSourceFile(), info), new combine_units.TextUpdate({
|
|
1027
1030
|
position: node.getStart(),
|
|
1028
1031
|
end: node.getEnd(),
|
|
1029
1032
|
toInsert: newPropertyText,
|
|
@@ -1062,129 +1065,6 @@ function extractTransformOfInput(transform, resolvedType, checker) {
|
|
|
1062
1065
|
};
|
|
1063
1066
|
}
|
|
1064
1067
|
|
|
1065
|
-
/**
|
|
1066
|
-
* Gets human-readable message information for the given field incompatibility.
|
|
1067
|
-
* This text will be used by the language service, or CLI-based migration.
|
|
1068
|
-
*/
|
|
1069
|
-
function getMessageForFieldIncompatibility(reason, fieldName) {
|
|
1070
|
-
switch (reason) {
|
|
1071
|
-
case group_replacements.FieldIncompatibilityReason.Accessor:
|
|
1072
|
-
return {
|
|
1073
|
-
short: `Accessor ${fieldName.plural} cannot be migrated as they are too complex.`,
|
|
1074
|
-
extra: 'The migration potentially requires usage of `effect` or `computed`, but ' +
|
|
1075
|
-
'the intent is unclear. The migration cannot safely migrate.',
|
|
1076
|
-
};
|
|
1077
|
-
case group_replacements.FieldIncompatibilityReason.OverriddenByDerivedClass:
|
|
1078
|
-
return {
|
|
1079
|
-
short: `The ${fieldName.single} cannot be migrated because the field is overridden by a subclass.`,
|
|
1080
|
-
extra: 'The field in the subclass is not a signal, so migrating would break your build.',
|
|
1081
|
-
};
|
|
1082
|
-
case group_replacements.FieldIncompatibilityReason.ParentIsIncompatible:
|
|
1083
|
-
return {
|
|
1084
|
-
short: `This ${fieldName.single} is inherited from a superclass, but the parent cannot be migrated.`,
|
|
1085
|
-
extra: 'Migrating this field would cause your build to fail.',
|
|
1086
|
-
};
|
|
1087
|
-
case group_replacements.FieldIncompatibilityReason.PotentiallyNarrowedInTemplateButNoSupportYet:
|
|
1088
|
-
return {
|
|
1089
|
-
short: `This ${fieldName.single} is used in a control flow expression (e.g. \`@if\` or \`*ngIf\`) and ` +
|
|
1090
|
-
'migrating would break narrowing currently.',
|
|
1091
|
-
extra: `In the future, Angular intends to support narrowing of signals.`,
|
|
1092
|
-
};
|
|
1093
|
-
case group_replacements.FieldIncompatibilityReason.RedeclaredViaDerivedClassInputsArray:
|
|
1094
|
-
return {
|
|
1095
|
-
short: `The ${fieldName.single} is overridden by a subclass that cannot be migrated.`,
|
|
1096
|
-
extra: `The subclass overrides this ${fieldName.single} via the \`inputs\` array in @Directive/@Component. ` +
|
|
1097
|
-
'Migrating the field would break your build because the subclass field cannot be a signal.',
|
|
1098
|
-
};
|
|
1099
|
-
case group_replacements.FieldIncompatibilityReason.SignalInput__RequiredButNoGoodExplicitTypeExtractable:
|
|
1100
|
-
return {
|
|
1101
|
-
short: `Input is required, but the migration cannot determine a good type for the input.`,
|
|
1102
|
-
extra: 'Consider adding an explicit type to make the migration possible.',
|
|
1103
|
-
};
|
|
1104
|
-
case group_replacements.FieldIncompatibilityReason.SignalInput__QuestionMarkButNoGoodExplicitTypeExtractable:
|
|
1105
|
-
return {
|
|
1106
|
-
short: `Input is marked with a question mark. Migration could not determine a good type for the input.`,
|
|
1107
|
-
extra: 'The migration needs to be able to resolve a type, so that it can include `undefined` in your type. ' +
|
|
1108
|
-
'Consider adding an explicit type to make the migration possible.',
|
|
1109
|
-
};
|
|
1110
|
-
case group_replacements.FieldIncompatibilityReason.SkippedViaConfigFilter:
|
|
1111
|
-
return {
|
|
1112
|
-
short: `This ${fieldName.single} is not part of the current migration scope.`,
|
|
1113
|
-
extra: 'Skipped via migration config.',
|
|
1114
|
-
};
|
|
1115
|
-
case group_replacements.FieldIncompatibilityReason.SpyOnThatOverwritesField:
|
|
1116
|
-
return {
|
|
1117
|
-
short: 'A jasmine `spyOn` call spies on this field. This breaks with signals.',
|
|
1118
|
-
extra: `Migration cannot safely migrate as "spyOn" writes to the ${fieldName.single}. ` +
|
|
1119
|
-
`Signal ${fieldName.plural} are readonly.`,
|
|
1120
|
-
};
|
|
1121
|
-
case group_replacements.FieldIncompatibilityReason.TypeConflictWithBaseClass:
|
|
1122
|
-
return {
|
|
1123
|
-
short: `This ${fieldName.single} overrides a field from a superclass, while the superclass ` +
|
|
1124
|
-
`field is not migrated.`,
|
|
1125
|
-
extra: 'Migrating the field would break your build because of a type conflict.',
|
|
1126
|
-
};
|
|
1127
|
-
case group_replacements.FieldIncompatibilityReason.WriteAssignment:
|
|
1128
|
-
return {
|
|
1129
|
-
short: `Your application code writes to the ${fieldName.single}. This prevents migration.`,
|
|
1130
|
-
extra: `Signal ${fieldName.plural} are readonly, so migrating would break your build.`,
|
|
1131
|
-
};
|
|
1132
|
-
case group_replacements.FieldIncompatibilityReason.OutsideOfMigrationScope:
|
|
1133
|
-
return {
|
|
1134
|
-
short: `This ${fieldName.single} is not part of any source files in your project.`,
|
|
1135
|
-
extra: `The migration excludes ${fieldName.plural} if no source file declaring them was seen.`,
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
/**
|
|
1140
|
-
* Gets human-readable message information for the given class incompatibility.
|
|
1141
|
-
* This text will be used by the language service, or CLI-based migration.
|
|
1142
|
-
*/
|
|
1143
|
-
function getMessageForClassIncompatibility(reason, fieldName) {
|
|
1144
|
-
switch (reason) {
|
|
1145
|
-
case group_replacements.ClassIncompatibilityReason.OwningClassReferencedInClassProperty:
|
|
1146
|
-
return {
|
|
1147
|
-
short: `Class of this ${fieldName.single} is referenced in the signature of another class.`,
|
|
1148
|
-
extra: 'The other class is likely typed to expect a non-migrated field, so ' +
|
|
1149
|
-
'migration is skipped to not break your build.',
|
|
1150
|
-
};
|
|
1151
|
-
case group_replacements.ClassIncompatibilityReason.ClassManuallyInstantiated:
|
|
1152
|
-
return {
|
|
1153
|
-
short: `Class of this ${fieldName.single} is manually instantiated. ` +
|
|
1154
|
-
'This is discouraged and prevents migration',
|
|
1155
|
-
extra: `Signal ${fieldName.plural} require a DI injection context. Manually instantiating ` +
|
|
1156
|
-
'breaks this requirement in some cases, so the migration is skipped.',
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
/**
|
|
1162
|
-
* Inserts a TODO for the incompatibility blocking the given node
|
|
1163
|
-
* from being migrated.
|
|
1164
|
-
*/
|
|
1165
|
-
function insertTodoForIncompatibility(node, programInfo, input) {
|
|
1166
|
-
const incompatibility = input.container.getInputMemberIncompatibility(input.descriptor);
|
|
1167
|
-
if (incompatibility === null) {
|
|
1168
|
-
return [];
|
|
1169
|
-
}
|
|
1170
|
-
// If an input is skipped via config filter or outside migration scope, do not
|
|
1171
|
-
// insert TODOs, as this could results in lots of unnecessary comments.
|
|
1172
|
-
if (group_replacements.isFieldIncompatibility(incompatibility) &&
|
|
1173
|
-
(incompatibility.reason === group_replacements.FieldIncompatibilityReason.SkippedViaConfigFilter ||
|
|
1174
|
-
incompatibility.reason === group_replacements.FieldIncompatibilityReason.OutsideOfMigrationScope)) {
|
|
1175
|
-
return [];
|
|
1176
|
-
}
|
|
1177
|
-
const message = group_replacements.isFieldIncompatibility(incompatibility)
|
|
1178
|
-
? getMessageForFieldIncompatibility(incompatibility.reason, { single: 'input', plural: 'inputs' })
|
|
1179
|
-
.short
|
|
1180
|
-
: getMessageForClassIncompatibility(incompatibility, { single: 'input', plural: 'inputs' }).short;
|
|
1181
|
-
const lines = cutStringToLineLimit(message, 70);
|
|
1182
|
-
return [
|
|
1183
|
-
insertPrecedingLine(node, programInfo, `// TODO: Skipped for migration because:`),
|
|
1184
|
-
...lines.map((line) => insertPrecedingLine(node, programInfo, `// ${line}`)),
|
|
1185
|
-
];
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
1068
|
/**
|
|
1189
1069
|
* Phase that migrates `@Input()` declarations to signal inputs and
|
|
1190
1070
|
* manages imports within the given file.
|
|
@@ -1197,9 +1077,13 @@ function pass6__migrateInputDeclarations(host, checker, result, knownInputs, imp
|
|
|
1197
1077
|
const inputInfo = knownInputs.get(input);
|
|
1198
1078
|
// Do not migrate incompatible inputs.
|
|
1199
1079
|
if (inputInfo.isIncompatible()) {
|
|
1080
|
+
const incompatibilityReason = inputInfo.container.getInputMemberIncompatibility(input);
|
|
1200
1081
|
// Add a TODO for the incompatible input, if desired.
|
|
1201
|
-
if (host.config.insertTodosForSkippedFields) {
|
|
1202
|
-
result.replacements.push(...insertTodoForIncompatibility(input.node, info,
|
|
1082
|
+
if (incompatibilityReason !== null && host.config.insertTodosForSkippedFields) {
|
|
1083
|
+
result.replacements.push(...migrate_ts_type_references.insertTodoForIncompatibility(input.node, info, incompatibilityReason, {
|
|
1084
|
+
single: 'input',
|
|
1085
|
+
plural: 'inputs',
|
|
1086
|
+
}));
|
|
1203
1087
|
}
|
|
1204
1088
|
filesWithIncompatibleInputs.add(sf);
|
|
1205
1089
|
continue;
|
|
@@ -1222,7 +1106,7 @@ function pass6__migrateInputDeclarations(host, checker, result, knownInputs, imp
|
|
|
1222
1106
|
* previous migrate phases.
|
|
1223
1107
|
*/
|
|
1224
1108
|
function pass10_applyImportManager(importManager, result, sourceFiles, info) {
|
|
1225
|
-
|
|
1109
|
+
combine_units.applyImportManagerChanges(importManager, result.replacements, sourceFiles, info);
|
|
1226
1110
|
}
|
|
1227
1111
|
|
|
1228
1112
|
/**
|
|
@@ -1232,7 +1116,7 @@ function pass10_applyImportManager(importManager, result, sourceFiles, info) {
|
|
|
1232
1116
|
* where needed to ensure narrowing continues to work. E.g.
|
|
1233
1117
|
*/
|
|
1234
1118
|
function pass5__migrateTypeScriptReferences(host, references, checker, info) {
|
|
1235
|
-
|
|
1119
|
+
migrate_ts_type_references.migrateTypeScriptReferences(host, references, checker, info);
|
|
1236
1120
|
}
|
|
1237
1121
|
|
|
1238
1122
|
/**
|
|
@@ -1243,7 +1127,7 @@ function pass7__migrateTemplateReferences(host, references) {
|
|
|
1243
1127
|
const seenFileReferences = new Set();
|
|
1244
1128
|
for (const reference of references) {
|
|
1245
1129
|
// This pass only deals with HTML template references.
|
|
1246
|
-
if (!
|
|
1130
|
+
if (!combine_units.isTemplateReference(reference)) {
|
|
1247
1131
|
continue;
|
|
1248
1132
|
}
|
|
1249
1133
|
// Skip references to incompatible inputs.
|
|
@@ -1260,7 +1144,7 @@ function pass7__migrateTemplateReferences(host, references) {
|
|
|
1260
1144
|
const appendText = reference.from.isObjectShorthandExpression
|
|
1261
1145
|
? `: ${reference.from.read.name}()`
|
|
1262
1146
|
: `()`;
|
|
1263
|
-
host.replacements.push(new
|
|
1147
|
+
host.replacements.push(new combine_units.Replacement(reference.from.templateFile, new combine_units.TextUpdate({
|
|
1264
1148
|
position: reference.from.read.sourceSpan.end,
|
|
1265
1149
|
end: reference.from.read.sourceSpan.end,
|
|
1266
1150
|
toInsert: appendText,
|
|
@@ -1276,7 +1160,7 @@ function pass8__migrateHostBindings(host, references, info) {
|
|
|
1276
1160
|
const seenReferences = new WeakMap();
|
|
1277
1161
|
for (const reference of references) {
|
|
1278
1162
|
// This pass only deals with host binding references.
|
|
1279
|
-
if (!
|
|
1163
|
+
if (!combine_units.isHostBindingReference(reference)) {
|
|
1280
1164
|
continue;
|
|
1281
1165
|
}
|
|
1282
1166
|
// Skip references to incompatible inputs.
|
|
@@ -1300,7 +1184,7 @@ function pass8__migrateHostBindings(host, references, info) {
|
|
|
1300
1184
|
const appendText = reference.from.isObjectShorthandExpression
|
|
1301
1185
|
? `: ${reference.from.read.name}()`
|
|
1302
1186
|
: `()`;
|
|
1303
|
-
host.replacements.push(new
|
|
1187
|
+
host.replacements.push(new combine_units.Replacement(combine_units.projectFile(bindingField.getSourceFile(), info), new combine_units.TextUpdate({ position: readEndPos, end: readEndPos, toInsert: appendText })));
|
|
1304
1188
|
}
|
|
1305
1189
|
}
|
|
1306
1190
|
|
|
@@ -1311,7 +1195,7 @@ function pass8__migrateHostBindings(host, references, info) {
|
|
|
1311
1195
|
in Catalyst test files.
|
|
1312
1196
|
*/
|
|
1313
1197
|
function pass9__migrateTypeScriptTypeReferences(host, references, importManager, info) {
|
|
1314
|
-
|
|
1198
|
+
migrate_ts_type_references.migrateTypeScriptTypeReferences(host, references, importManager, info);
|
|
1315
1199
|
}
|
|
1316
1200
|
|
|
1317
1201
|
/**
|
|
@@ -1346,25 +1230,13 @@ function executeMigrationPhase(host, knownInputs, result, info) {
|
|
|
1346
1230
|
pass10_applyImportManager(importManager, result, sourceFiles, info);
|
|
1347
1231
|
}
|
|
1348
1232
|
|
|
1349
|
-
/** Input reasons that cannot be ignored. */
|
|
1350
|
-
const nonIgnorableInputIncompatibilities = [
|
|
1351
|
-
// Outside of scope inputs should not be migrated. E.g. references to inputs in `node_modules/`.
|
|
1352
|
-
group_replacements.FieldIncompatibilityReason.OutsideOfMigrationScope,
|
|
1353
|
-
// Explicitly filtered inputs cannot be skipped via best effort mode.
|
|
1354
|
-
group_replacements.FieldIncompatibilityReason.SkippedViaConfigFilter,
|
|
1355
|
-
// There is no good output for accessor inputs.
|
|
1356
|
-
group_replacements.FieldIncompatibilityReason.Accessor,
|
|
1357
|
-
// There is no good output for such inputs. We can't perform "conversion".
|
|
1358
|
-
group_replacements.FieldIncompatibilityReason.SignalInput__RequiredButNoGoodExplicitTypeExtractable,
|
|
1359
|
-
group_replacements.FieldIncompatibilityReason.SignalInput__QuestionMarkButNoGoodExplicitTypeExtractable,
|
|
1360
|
-
];
|
|
1361
1233
|
/** Filters ignorable input incompatibilities when best effort mode is enabled. */
|
|
1362
1234
|
function filterIncompatibilitiesForBestEffortMode(knownInputs) {
|
|
1363
1235
|
knownInputs.knownInputIds.forEach(({ container: c }) => {
|
|
1364
1236
|
// All class incompatibilities are "filterable" right now.
|
|
1365
1237
|
c.incompatible = null;
|
|
1366
1238
|
for (const [key, i] of c.memberIncompatibility.entries()) {
|
|
1367
|
-
if (!
|
|
1239
|
+
if (!migrate_ts_type_references.nonIgnorableFieldIncompatibilities.includes(i.reason)) {
|
|
1368
1240
|
c.memberIncompatibility.delete(key);
|
|
1369
1241
|
}
|
|
1370
1242
|
}
|
|
@@ -1375,15 +1247,16 @@ function filterIncompatibilitiesForBestEffortMode(knownInputs) {
|
|
|
1375
1247
|
* Tsurge migration for migrating Angular `@Input()` declarations to
|
|
1376
1248
|
* signal inputs, with support for batch execution.
|
|
1377
1249
|
*/
|
|
1378
|
-
class SignalInputMigration extends
|
|
1250
|
+
class SignalInputMigration extends combine_units.TsurgeComplexMigration {
|
|
1251
|
+
config;
|
|
1252
|
+
upgradedAnalysisPhaseResults = null;
|
|
1379
1253
|
constructor(config = {}) {
|
|
1380
1254
|
super();
|
|
1381
1255
|
this.config = config;
|
|
1382
|
-
this.upgradedAnalysisPhaseResults = null;
|
|
1383
1256
|
}
|
|
1384
1257
|
// Override the default ngtsc program creation, to add extra flags.
|
|
1385
1258
|
createProgram(tsconfigAbsPath, fs) {
|
|
1386
|
-
return
|
|
1259
|
+
return combine_units.createNgtscProgram(tsconfigAbsPath, fs, {
|
|
1387
1260
|
_compilePoisonedComponents: true,
|
|
1388
1261
|
// We want to migrate non-exported classes too.
|
|
1389
1262
|
compileNonExportedClasses: true,
|
|
@@ -1441,8 +1314,8 @@ class SignalInputMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
1441
1314
|
const unitData = getCompilationUnitMetadata(knownInputs);
|
|
1442
1315
|
// Non-batch mode!
|
|
1443
1316
|
if (this.config.upgradeAnalysisPhaseToAvoidBatch) {
|
|
1444
|
-
const
|
|
1445
|
-
const { replacements } = await this.migrate(
|
|
1317
|
+
const globalMeta = await this.globalMeta(unitData);
|
|
1318
|
+
const { replacements } = await this.migrate(globalMeta, info, {
|
|
1446
1319
|
knownInputs,
|
|
1447
1320
|
result,
|
|
1448
1321
|
host,
|
|
@@ -1456,10 +1329,13 @@ class SignalInputMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
1456
1329
|
knownInputs,
|
|
1457
1330
|
};
|
|
1458
1331
|
}
|
|
1459
|
-
return
|
|
1332
|
+
return combine_units.confirmAsSerializable(unitData);
|
|
1333
|
+
}
|
|
1334
|
+
async combine(unitA, unitB) {
|
|
1335
|
+
return combine_units.confirmAsSerializable(combineCompilationUnitData(unitA, unitB));
|
|
1460
1336
|
}
|
|
1461
|
-
async
|
|
1462
|
-
return
|
|
1337
|
+
async globalMeta(combinedData) {
|
|
1338
|
+
return combine_units.confirmAsSerializable(convertToGlobalMeta(combinedData));
|
|
1463
1339
|
}
|
|
1464
1340
|
async migrate(globalMetadata, info, nonBatchData) {
|
|
1465
1341
|
const knownInputs = nonBatchData?.knownInputs ?? new KnownInputs(info, this.config);
|
|
@@ -1488,8 +1364,8 @@ class SignalInputMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
1488
1364
|
for (const [id, input] of Object.entries(globalMetadata.knownInputs)) {
|
|
1489
1365
|
fullCompilationInputs++;
|
|
1490
1366
|
const isConsideredSourceInput = input.seenAsSourceInput &&
|
|
1491
|
-
input.memberIncompatibility !==
|
|
1492
|
-
input.memberIncompatibility !==
|
|
1367
|
+
input.memberIncompatibility !== migrate_ts_type_references.FieldIncompatibilityReason.OutsideOfMigrationScope &&
|
|
1368
|
+
input.memberIncompatibility !== migrate_ts_type_references.FieldIncompatibilityReason.SkippedViaConfigFilter;
|
|
1493
1369
|
// We won't track incompatibilities to inputs that aren't considered source inputs.
|
|
1494
1370
|
// Tracking their statistics wouldn't provide any value.
|
|
1495
1371
|
if (!isConsideredSourceInput) {
|
|
@@ -1500,13 +1376,13 @@ class SignalInputMigration extends group_replacements.TsurgeComplexMigration {
|
|
|
1500
1376
|
incompatibleInputs++;
|
|
1501
1377
|
}
|
|
1502
1378
|
if (input.memberIncompatibility !== null) {
|
|
1503
|
-
const reasonName =
|
|
1379
|
+
const reasonName = migrate_ts_type_references.FieldIncompatibilityReason[input.memberIncompatibility];
|
|
1504
1380
|
const key = `input-field-incompatibility-${reasonName}`;
|
|
1505
1381
|
fieldIncompatibleCounts[key] ??= 0;
|
|
1506
1382
|
fieldIncompatibleCounts[key]++;
|
|
1507
1383
|
}
|
|
1508
1384
|
if (input.owningClassIncompatibility !== null) {
|
|
1509
|
-
const reasonName =
|
|
1385
|
+
const reasonName = migrate_ts_type_references.ClassIncompatibilityReason[input.owningClassIncompatibility];
|
|
1510
1386
|
const key = `input-owning-class-incompatibility-${reasonName}`;
|
|
1511
1387
|
classIncompatibleCounts[key] ??= 0;
|
|
1512
1388
|
classIncompatibleCounts[key]++;
|
|
@@ -1538,19 +1414,10 @@ function filterInputsViaConfig(result, knownInputs, config) {
|
|
|
1538
1414
|
skippedInputs.add(input.descriptor.key);
|
|
1539
1415
|
knownInputs.markFieldIncompatible(input.descriptor, {
|
|
1540
1416
|
context: null,
|
|
1541
|
-
reason:
|
|
1417
|
+
reason: migrate_ts_type_references.FieldIncompatibilityReason.SkippedViaConfigFilter,
|
|
1542
1418
|
});
|
|
1543
1419
|
}
|
|
1544
1420
|
}
|
|
1545
|
-
result.references = result.references.filter((reference) => {
|
|
1546
|
-
if (isInputDescriptor(reference.target)) {
|
|
1547
|
-
// Only migrate the reference if the target is NOT skipped.
|
|
1548
|
-
return !skippedInputs.has(reference.target.key);
|
|
1549
|
-
}
|
|
1550
|
-
// Class references may be migrated. This is up to the logic handling
|
|
1551
|
-
// the class reference. E.g. it may not migrate if any member is incompatible.
|
|
1552
|
-
return true;
|
|
1553
|
-
});
|
|
1554
1421
|
}
|
|
1555
1422
|
function createMigrationHost(info, config) {
|
|
1556
1423
|
return new MigrationHost(/* isMigratingCore */ false, info, config, info.sourceFiles);
|
|
@@ -1562,7 +1429,7 @@ function migrate(options) {
|
|
|
1562
1429
|
if (!buildPaths.length && !testPaths.length) {
|
|
1563
1430
|
throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run signal input migration.');
|
|
1564
1431
|
}
|
|
1565
|
-
const fs = new
|
|
1432
|
+
const fs = new combine_units.DevkitMigrationFilesystem(tree);
|
|
1566
1433
|
checker.setFileSystem(fs);
|
|
1567
1434
|
const migration = new SignalInputMigration({
|
|
1568
1435
|
bestEffortMode: options.bestEffortMode,
|
|
@@ -1594,12 +1461,17 @@ function migrate(options) {
|
|
|
1594
1461
|
context.logger.info(``);
|
|
1595
1462
|
context.logger.info(`Processing analysis data between targets..`);
|
|
1596
1463
|
context.logger.info(``);
|
|
1597
|
-
const
|
|
1464
|
+
const combined = await combine_units.synchronouslyCombineUnitData(migration, unitResults);
|
|
1465
|
+
if (combined === null) {
|
|
1466
|
+
context.logger.error('Migration failed unexpectedly with no analysis data');
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
const globalMeta = await migration.globalMeta(combined);
|
|
1598
1470
|
const replacementsPerFile = new Map();
|
|
1599
1471
|
for (const { info, tsconfigPath } of programInfos) {
|
|
1600
1472
|
context.logger.info(`Migrating: ${tsconfigPath}..`);
|
|
1601
|
-
const { replacements } = await migration.migrate(
|
|
1602
|
-
const changesPerFile =
|
|
1473
|
+
const { replacements } = await migration.migrate(globalMeta, info);
|
|
1474
|
+
const changesPerFile = combine_units.groupReplacementsByFile(replacements);
|
|
1603
1475
|
for (const [file, changes] of changesPerFile) {
|
|
1604
1476
|
if (!replacementsPerFile.has(file)) {
|
|
1605
1477
|
replacementsPerFile.set(file, changes);
|
|
@@ -1616,7 +1488,7 @@ function migrate(options) {
|
|
|
1616
1488
|
}
|
|
1617
1489
|
tree.commitUpdate(recorder);
|
|
1618
1490
|
}
|
|
1619
|
-
const { counters } = await migration.stats(
|
|
1491
|
+
const { counters } = await migration.stats(globalMeta);
|
|
1620
1492
|
const migratedInputs = counters.sourceInputs - counters.incompatibleInputs;
|
|
1621
1493
|
context.logger.info('');
|
|
1622
1494
|
context.logger.info(`Successfully migrated to signal inputs 🎉`);
|