@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.
Files changed (42) hide show
  1. package/fesm2022/core.mjs +21183 -19410
  2. package/fesm2022/core.mjs.map +1 -1
  3. package/fesm2022/primitives/event-dispatch.mjs +71 -47
  4. package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
  5. package/fesm2022/primitives/signals.mjs +8 -6
  6. package/fesm2022/primitives/signals.mjs.map +1 -1
  7. package/fesm2022/rxjs-interop.mjs +90 -10
  8. package/fesm2022/rxjs-interop.mjs.map +1 -1
  9. package/fesm2022/testing.mjs +174 -113
  10. package/fesm2022/testing.mjs.map +1 -1
  11. package/index.d.ts +524 -92
  12. package/package.json +1 -1
  13. package/primitives/event-dispatch/index.d.ts +7 -4
  14. package/primitives/signals/index.d.ts +3 -1
  15. package/rxjs-interop/index.d.ts +35 -4
  16. package/schematics/bundles/{checker-3b2ea20f.js → checker-9ca42e51.js} +2303 -1006
  17. package/schematics/bundles/combine_units-a16385aa.js +1634 -0
  18. package/schematics/bundles/{compiler_host-b4ba5a28.js → compiler_host-31afa4ed.js} +8 -5
  19. package/schematics/bundles/control-flow-migration.js +3 -3
  20. package/schematics/bundles/explicit-standalone-flag.js +29 -9
  21. package/schematics/bundles/imports-4ac08251.js +1 -1
  22. package/schematics/bundles/inject-migration.js +222 -54
  23. package/schematics/bundles/leading_space-d190b83b.js +1 -1
  24. package/schematics/bundles/migrate_ts_type_references-b2a28742.js +1463 -0
  25. package/schematics/bundles/nodes-0e7d45ca.js +1 -1
  26. package/schematics/bundles/output-migration.js +575 -0
  27. package/schematics/bundles/pending-tasks.js +3 -3
  28. package/schematics/bundles/{program-6534a30a.js → program-71beec0b.js} +1385 -460
  29. package/schematics/bundles/project_tsconfig_paths-e9ccccbf.js +1 -1
  30. package/schematics/bundles/provide-initializer.js +179 -0
  31. package/schematics/bundles/route-lazy-loading.js +9 -4
  32. package/schematics/bundles/signal-input-migration.js +183 -311
  33. package/schematics/bundles/signal-queries-migration.js +404 -146
  34. package/schematics/bundles/signals.js +54 -0
  35. package/schematics/bundles/standalone-migration.js +29 -12
  36. package/schematics/collection.json +11 -0
  37. package/schematics/migrations.json +7 -1
  38. package/schematics/ng-generate/output-migration/schema.json +19 -0
  39. package/schematics/ng-generate/signal-queries-migration/schema.json +11 -0
  40. package/schematics/ng-generate/signals/schema.json +65 -0
  41. package/testing/index.d.ts +1 -1
  42. 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-next.9
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-3b2ea20f.js');
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-next.9
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-b4ba5a28.js');
14
- var checker = require('./checker-3b2ea20f.js');
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-next.9
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-b4ba5a28.js');
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-3b2ea20f.js');
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
- // Need to take care of 3 cases
81
- // - standalone: true => remove the property
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
- const standaloneFalseProperty = ts__default["default"].factory.createPropertyAssignment('standalone', ts__default["default"].factory.createFalse());
87
- newProperties = [...properties, standaloneFalseProperty];
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-next.9
3
+ * @license Angular v19.0.0-rc.1
4
4
  * (c) 2010-2024 Google LLC. https://angular.io/
5
5
  * License: MIT
6
6
  */
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  /**
3
- * @license Angular v19.0.0-next.9
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-b4ba5a28.js');
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-3b2ea20f.js');
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
- if (supportsDI && constructorNode) {
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 (removedStatements && ts__default["default"].isStatement(node) && removedStatements.has(node)) {
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 result = null;
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 result;
303
+ return null;
300
304
  }
301
305
  const memberInitializers = getMemberInitializers(constructor);
302
306
  if (memberInitializers === null) {
303
- return result;
307
+ return null;
304
308
  }
305
- for (const [name, initializer] of memberInitializers.entries()) {
306
- if (membersToDeclarations.has(name) &&
307
- !hasLocalReferences(initializer, constructor, localTypeChecker)) {
308
- result = result || new Map();
309
- result.set(membersToDeclarations.get(name), initializer);
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
- return result;
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
- root.forEachChild(function walk(node) {
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
- let removedStatements = null;
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
- findUninitializedPropertiesToCombine(node, constructor, localTypeChecker)?.forEach((initializer, property) => {
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 memberIndentation = leading_space.getLeadingLineWhitespaceOfNode(node.members[0]);
495
- const removedStatementCount = removedStatements?.size || 0;
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, propsToAdd, afterSuper);
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.replaceText(sourceFile, member.getFullStart(), member.getFullWidth(), '');
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.replaceText(sourceFile, constructor.getFullStart(), constructor.getFullWidth(), '');
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
- tracker.insertText(sourceFile, superCall.getEnd() + 1, `\n${afterSuper.join('\n')}\n`);
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
- propsToAdd.push(extraSignature);
630
+ prependToClass.push(extraSignature);
543
631
  }
544
632
  else {
545
633
  tracker.insertText(sourceFile, constructor.getFullStart(), '\n' + extraSignature);
546
634
  }
547
635
  }
548
- if (propsToAdd.length > 0) {
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, `${propsToAdd.join('\n')}\n`);
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${propsToAdd.join('\n')}\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) => {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  /**
3
- * @license Angular v19.0.0-next.9
3
+ * @license Angular v19.0.0-rc.1
4
4
  * (c) 2010-2024 Google LLC. https://angular.io/
5
5
  * License: MIT
6
6
  */