Skip to content

Commit 7191aa6

Browse files
crisbetopkozlowski-opensource
authored andcommitted
fix(migrations): don't migrate classes with parameters that can't be injected (#58959)
Updates the inject migrations to skip over classes that have uninjectable class parameters. PR Close #58959
1 parent 68e5ba7 commit 7191aa6

File tree

2 files changed

+46
-1
lines changed

2 files changed

+46
-1
lines changed

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ export const DI_PARAM_SYMBOLS = new Set([
6262
'forwardRef',
6363
]);
6464

65+
/** Kinds of nodes which aren't injectable when set as a type of a parameter. */
66+
const UNINJECTABLE_TYPE_KINDS = new Set([
67+
ts.SyntaxKind.TrueKeyword,
68+
ts.SyntaxKind.FalseKeyword,
69+
ts.SyntaxKind.NumberKeyword,
70+
ts.SyntaxKind.StringKeyword,
71+
ts.SyntaxKind.NullKeyword,
72+
ts.SyntaxKind.VoidKeyword,
73+
]);
74+
6575
/**
6676
* Finds the necessary information for the `inject` migration in a file.
6777
* @param sourceFile File which to analyze.
@@ -156,9 +166,26 @@ export function analyzeFile(
156166
member.parameters.length > 0,
157167
) as ts.ConstructorDeclaration | undefined;
158168

169+
// Basic check to determine if all parameters are injectable. This isn't exhaustive, but it
170+
// should catch the majority of cases. An exhaustive check would require a full type checker
171+
// which we don't have in this migration.
172+
const allParamsInjectable = !!constructorNode?.parameters.every((param) => {
173+
if (!param.type || !UNINJECTABLE_TYPE_KINDS.has(param.type.kind)) {
174+
return true;
175+
}
176+
return getAngularDecorators(localTypeChecker, ts.getDecorators(param) || []).some(
177+
(dec) => dec.name === 'Inject' || dec.name === 'Attribute',
178+
);
179+
});
180+
159181
// Don't migrate abstract classes by default, because
160182
// their parameters aren't guaranteed to be injectable.
161-
if (supportsDI && constructorNode && (!isAbstract || options.migrateAbstractClasses)) {
183+
if (
184+
supportsDI &&
185+
constructorNode &&
186+
allParamsInjectable &&
187+
(!isAbstract || options.migrateAbstractClasses)
188+
) {
162189
classes.push({
163190
node,
164191
constructor: constructorNode,

packages/core/schematics/test/inject_migration_spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,24 @@ describe('inject migration', () => {
13411341
]);
13421342
});
13431343

1344+
it('should not migrate class that has un-injectable parameters', async () => {
1345+
const initialText = [
1346+
`import { Directive, Inject } from '@angular/core';`,
1347+
`import { FOO_TOKEN, Foo } from 'foo';`,
1348+
``,
1349+
`@Directive()`,
1350+
`class MyDir {`,
1351+
` constructor(readonly injectable: Foo, private notInjectable: string) {}`,
1352+
`}`,
1353+
].join('\n');
1354+
1355+
writeFile('/dir.ts', initialText);
1356+
1357+
await runMigration();
1358+
1359+
expect(tree.readContent('/dir.ts')).toBe(initialText);
1360+
});
1361+
13441362
it('should unwrap forwardRef with an implicit return', async () => {
13451363
writeFile(
13461364
'/dir.ts',

0 commit comments

Comments
 (0)