@angular/core 19.0.0-next.9 → 19.0.0-rc.1
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 +21183 -19410
- 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 +174 -113
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +524 -92
- 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-9ca42e51.js} +2303 -1006
- package/schematics/bundles/combine_units-a16385aa.js +1634 -0
- package/schematics/bundles/{compiler_host-b4ba5a28.js → compiler_host-31afa4ed.js} +8 -5
- package/schematics/bundles/control-flow-migration.js +3 -3
- package/schematics/bundles/explicit-standalone-flag.js +29 -9
- package/schematics/bundles/imports-4ac08251.js +1 -1
- package/schematics/bundles/inject-migration.js +222 -54
- package/schematics/bundles/leading_space-d190b83b.js +1 -1
- package/schematics/bundles/migrate_ts_type_references-b2a28742.js +1463 -0
- package/schematics/bundles/nodes-0e7d45ca.js +1 -1
- package/schematics/bundles/output-migration.js +575 -0
- package/schematics/bundles/pending-tasks.js +3 -3
- package/schematics/bundles/{program-6534a30a.js → program-71beec0b.js} +1385 -460
- package/schematics/bundles/project_tsconfig_paths-e9ccccbf.js +1 -1
- package/schematics/bundles/provide-initializer.js +179 -0
- package/schematics/bundles/route-lazy-loading.js +9 -4
- package/schematics/bundles/signal-input-migration.js +183 -311
- package/schematics/bundles/signal-queries-migration.js +404 -146
- package/schematics/bundles/signals.js +54 -0
- package/schematics/bundles/standalone-migration.js +29 -12
- 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
- package/schematics/bundles/group_replacements-e1b5cbf8.js +0 -31571
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.0.0-
|
|
3
|
+
* @license Angular v19.0.0-rc.1
|
|
4
4
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
var ts = require('typescript');
|
|
10
|
-
var checker = require('./checker-
|
|
10
|
+
var checker = require('./checker-9ca42e51.js');
|
|
11
11
|
require('os');
|
|
12
12
|
var p = require('path');
|
|
13
13
|
|
|
@@ -83,11 +83,14 @@ class ChangeTracker {
|
|
|
83
83
|
/**
|
|
84
84
|
* Removes the text of an AST node from a file.
|
|
85
85
|
* @param node Node whose text should be removed.
|
|
86
|
+
* @param useFullOffsets Whether to remove the node using its full offset (e.g. `getFullStart`
|
|
87
|
+
* rather than `fullStart`). This has the advantage of removing any comments that may be tied
|
|
88
|
+
* to the node, but can lead to too much code being deleted.
|
|
86
89
|
*/
|
|
87
|
-
removeNode(node) {
|
|
90
|
+
removeNode(node, useFullOffsets = false) {
|
|
88
91
|
this._trackChange(node.getSourceFile(), {
|
|
89
|
-
start: node.getStart(),
|
|
90
|
-
removeLength: node.getWidth(),
|
|
92
|
+
start: useFullOffsets ? node.getFullStart() : node.getStart(),
|
|
93
|
+
removeLength: useFullOffsets ? node.getFullWidth() : node.getWidth(),
|
|
91
94
|
text: '',
|
|
92
95
|
});
|
|
93
96
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.0.0-
|
|
3
|
+
* @license Angular v19.0.0-rc.1
|
|
4
4
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -10,8 +10,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
10
10
|
|
|
11
11
|
var schematics = require('@angular-devkit/schematics');
|
|
12
12
|
var p = require('path');
|
|
13
|
-
var compiler_host = require('./compiler_host-
|
|
14
|
-
var checker = require('./checker-
|
|
13
|
+
var compiler_host = require('./compiler_host-31afa4ed.js');
|
|
14
|
+
var checker = require('./checker-9ca42e51.js');
|
|
15
15
|
var ts = require('typescript');
|
|
16
16
|
require('os');
|
|
17
17
|
require('fs');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.0.0-
|
|
3
|
+
* @license Angular v19.0.0-rc.1
|
|
4
4
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -11,11 +11,11 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
11
11
|
var schematics = require('@angular-devkit/schematics');
|
|
12
12
|
var p = require('path');
|
|
13
13
|
var project_tsconfig_paths = require('./project_tsconfig_paths-e9ccccbf.js');
|
|
14
|
-
var compiler_host = require('./compiler_host-
|
|
14
|
+
var compiler_host = require('./compiler_host-31afa4ed.js');
|
|
15
15
|
var ts = require('typescript');
|
|
16
16
|
var imports = require('./imports-4ac08251.js');
|
|
17
17
|
require('@angular-devkit/core');
|
|
18
|
-
require('./checker-
|
|
18
|
+
require('./checker-9ca42e51.js');
|
|
19
19
|
require('os');
|
|
20
20
|
require('fs');
|
|
21
21
|
require('module');
|
|
@@ -77,16 +77,23 @@ function migrateFile(sourceFile, rewriteFn) {
|
|
|
77
77
|
}
|
|
78
78
|
const properties = decoratorArgument.properties;
|
|
79
79
|
const standaloneProp = getStandaloneProperty(properties);
|
|
80
|
-
|
|
81
|
-
//
|
|
80
|
+
const hasImports = decoratorHasImports(decoratorArgument);
|
|
81
|
+
// We'll use the presence of imports to keep the migration idempotent
|
|
82
|
+
// We need to take care of 3 cases
|
|
83
|
+
// - standalone: true => remove the property if we have imports
|
|
82
84
|
// - standalone: false => nothing
|
|
83
|
-
// - No standalone property => add a standalone: false property
|
|
85
|
+
// - No standalone property => add a standalone: false property if there are no imports
|
|
84
86
|
let newProperties;
|
|
85
87
|
if (!standaloneProp) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
if (!hasImports) {
|
|
89
|
+
const standaloneFalseProperty = ts__default["default"].factory.createPropertyAssignment('standalone', ts__default["default"].factory.createFalse());
|
|
90
|
+
newProperties = [...properties, standaloneFalseProperty];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (standaloneProp.value === ts__default["default"].SyntaxKind.TrueKeyword && hasImports) {
|
|
94
|
+
// To keep the migration idempotent, we'll only remove the standalone prop when there are imports
|
|
95
|
+
newProperties = properties.filter((p) => p !== standaloneProp.property);
|
|
88
96
|
}
|
|
89
|
-
else if (standaloneProp.value === ts__default["default"].SyntaxKind.TrueKeyword) ;
|
|
90
97
|
if (newProperties) {
|
|
91
98
|
// At this point we know that we need to add standalone: false or
|
|
92
99
|
// remove an existing standalone: true property.
|
|
@@ -123,6 +130,19 @@ function getStandaloneProperty(properties) {
|
|
|
123
130
|
function isStandaloneProperty(prop) {
|
|
124
131
|
return (ts__default["default"].isPropertyAssignment(prop) && ts__default["default"].isIdentifier(prop.name) && prop.name.text === 'standalone');
|
|
125
132
|
}
|
|
133
|
+
function decoratorHasImports(decoratorArgument) {
|
|
134
|
+
for (const prop of decoratorArgument.properties) {
|
|
135
|
+
if (ts__default["default"].isPropertyAssignment(prop) &&
|
|
136
|
+
ts__default["default"].isIdentifier(prop.name) &&
|
|
137
|
+
prop.name.text === 'imports') {
|
|
138
|
+
if (prop.initializer.kind === ts__default["default"].SyntaxKind.ArrayLiteralExpression ||
|
|
139
|
+
prop.initializer.kind === ts__default["default"].SyntaxKind.Identifier) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
126
146
|
|
|
127
147
|
function migrate() {
|
|
128
148
|
return async (tree) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* @license Angular v19.0.0-
|
|
3
|
+
* @license Angular v19.0.0-rc.1
|
|
4
4
|
* (c) 2010-2024 Google LLC. https://angular.io/
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
@@ -10,12 +10,12 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
10
10
|
|
|
11
11
|
var schematics = require('@angular-devkit/schematics');
|
|
12
12
|
var p = require('path');
|
|
13
|
-
var compiler_host = require('./compiler_host-
|
|
13
|
+
var compiler_host = require('./compiler_host-31afa4ed.js');
|
|
14
14
|
var ts = require('typescript');
|
|
15
15
|
var nodes = require('./nodes-0e7d45ca.js');
|
|
16
16
|
var imports = require('./imports-4ac08251.js');
|
|
17
17
|
var leading_space = require('./leading_space-d190b83b.js');
|
|
18
|
-
require('./checker-
|
|
18
|
+
require('./checker-9ca42e51.js');
|
|
19
19
|
require('os');
|
|
20
20
|
require('fs');
|
|
21
21
|
require('module');
|
|
@@ -55,7 +55,7 @@ const DI_PARAM_SYMBOLS = new Set([
|
|
|
55
55
|
* @param sourceFile File which to analyze.
|
|
56
56
|
* @param localTypeChecker Type checker scoped to the specific file.
|
|
57
57
|
*/
|
|
58
|
-
function analyzeFile(sourceFile, localTypeChecker) {
|
|
58
|
+
function analyzeFile(sourceFile, localTypeChecker, options) {
|
|
59
59
|
const coreSpecifiers = imports.getNamedImports(sourceFile, '@angular/core');
|
|
60
60
|
// Exit early if there are no Angular imports.
|
|
61
61
|
if (coreSpecifiers === null || coreSpecifiers.elements.length === 0) {
|
|
@@ -106,11 +106,14 @@ function analyzeFile(sourceFile, localTypeChecker) {
|
|
|
106
106
|
}
|
|
107
107
|
else if (ts__default["default"].isClassDeclaration(node)) {
|
|
108
108
|
const decorators = nodes.getAngularDecorators(localTypeChecker, ts__default["default"].getDecorators(node) || []);
|
|
109
|
+
const isAbstract = !!node.modifiers?.some((m) => m.kind === ts__default["default"].SyntaxKind.AbstractKeyword);
|
|
109
110
|
const supportsDI = decorators.some((dec) => DECORATORS_SUPPORTING_DI.has(dec.name));
|
|
110
111
|
const constructorNode = node.members.find((member) => ts__default["default"].isConstructorDeclaration(member) &&
|
|
111
112
|
member.body != null &&
|
|
112
113
|
member.parameters.length > 0);
|
|
113
|
-
|
|
114
|
+
// Don't migrate abstract classes by default, because
|
|
115
|
+
// their parameters aren't guaranteed to be injectable.
|
|
116
|
+
if (supportsDI && constructorNode && (!isAbstract || options.migrateAbstractClasses)) {
|
|
114
117
|
classes.push({
|
|
115
118
|
node,
|
|
116
119
|
constructor: constructorNode,
|
|
@@ -145,7 +148,7 @@ function getConstructorUnusedParameters(declaration, localTypeChecker, removedSt
|
|
|
145
148
|
}
|
|
146
149
|
declaration.body.forEachChild(function walk(node) {
|
|
147
150
|
// Don't descend into statements that were removed already.
|
|
148
|
-
if (
|
|
151
|
+
if (ts__default["default"].isStatement(node) && removedStatements.has(node)) {
|
|
149
152
|
return;
|
|
150
153
|
}
|
|
151
154
|
if (!ts__default["default"].isIdentifier(node) || !topLevelParameterNames.has(node.text)) {
|
|
@@ -286,7 +289,8 @@ function findSuperCall(root) {
|
|
|
286
289
|
* @param localTypeChecker Type checker scoped to the current file.
|
|
287
290
|
*/
|
|
288
291
|
function findUninitializedPropertiesToCombine(node, constructor, localTypeChecker) {
|
|
289
|
-
let
|
|
292
|
+
let toCombine = null;
|
|
293
|
+
let toHoist = [];
|
|
290
294
|
const membersToDeclarations = new Map();
|
|
291
295
|
for (const member of node.members) {
|
|
292
296
|
if (ts__default["default"].isPropertyDeclaration(member) &&
|
|
@@ -296,20 +300,110 @@ function findUninitializedPropertiesToCombine(node, constructor, localTypeChecke
|
|
|
296
300
|
}
|
|
297
301
|
}
|
|
298
302
|
if (membersToDeclarations.size === 0) {
|
|
299
|
-
return
|
|
303
|
+
return null;
|
|
300
304
|
}
|
|
301
305
|
const memberInitializers = getMemberInitializers(constructor);
|
|
302
306
|
if (memberInitializers === null) {
|
|
303
|
-
return
|
|
307
|
+
return null;
|
|
304
308
|
}
|
|
305
|
-
for (const [name,
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
309
|
+
for (const [name, decl] of membersToDeclarations.entries()) {
|
|
310
|
+
if (memberInitializers.has(name)) {
|
|
311
|
+
const initializer = memberInitializers.get(name);
|
|
312
|
+
if (!hasLocalReferences(initializer, constructor, localTypeChecker)) {
|
|
313
|
+
toCombine ??= [];
|
|
314
|
+
toCombine.push({ declaration: membersToDeclarations.get(name), initializer });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
// Mark members that have no initializers and can't be combined to be hoisted above the
|
|
319
|
+
// injected members. This is either a no-op or it allows us to avoid some patterns internally
|
|
320
|
+
// like the following:
|
|
321
|
+
// ```
|
|
322
|
+
// class Foo {
|
|
323
|
+
// publicFoo: Foo;
|
|
324
|
+
// private privateFoo: Foo;
|
|
325
|
+
//
|
|
326
|
+
// constructor() {
|
|
327
|
+
// this.initializePrivateFooSomehow();
|
|
328
|
+
// this.publicFoo = this.privateFoo;
|
|
329
|
+
// }
|
|
330
|
+
// }
|
|
331
|
+
// ```
|
|
332
|
+
toHoist.push(decl);
|
|
310
333
|
}
|
|
311
334
|
}
|
|
312
|
-
|
|
335
|
+
// If no members need to be combined, none need to be hoisted either.
|
|
336
|
+
return toCombine === null ? null : { toCombine, toHoist };
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* In some cases properties may be declared out of order, but initialized in the correct order.
|
|
340
|
+
* The internal-specific migration will combine such properties which will result in a compilation
|
|
341
|
+
* error, for example:
|
|
342
|
+
*
|
|
343
|
+
* ```
|
|
344
|
+
* class MyClass {
|
|
345
|
+
* foo: Foo;
|
|
346
|
+
* bar: Bar;
|
|
347
|
+
*
|
|
348
|
+
* constructor(bar: Bar) {
|
|
349
|
+
* this.bar = bar;
|
|
350
|
+
* this.foo = this.bar.getFoo();
|
|
351
|
+
* }
|
|
352
|
+
* }
|
|
353
|
+
* ```
|
|
354
|
+
*
|
|
355
|
+
* Will become:
|
|
356
|
+
*
|
|
357
|
+
* ```
|
|
358
|
+
* class MyClass {
|
|
359
|
+
* foo: Foo = this.bar.getFoo();
|
|
360
|
+
* bar: Bar = inject(Bar);
|
|
361
|
+
* }
|
|
362
|
+
* ```
|
|
363
|
+
*
|
|
364
|
+
* This function determines if cases like this can be saved by reordering the properties so their
|
|
365
|
+
* declaration order matches the order in which they're initialized.
|
|
366
|
+
*
|
|
367
|
+
* @param toCombine Properties that are candidates to be combined.
|
|
368
|
+
* @param constructor
|
|
369
|
+
*/
|
|
370
|
+
function shouldCombineInInitializationOrder(toCombine, constructor) {
|
|
371
|
+
let combinedMemberReferenceCount = 0;
|
|
372
|
+
let otherMemberReferenceCount = 0;
|
|
373
|
+
const injectedMemberNames = new Set();
|
|
374
|
+
const combinedMemberNames = new Set();
|
|
375
|
+
// Collect the name of constructor parameters that declare new properties.
|
|
376
|
+
// These can be ignored since they'll be hoisted above other properties.
|
|
377
|
+
constructor.parameters.forEach((param) => {
|
|
378
|
+
if (parameterDeclaresProperty(param) && ts__default["default"].isIdentifier(param.name)) {
|
|
379
|
+
injectedMemberNames.add(param.name.text);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
// Collect the names of the properties being combined. We should only reorder
|
|
383
|
+
// the properties if at least one of them refers to another one.
|
|
384
|
+
toCombine.forEach(({ declaration: { name } }) => {
|
|
385
|
+
if (ts__default["default"].isStringLiteralLike(name) || ts__default["default"].isIdentifier(name)) {
|
|
386
|
+
combinedMemberNames.add(name.text);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
// Visit all the initializers and check all the property reads in the form of `this.<name>`.
|
|
390
|
+
// Skip over the ones referring to injected parameters since they're going to be hoisted.
|
|
391
|
+
const walkInitializer = (node) => {
|
|
392
|
+
if (ts__default["default"].isPropertyAccessExpression(node) && node.expression.kind === ts__default["default"].SyntaxKind.ThisKeyword) {
|
|
393
|
+
if (combinedMemberNames.has(node.name.text)) {
|
|
394
|
+
combinedMemberReferenceCount++;
|
|
395
|
+
}
|
|
396
|
+
else if (!injectedMemberNames.has(node.name.text)) {
|
|
397
|
+
otherMemberReferenceCount++;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
node.forEachChild(walkInitializer);
|
|
401
|
+
};
|
|
402
|
+
toCombine.forEach((candidate) => walkInitializer(candidate.initializer));
|
|
403
|
+
// If at the end there is at least one reference between a combined member and another,
|
|
404
|
+
// and there are no references to any other class members, we can safely reorder the
|
|
405
|
+
// properties based on how they were initialized.
|
|
406
|
+
return combinedMemberReferenceCount > 0 && otherMemberReferenceCount === 0;
|
|
313
407
|
}
|
|
314
408
|
/**
|
|
315
409
|
* Finds the expressions from the constructor that initialize class members, for example:
|
|
@@ -366,7 +460,7 @@ function getMemberInitializers(constructor) {
|
|
|
366
460
|
function hasLocalReferences(root, constructor, localTypeChecker) {
|
|
367
461
|
const sourceFile = root.getSourceFile();
|
|
368
462
|
let hasLocalRefs = false;
|
|
369
|
-
|
|
463
|
+
const walk = (node) => {
|
|
370
464
|
// Stop searching if we know that it has local references.
|
|
371
465
|
if (hasLocalRefs) {
|
|
372
466
|
return;
|
|
@@ -396,7 +490,8 @@ function hasLocalReferences(root, constructor, localTypeChecker) {
|
|
|
396
490
|
if (!hasLocalRefs) {
|
|
397
491
|
node.forEachChild(walk);
|
|
398
492
|
}
|
|
399
|
-
}
|
|
493
|
+
};
|
|
494
|
+
walk(root);
|
|
400
495
|
return hasLocalRefs;
|
|
401
496
|
}
|
|
402
497
|
/**
|
|
@@ -437,28 +532,22 @@ function migrateFile(sourceFile, options) {
|
|
|
437
532
|
// 2. All the necessary information for this migration is local so using a file-specific type
|
|
438
533
|
// checker should speed up the lookups.
|
|
439
534
|
const localTypeChecker = getLocalTypeChecker(sourceFile);
|
|
440
|
-
const analysis = analyzeFile(sourceFile, localTypeChecker);
|
|
535
|
+
const analysis = analyzeFile(sourceFile, localTypeChecker, options);
|
|
441
536
|
if (analysis === null || analysis.classes.length === 0) {
|
|
442
537
|
return [];
|
|
443
538
|
}
|
|
444
539
|
const printer = ts__default["default"].createPrinter();
|
|
445
540
|
const tracker = new compiler_host.ChangeTracker(printer);
|
|
446
541
|
analysis.classes.forEach(({ node, constructor, superCall }) => {
|
|
447
|
-
|
|
542
|
+
const memberIndentation = leading_space.getLeadingLineWhitespaceOfNode(node.members[0]);
|
|
543
|
+
const prependToClass = [];
|
|
544
|
+
const afterInjectCalls = [];
|
|
545
|
+
const removedStatements = new Set();
|
|
546
|
+
const removedMembers = new Set();
|
|
448
547
|
if (options._internalCombineMemberInitializers) {
|
|
449
|
-
|
|
450
|
-
const statement = nodes.closestNode(initializer, ts__default["default"].isStatement);
|
|
451
|
-
if (!statement) {
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
const newProperty = ts__default["default"].factory.createPropertyDeclaration(cloneModifiers(property.modifiers), cloneName(property.name), property.questionToken, property.type, initializer);
|
|
455
|
-
tracker.replaceText(statement.getSourceFile(), statement.getFullStart(), statement.getFullWidth(), '');
|
|
456
|
-
tracker.replaceNode(property, newProperty);
|
|
457
|
-
removedStatements = removedStatements || new Set();
|
|
458
|
-
removedStatements.add(statement);
|
|
459
|
-
});
|
|
548
|
+
applyInternalOnlyChanges(node, constructor, localTypeChecker, tracker, printer, removedStatements, removedMembers, prependToClass, afterInjectCalls, memberIndentation);
|
|
460
549
|
}
|
|
461
|
-
migrateClass(node, constructor, superCall, options, removedStatements, localTypeChecker, printer, tracker);
|
|
550
|
+
migrateClass(node, constructor, superCall, options, memberIndentation, prependToClass, afterInjectCalls, removedStatements, removedMembers, localTypeChecker, printer, tracker);
|
|
462
551
|
});
|
|
463
552
|
DI_PARAM_SYMBOLS.forEach((name) => {
|
|
464
553
|
// Both zero and undefined are fine here.
|
|
@@ -474,61 +563,60 @@ function migrateFile(sourceFile, options) {
|
|
|
474
563
|
* @param constructor Reference to the class' constructor node.
|
|
475
564
|
* @param superCall Reference to the constructor's `super()` call, if any.
|
|
476
565
|
* @param options Options used to configure the migration.
|
|
566
|
+
* @param memberIndentation Indentation string of the members of the class.
|
|
567
|
+
* @param prependToClass Text that should be prepended to the class.
|
|
568
|
+
* @param afterInjectCalls Text that will be inserted after the newly-added `inject` calls.
|
|
477
569
|
* @param removedStatements Statements that have been removed from the constructor already.
|
|
570
|
+
* @param removedMembers Class members that have been removed by the migration.
|
|
478
571
|
* @param localTypeChecker Type checker set up for the specific file.
|
|
479
572
|
* @param printer Printer used to output AST nodes as strings.
|
|
480
573
|
* @param tracker Object keeping track of the changes made to the file.
|
|
481
574
|
*/
|
|
482
|
-
function migrateClass(node, constructor, superCall, options, removedStatements, localTypeChecker, printer, tracker) {
|
|
483
|
-
const isAbstract = !!node.modifiers?.some((m) => m.kind === ts__default["default"].SyntaxKind.AbstractKeyword);
|
|
484
|
-
// Don't migrate abstract classes by default, because
|
|
485
|
-
// their parameters aren't guaranteed to be injectable.
|
|
486
|
-
if (isAbstract && !options.migrateAbstractClasses) {
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
575
|
+
function migrateClass(node, constructor, superCall, options, memberIndentation, prependToClass, afterInjectCalls, removedStatements, removedMembers, localTypeChecker, printer, tracker) {
|
|
489
576
|
const sourceFile = node.getSourceFile();
|
|
490
577
|
const unusedParameters = getConstructorUnusedParameters(constructor, localTypeChecker, removedStatements);
|
|
491
578
|
const superParameters = superCall
|
|
492
579
|
? getSuperParameters(constructor, superCall, localTypeChecker)
|
|
493
580
|
: null;
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
const innerReference = superCall ||
|
|
497
|
-
constructor.body?.statements.find((statement) => !removedStatements?.has(statement)) ||
|
|
498
|
-
constructor;
|
|
581
|
+
const removedStatementCount = removedStatements.size;
|
|
582
|
+
const firstConstructorStatement = constructor.body?.statements.find((statement) => !removedStatements.has(statement));
|
|
583
|
+
const innerReference = superCall || firstConstructorStatement || constructor;
|
|
499
584
|
const innerIndentation = leading_space.getLeadingLineWhitespaceOfNode(innerReference);
|
|
500
|
-
const propsToAdd = [];
|
|
501
585
|
const prependToConstructor = [];
|
|
502
586
|
const afterSuper = [];
|
|
503
|
-
const removedMembers = new Set();
|
|
504
587
|
for (const param of constructor.parameters) {
|
|
505
588
|
const usedInSuper = superParameters !== null && superParameters.has(param);
|
|
506
589
|
const usedInConstructor = !unusedParameters.has(param);
|
|
507
|
-
migrateParameter(param, options, localTypeChecker, printer, tracker, superCall, usedInSuper, usedInConstructor, memberIndentation, innerIndentation, prependToConstructor,
|
|
590
|
+
migrateParameter(param, options, localTypeChecker, printer, tracker, superCall, usedInSuper, usedInConstructor, memberIndentation, innerIndentation, prependToConstructor, prependToClass, afterSuper);
|
|
508
591
|
}
|
|
509
592
|
// Delete all of the constructor overloads since below we're either going to
|
|
510
593
|
// remove the implementation, or we're going to delete all of the parameters.
|
|
511
594
|
for (const member of node.members) {
|
|
512
595
|
if (ts__default["default"].isConstructorDeclaration(member) && member !== constructor) {
|
|
513
596
|
removedMembers.add(member);
|
|
514
|
-
tracker.
|
|
597
|
+
tracker.removeNode(member, true);
|
|
515
598
|
}
|
|
516
599
|
}
|
|
517
600
|
if (canRemoveConstructor(options, constructor, removedStatementCount, superCall)) {
|
|
518
601
|
// Drop the constructor if it was empty.
|
|
519
602
|
removedMembers.add(constructor);
|
|
520
|
-
tracker.
|
|
603
|
+
tracker.removeNode(constructor, true);
|
|
521
604
|
}
|
|
522
605
|
else {
|
|
523
606
|
// If the constructor contains any statements, only remove the parameters.
|
|
524
607
|
// We always do this no matter what is passed into `backwardsCompatibleConstructors`.
|
|
525
608
|
stripConstructorParameters(constructor, tracker);
|
|
526
609
|
if (prependToConstructor.length > 0) {
|
|
527
|
-
tracker.insertText(sourceFile, innerReference.getFullStart(), `\n${prependToConstructor.join('\n')}\n`);
|
|
610
|
+
tracker.insertText(sourceFile, (firstConstructorStatement || innerReference).getFullStart(), `\n${prependToConstructor.join('\n')}\n`);
|
|
528
611
|
}
|
|
529
612
|
}
|
|
530
613
|
if (afterSuper.length > 0 && superCall !== null) {
|
|
531
|
-
|
|
614
|
+
// Note that if we can, we should insert before the next statement after the `super` call,
|
|
615
|
+
// rather than after the end of it. Otherwise the string buffering implementation may drop
|
|
616
|
+
// the text if the statement after the `super` call is being deleted. This appears to be because
|
|
617
|
+
// the full start of the next statement appears to always be the end of the `super` call plus 1.
|
|
618
|
+
const nextStatement = getNextPreservedStatement(superCall, removedStatements);
|
|
619
|
+
tracker.insertText(sourceFile, nextStatement ? nextStatement.getFullStart() : superCall.getEnd() + 1, `\n${afterSuper.join('\n')}\n`);
|
|
532
620
|
}
|
|
533
621
|
// Need to resolve this once all constructor signatures have been removed.
|
|
534
622
|
const memberReference = node.members.find((m) => !removedMembers.has(m)) || node.members[0];
|
|
@@ -539,19 +627,22 @@ function migrateClass(node, constructor, superCall, options, removedStatements,
|
|
|
539
627
|
`${memberIndentation}constructor(...args: unknown[]);`;
|
|
540
628
|
// The new signature always has to be right before the constructor implementation.
|
|
541
629
|
if (memberReference === constructor) {
|
|
542
|
-
|
|
630
|
+
prependToClass.push(extraSignature);
|
|
543
631
|
}
|
|
544
632
|
else {
|
|
545
633
|
tracker.insertText(sourceFile, constructor.getFullStart(), '\n' + extraSignature);
|
|
546
634
|
}
|
|
547
635
|
}
|
|
548
|
-
|
|
636
|
+
// Push the block of code that should appear after the `inject`
|
|
637
|
+
// calls now once all the members have been generated.
|
|
638
|
+
prependToClass.push(...afterInjectCalls);
|
|
639
|
+
if (prependToClass.length > 0) {
|
|
549
640
|
if (removedMembers.size === node.members.length) {
|
|
550
|
-
tracker.insertText(sourceFile, constructor.getEnd() + 1, `${
|
|
641
|
+
tracker.insertText(sourceFile, constructor.getEnd() + 1, `${prependToClass.join('\n')}\n`);
|
|
551
642
|
}
|
|
552
643
|
else {
|
|
553
644
|
// Insert the new properties after the first member that hasn't been deleted.
|
|
554
|
-
tracker.insertText(sourceFile, memberReference.getFullStart(), `\n${
|
|
645
|
+
tracker.insertText(sourceFile, memberReference.getFullStart(), `\n${prependToClass.join('\n')}\n`);
|
|
555
646
|
}
|
|
556
647
|
}
|
|
557
648
|
}
|
|
@@ -877,6 +968,83 @@ function canRemoveConstructor(options, constructor, removedStatementCount, super
|
|
|
877
968
|
return (statementCount === 0 ||
|
|
878
969
|
(statementCount === 1 && superCall !== null && superCall.arguments.length === 0));
|
|
879
970
|
}
|
|
971
|
+
/**
|
|
972
|
+
* Gets the next statement after a node that *won't* be deleted by the migration.
|
|
973
|
+
* @param startNode Node from which to start the search.
|
|
974
|
+
* @param removedStatements Statements that have been removed by the migration.
|
|
975
|
+
* @returns
|
|
976
|
+
*/
|
|
977
|
+
function getNextPreservedStatement(startNode, removedStatements) {
|
|
978
|
+
const body = nodes.closestNode(startNode, ts__default["default"].isBlock);
|
|
979
|
+
const closestStatement = nodes.closestNode(startNode, ts__default["default"].isStatement);
|
|
980
|
+
if (body === null || closestStatement === null) {
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
const index = body.statements.indexOf(closestStatement);
|
|
984
|
+
if (index === -1) {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
for (let i = index + 1; i < body.statements.length; i++) {
|
|
988
|
+
if (!removedStatements.has(body.statements[i])) {
|
|
989
|
+
return body.statements[i];
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Applies the internal-specific migrations to a class.
|
|
996
|
+
* @param node Class being migrated.
|
|
997
|
+
* @param constructor The migrated class' constructor.
|
|
998
|
+
* @param localTypeChecker File-specific type checker.
|
|
999
|
+
* @param tracker Object keeping track of the changes.
|
|
1000
|
+
* @param printer Printer used to output AST nodes as text.
|
|
1001
|
+
* @param removedStatements Statements that have been removed by the migration.
|
|
1002
|
+
* @param removedMembers Class members that have been removed by the migration.
|
|
1003
|
+
* @param prependToClass Text that will be prepended to a class.
|
|
1004
|
+
* @param afterInjectCalls Text that will be inserted after the newly-added `inject` calls.
|
|
1005
|
+
* @param memberIndentation Indentation string of the class' members.
|
|
1006
|
+
*/
|
|
1007
|
+
function applyInternalOnlyChanges(node, constructor, localTypeChecker, tracker, printer, removedStatements, removedMembers, prependToClass, afterInjectCalls, memberIndentation) {
|
|
1008
|
+
const result = findUninitializedPropertiesToCombine(node, constructor, localTypeChecker);
|
|
1009
|
+
if (result === null) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
const preserveInitOrder = shouldCombineInInitializationOrder(result.toCombine, constructor);
|
|
1013
|
+
// Sort the combined members based on the declaration order of their initializers, only if
|
|
1014
|
+
// we've determined that would be safe. Note that `Array.prototype.sort` is in-place so we
|
|
1015
|
+
// can just call it conditionally here.
|
|
1016
|
+
if (preserveInitOrder) {
|
|
1017
|
+
result.toCombine.sort((a, b) => a.initializer.getStart() - b.initializer.getStart());
|
|
1018
|
+
}
|
|
1019
|
+
result.toCombine.forEach(({ declaration, initializer }) => {
|
|
1020
|
+
const initializerStatement = nodes.closestNode(initializer, ts__default["default"].isStatement);
|
|
1021
|
+
const newProperty = ts__default["default"].factory.createPropertyDeclaration(cloneModifiers(declaration.modifiers), cloneName(declaration.name), declaration.questionToken, declaration.type, initializer);
|
|
1022
|
+
// If the initialization order is being preserved, we have to remove the original
|
|
1023
|
+
// declaration and re-declare it. Otherwise we can do the replacement in-place.
|
|
1024
|
+
if (preserveInitOrder) {
|
|
1025
|
+
tracker.removeNode(declaration, true);
|
|
1026
|
+
removedMembers.add(declaration);
|
|
1027
|
+
afterInjectCalls.push(memberIndentation +
|
|
1028
|
+
printer.printNode(ts__default["default"].EmitHint.Unspecified, newProperty, declaration.getSourceFile()));
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
tracker.replaceNode(declaration, newProperty);
|
|
1032
|
+
}
|
|
1033
|
+
// This should always be defined, but null check it just in case.
|
|
1034
|
+
if (initializerStatement) {
|
|
1035
|
+
tracker.removeNode(initializerStatement, true);
|
|
1036
|
+
removedStatements.add(initializerStatement);
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
result.toHoist.forEach((decl) => {
|
|
1040
|
+
prependToClass.push(memberIndentation + printer.printNode(ts__default["default"].EmitHint.Unspecified, decl, decl.getSourceFile()));
|
|
1041
|
+
tracker.removeNode(decl, true);
|
|
1042
|
+
});
|
|
1043
|
+
// If we added any hoisted properties, separate them visually with a new line.
|
|
1044
|
+
if (prependToClass.length > 0) {
|
|
1045
|
+
prependToClass.push('');
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
880
1048
|
|
|
881
1049
|
function migrate(options) {
|
|
882
1050
|
return async (tree) => {
|