Skip to content

Commit 7a65cdd

Browse files
crisbetoalxhub
authored andcommitted
fix(migrations): inject migration not inserting generated code after super call in some cases (#58393)
Fixes an issue where the `inject` migration was generating and attempting to insert code after a `super` call, but the string buffering implementation was dropping it if the statement right after the `super` call was deleted as a result of the migration. PR Close #58393
1 parent 1806fcc commit 7a65cdd

File tree

2 files changed

+85
-1
lines changed

2 files changed

+85
-1
lines changed

packages/core/schematics/ng-generate/inject-migration/migration.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,16 @@ function migrateClass(
224224
}
225225

226226
if (afterSuper.length > 0 && superCall !== null) {
227-
tracker.insertText(sourceFile, superCall.getEnd() + 1, `\n${afterSuper.join('\n')}\n`);
227+
// Note that if we can, we should insert before the next statement after the `super` call,
228+
// rather than after the end of it. Otherwise the string buffering implementation may drop
229+
// the text if the statement after the `super` call is being deleted. This appears to be because
230+
// the full start of the next statement appears to always be the end of the `super` call plus 1.
231+
const nextStatement = getNextPreservedStatement(superCall, removedStatements);
232+
tracker.insertText(
233+
sourceFile,
234+
nextStatement ? nextStatement.getFullStart() : superCall.getEnd() + 1,
235+
`\n${afterSuper.join('\n')}\n`,
236+
);
228237
}
229238

230239
// Need to resolve this once all constructor signatures have been removed.
@@ -676,6 +685,36 @@ function canRemoveConstructor(
676685
);
677686
}
678687

688+
/**
689+
* Gets the next statement after a node that *won't* be deleted by the migration.
690+
* @param startNode Node from which to start the search.
691+
* @param removedStatements Statements that have been removed by the migration.
692+
* @returns
693+
*/
694+
function getNextPreservedStatement(
695+
startNode: ts.Node,
696+
removedStatements: Set<ts.Statement>,
697+
): ts.Statement | null {
698+
const body = closestNode(startNode, ts.isBlock);
699+
const closestStatement = closestNode(startNode, ts.isStatement);
700+
if (body === null || closestStatement === null) {
701+
return null;
702+
}
703+
704+
const index = body.statements.indexOf(closestStatement);
705+
if (index === -1) {
706+
return null;
707+
}
708+
709+
for (let i = index + 1; i < body.statements.length; i++) {
710+
if (!removedStatements.has(body.statements[i])) {
711+
return body.statements[i];
712+
}
713+
}
714+
715+
return null;
716+
}
717+
679718
/**
680719
* Applies the internal-specific migrations to a class.
681720
* @param node Class being migrated.

packages/core/schematics/test/inject_migration_spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1941,5 +1941,50 @@ describe('inject migration', () => {
19411941
`}`,
19421942
]);
19431943
});
1944+
1945+
it('should be able to insert statements after the `super` call when running in internal migration mode', async () => {
1946+
writeFile(
1947+
'/dir.ts',
1948+
[
1949+
`import { Directive, Inject, ElementRef } from '@angular/core';`,
1950+
`import { Foo } from 'foo';`,
1951+
`import { Parent } from './parent';`,
1952+
``,
1953+
`@Directive()`,
1954+
`class MyDir extends Parent {`,
1955+
` private value: number;`,
1956+
``,
1957+
` constructor(private foo: Foo, readonly elementRef: ElementRef) {`,
1958+
` super();`,
1959+
` this.value = this.foo.getValue();`,
1960+
` console.log(elementRef.nativeElement.tagName);`,
1961+
` }`,
1962+
`}`,
1963+
].join('\n'),
1964+
);
1965+
1966+
await runInternalMigration();
1967+
1968+
expect(tree.readContent('/dir.ts').split('\n')).toEqual([
1969+
`import { Directive, ElementRef, inject } from '@angular/core';`,
1970+
`import { Foo } from 'foo';`,
1971+
`import { Parent } from './parent';`,
1972+
``,
1973+
`@Directive()`,
1974+
`class MyDir extends Parent {`,
1975+
` private foo = inject(Foo);`,
1976+
` readonly elementRef = inject(ElementRef);`,
1977+
``,
1978+
` private value: number = this.foo.getValue();`,
1979+
``,
1980+
` constructor() {`,
1981+
` super();`,
1982+
` const elementRef = this.elementRef;`,
1983+
``,
1984+
` console.log(elementRef.nativeElement.tagName);`,
1985+
` }`,
1986+
`}`,
1987+
]);
1988+
});
19441989
});
19451990
});

0 commit comments

Comments
 (0)