fix(migrations): inject migration not handling super parameter referenced via this (#60602)

The inject migration has some logic that treats parameters referenced directly inside of `super` differently. This logic didn't account for the fact that the parameters could be inside of inline functions which have less strict access requirements.

PR Close #60602
This commit is contained in:
Kristiyan Kostadinov
2025-03-28 09:52:36 +01:00
committed by Jessica Janiuk
parent 7c971566ad
commit 3ff48b6467
3 changed files with 49 additions and 7 deletions
@@ -304,7 +304,9 @@ export function getSuperParameters(
usedParams.add(decl);
}
});
} else {
// Parameters referenced inside callbacks can be used directly
// within `super` so don't descend into inline functions.
} else if (!isInlineFunction(node)) {
node.forEachChild(walk);
}
});
@@ -423,3 +425,12 @@ function findSuperCall(root: ts.Node): ts.CallExpression | null {
return result;
}
/** Checks whether a node is an inline function. */
export function isInlineFunction(
node: ts.Node,
): node is ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction {
return (
ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)
);
}
@@ -7,7 +7,7 @@
*/
import ts from 'typescript';
import {isAccessedViaThis, parameterDeclaresProperty} from './analysis';
import {isAccessedViaThis, isInlineFunction, parameterDeclaresProperty} from './analysis';
/** Property that is a candidate to be combined. */
interface CombineCandidate {
@@ -299,11 +299,7 @@ function isInsideInlineFunction(startNode: ts.Node, boundary: ts.Node): boolean
return false;
}
if (
ts.isFunctionDeclaration(current) ||
ts.isFunctionExpression(current) ||
ts.isArrowFunction(current)
) {
if (isInlineFunction(current)) {
return true;
}
@@ -1800,6 +1800,41 @@ describe('inject migration', () => {
]);
});
it('should handle parameter referenced through `this` inside a callback within super()', async () => {
writeFile(
'/dir.ts',
[
`import { Directive } from '@angular/core';`,
`import { Service } from './service';`,
`import { Parent } from './parent';`,
``,
`@Directive()`,
`export class MyDir extends Parent {`,
` constructor(private service: Service) {`,
` super({callback: () => this.service.doStuff()});`,
` }`,
`}`,
].join('\n'),
);
await runMigration();
expect(tree.readContent('/dir.ts').split('\n')).toEqual([
`import { Directive, inject } from '@angular/core';`,
`import { Service } from './service';`,
`import { Parent } from './parent';`,
``,
`@Directive()`,
`export class MyDir extends Parent {`,
` private service = inject(Service);`,
``,
` constructor() {`,
` super({callback: () => this.service.doStuff()});`,
` }`,
`}`,
]);
});
describe('internal-only behavior', () => {
function runInternalMigration() {
return runMigration({_internalCombineMemberInitializers: true});