From 3ff48b64670645a70c69706c4b20ee11e8f61306 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 28 Mar 2025 09:52:36 +0100 Subject: [PATCH] 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 --- .../ng-generate/inject-migration/analysis.ts | 13 ++++++- .../ng-generate/inject-migration/internal.ts | 8 ++--- .../schematics/test/inject_migration_spec.ts | 35 +++++++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/packages/core/schematics/ng-generate/inject-migration/analysis.ts b/packages/core/schematics/ng-generate/inject-migration/analysis.ts index 9dd9ca2dcca..a513eba6c82 100644 --- a/packages/core/schematics/ng-generate/inject-migration/analysis.ts +++ b/packages/core/schematics/ng-generate/inject-migration/analysis.ts @@ -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) + ); +} diff --git a/packages/core/schematics/ng-generate/inject-migration/internal.ts b/packages/core/schematics/ng-generate/inject-migration/internal.ts index 8c94033f3dd..095b733e4f2 100644 --- a/packages/core/schematics/ng-generate/inject-migration/internal.ts +++ b/packages/core/schematics/ng-generate/inject-migration/internal.ts @@ -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; } diff --git a/packages/core/schematics/test/inject_migration_spec.ts b/packages/core/schematics/test/inject_migration_spec.ts index dbe58e99b81..fb5e906e049 100644 --- a/packages/core/schematics/test/inject_migration_spec.ts +++ b/packages/core/schematics/test/inject_migration_spec.ts @@ -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});