Skip to content

Commit 7c5f990

Browse files
crisbetopkozlowski-opensource
authored andcommitted
fix(migrations): inject migration aggressively removing imports (#58959)
Fixes that the inject migration would remove imports even if they're still used by classes that aren't being migrated. PR Close #58959
1 parent d1cbdd6 commit 7c5f990

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

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

+18-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import ts from 'typescript';
1010
import {getAngularDecorators} from '../../utils/ng_decorators';
1111
import {getNamedImports} from '../../utils/typescript/imports';
12+
import {closestNode} from '../../utils/typescript/nodes';
1213

1314
/** Options that can be used to configure the migration. */
1415
export interface MigrationOptions {
@@ -99,12 +100,27 @@ export function analyzeFile(
99100
return;
100101
}
101102

102-
// Only visit the initializer of parameters, because we won't exclude
103-
// their decorators from the identifier counting result below.
104103
if (ts.isParameter(node)) {
104+
const closestConstructor = closestNode(node, ts.isConstructorDeclaration);
105+
106+
// Visiting the same parameters that we're about to remove can throw off the reference
107+
// counting logic below. If we run into an initializer, we always visit its initializer
108+
// and optionally visit the modifiers/decorators if it's not due to be deleted. Note that
109+
// here we technically aren't dealing with the the full list of classes, but the parent class
110+
// will have been visited by the time we reach the parameters.
105111
if (node.initializer) {
106112
walk(node.initializer);
107113
}
114+
115+
if (
116+
closestConstructor === null ||
117+
// This is meant to avoid the case where this is a
118+
// parameter inside a function placed in a constructor.
119+
!closestConstructor.parameters.includes(node) ||
120+
!classes.some((c) => c.constructor === closestConstructor)
121+
) {
122+
node.modifiers?.forEach(walk);
123+
}
108124
return;
109125
}
110126

packages/core/schematics/test/inject_migration_spec.ts

+39
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,45 @@ describe('inject migration', () => {
16841684
]);
16851685
});
16861686

1687+
it('should not remove decorator imports if unmigrated classes are still using them', async () => {
1688+
writeFile(
1689+
'/dir.ts',
1690+
[
1691+
`import { Directive, Optional } from '@angular/core';`,
1692+
`import { Foo } from 'foo';`,
1693+
`import { Bar } from 'bar';`,
1694+
``,
1695+
`@Directive()`,
1696+
`class WillMigrate {`,
1697+
` constructor(@Optional() private foo: Foo) {}`,
1698+
`}`,
1699+
``,
1700+
`@Directive()`,
1701+
`abstract class WillNotMigrate {`,
1702+
` constructor(@Optional() private bar: Bar) {}`,
1703+
`}`,
1704+
].join('\n'),
1705+
);
1706+
1707+
await runMigration();
1708+
1709+
expect(tree.readContent('/dir.ts').split('\n')).toEqual([
1710+
`import { Directive, Optional, inject } from '@angular/core';`,
1711+
`import { Foo } from 'foo';`,
1712+
`import { Bar } from 'bar';`,
1713+
``,
1714+
`@Directive()`,
1715+
`class WillMigrate {`,
1716+
` private foo = inject(Foo, { optional: true });`,
1717+
`}`,
1718+
``,
1719+
`@Directive()`,
1720+
`abstract class WillNotMigrate {`,
1721+
` constructor(@Optional() private bar: Bar) {}`,
1722+
`}`,
1723+
]);
1724+
});
1725+
16871726
describe('internal-only behavior', () => {
16881727
function runInternalMigration() {
16891728
return runMigration({_internalCombineMemberInitializers: true});

0 commit comments

Comments
 (0)