fix(core): preserve comments in internal inject migration (#60588)

The internal-only combineMemberInitializers option
for the inject migration sometimes dropped the
doc comments from the members.

PR Close #60588
This commit is contained in:
Ryan Russell
2025-03-27 15:49:08 +00:00
committed by Jessica Janiuk
parent 76c60a609f
commit 0cd7d3bdf0
2 changed files with 61 additions and 7 deletions
@@ -773,17 +773,19 @@ function applyInternalOnlyChanges(
result.toCombine.forEach(({declaration, initializer}) => {
const initializerStatement = closestNode(initializer, ts.isStatement);
const newProperty = ts.factory.createPropertyDeclaration(
cloneModifiers(declaration.modifiers),
cloneName(declaration.name),
declaration.questionToken,
declaration.type,
initializer,
);
// If the initialization order is being preserved, we have to remove the original
// declaration and re-declare it. Otherwise we can do the replacement in-place.
if (preserveInitOrder) {
// Preserve comment in the new property since we are removing the entire node.
const newProperty = ts.factory.createPropertyDeclaration(
declaration.modifiers,
declaration.name,
declaration.questionToken,
declaration.type,
initializer,
);
tracker.removeNode(declaration, true);
removedMembers.add(declaration);
afterInjectCalls.push(
@@ -791,6 +793,16 @@ function applyInternalOnlyChanges(
printer.printNode(ts.EmitHint.Unspecified, newProperty, declaration.getSourceFile()),
);
} else {
// Strip comments from the declaration since we are replacing just
// the node, not the leading comment.
const newProperty = ts.factory.createPropertyDeclaration(
cloneModifiers(declaration.modifiers),
cloneName(declaration.name),
declaration.questionToken,
declaration.type,
initializer,
);
tracker.replaceNode(declaration, newProperty);
}
@@ -2221,6 +2221,48 @@ describe('inject migration', () => {
]);
});
it('should account for doc strings when inlining initializers and combining in initialization order', async () => {
writeFile(
'/dir.ts',
[
`import { Directive } from '@angular/core';`,
`import { Foo } from 'foo';`,
``,
`@Directive()`,
`class MyDir {`,
` /** Value of Foo */`,
` private readonly value: number;`,
``,
` /** ID of Foo */`,
` id: string;`,
``,
` constructor(private foo: Foo) {`,
` this.value = this.foo.getValue();`,
` this.id = this.value.toString();`,
` }`,
`}`,
].join('\n'),
);
await runInternalMigration();
expect(tree.readContent('/dir.ts').split('\n')).toEqual([
`import { Directive, inject } from '@angular/core';`,
`import { Foo } from 'foo';`,
``,
`@Directive()`,
`class MyDir {`,
` private foo = inject(Foo);`,
// The indentation of the members here is slightly off,
// but it's not a problem because this code is internal-only.
` /** Value of Foo */`,
`private readonly value: number = this.foo.getValue();`,
` /** ID of Foo */`,
`id: string = this.value.toString();`,
`}`,
]);
});
it('should hoist property declarations that were not combined above the inject() calls', async () => {
writeFile(
'/dir.ts',