Skip to content

Commit aa2d815

Browse files
fix(migrations): Add support for removing imports post migration (#52763)
This update removes imports from component decorators and at the top of the files. It only removes standalone imports though. It does not remove CommonModule if that is the only import. PR Close #52763
1 parent 54a7b33 commit aa2d815

File tree

5 files changed

+386
-60
lines changed

5 files changed

+386
-60
lines changed

packages/core/schematics/ng-generate/control-flow-migration/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ function runControlFlowMigration(
8282
const content = tree.readText(relativePath);
8383
const update = tree.beginUpdate(relativePath);
8484

85-
for (const [start, end] of ranges) {
85+
for (const {start, end, node, type} of ranges) {
8686
const template = content.slice(start, end);
8787
const length = (end ?? content.length) - start;
8888

89-
const {migrated, errors} = migrateTemplate(template);
89+
const {migrated, errors} = migrateTemplate(template, type, node, file);
9090

9191
if (migrated !== null) {
9292
update.remove(start, length);

packages/core/schematics/ng-generate/control-flow-migration/migration.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,37 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import ts from 'typescript';
10+
911
import {migrateFor} from './fors';
1012
import {migrateIf} from './ifs';
1113
import {migrateSwitch} from './switches';
12-
import {MigrateError} from './types';
13-
import {processNgTemplates} from './util';
14+
import {AnalyzedFile, MigrateError} from './types';
15+
import {canRemoveCommonModule, processNgTemplates, removeImports} from './util';
1416

1517
/**
1618
* Actually migrates a given template to the new syntax
1719
*/
18-
export function migrateTemplate(template: string): {migrated: string, errors: MigrateError[]} {
19-
const ifResult = migrateIf(template);
20-
const forResult = migrateFor(ifResult.migrated);
21-
const switchResult = migrateSwitch(forResult.migrated);
20+
export function migrateTemplate(
21+
template: string, templateType: string, node: ts.Node,
22+
file: AnalyzedFile): {migrated: string, errors: MigrateError[]} {
23+
let errors: MigrateError[] = [];
24+
let migrated = template;
25+
if (templateType === 'template') {
26+
const ifResult = migrateIf(template);
27+
const forResult = migrateFor(ifResult.migrated);
28+
const switchResult = migrateSwitch(forResult.migrated);
29+
migrated = processNgTemplates(switchResult.migrated);
30+
file.removeCommonModule = canRemoveCommonModule(template);
2231

23-
const migrated = processNgTemplates(switchResult.migrated);
32+
errors = [
33+
...ifResult.errors,
34+
...forResult.errors,
35+
...switchResult.errors,
36+
];
37+
} else {
38+
migrated = removeImports(template, node, file.removeCommonModule);
39+
}
2440

25-
const errors = [
26-
...ifResult.errors,
27-
...forResult.errors,
28-
...switchResult.errors,
29-
];
3041
return {migrated, errors};
3142
}

packages/core/schematics/ng-generate/control-flow-migration/types.ts

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,59 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Attribute, Element, RecursiveVisitor} from '@angular/compiler';
9+
import {Attribute, Element, RecursiveVisitor, Text} from '@angular/compiler';
10+
import ts from 'typescript';
1011

1112
export const ngtemplate = 'ng-template';
1213

14+
function allFormsOf(selector: string): string[] {
15+
return [
16+
selector,
17+
`*${selector}`,
18+
`[${selector}]`,
19+
];
20+
}
21+
22+
const commonModuleDirectives = new Set([
23+
...allFormsOf('ngComponentOutlet'),
24+
...allFormsOf('ngTemplateOutlet'),
25+
...allFormsOf('ngClass'),
26+
...allFormsOf('ngPlural'),
27+
...allFormsOf('ngPluralCase'),
28+
...allFormsOf('ngStyle'),
29+
...allFormsOf('ngTemplateOutlet'),
30+
...allFormsOf('ngComponentOutlet'),
31+
]);
32+
33+
function pipeMatchRegExpFor(name: string): RegExp {
34+
return new RegExp(`\\|\\s*${name}`);
35+
}
36+
37+
const commonModulePipes = [
38+
'date',
39+
'async',
40+
'currency',
41+
'number',
42+
'i18nPlural',
43+
'i18nSelect',
44+
'json',
45+
'keyvalue',
46+
'slice',
47+
'lowercase',
48+
'uppercase',
49+
'titlecase',
50+
'percent',
51+
'titlecase',
52+
].map(name => pipeMatchRegExpFor(name));
53+
1354
/**
1455
* Represents a range of text within a file. Omitting the end
1556
* means that it's until the end of the file.
1657
*/
17-
type Range = [start: number, end?: number];
58+
type Range = {
59+
start: number,
60+
end?: number, node: ts.Node, type: string,
61+
};
1862

1963
export type Offsets = {
2064
pre: number,
@@ -104,10 +148,17 @@ export class Template {
104148
/** Represents a file that was analyzed by the migration. */
105149
export class AnalyzedFile {
106150
private ranges: Range[] = [];
151+
removeCommonModule = false;
107152

108153
/** Returns the ranges in the order in which they should be migrated. */
109154
getSortedRanges(): Range[] {
110-
return this.ranges.slice().sort(([aStart], [bStart]) => bStart - aStart);
155+
const templateRanges = this.ranges.slice()
156+
.filter(x => x.type === 'template')
157+
.sort((aStart, bStart) => bStart.start - aStart.start);
158+
const importRanges = this.ranges.slice()
159+
.filter(x => x.type === 'import')
160+
.sort((aStart, bStart) => bStart.start - aStart.start);
161+
return [...templateRanges, ...importRanges];
111162
}
112163

113164
/**
@@ -125,14 +176,44 @@ export class AnalyzedFile {
125176
}
126177

127178
const duplicate =
128-
analysis.ranges.find(current => current[0] === range[0] && current[1] === range[1]);
179+
analysis.ranges.find(current => current.start === range.start && current.end === range.end);
129180

130181
if (!duplicate) {
131182
analysis.ranges.push(range);
132183
}
133184
}
134185
}
135186

187+
/** Finds all non-control flow elements from common module. */
188+
export class CommonCollector extends RecursiveVisitor {
189+
count = 0;
190+
191+
override visitElement(el: Element): void {
192+
if (el.attrs.length > 0) {
193+
for (const attr of el.attrs) {
194+
if (this.hasDirectives(attr.name) || this.hasPipes(attr.value)) {
195+
this.count++;
196+
}
197+
}
198+
}
199+
super.visitElement(el, null);
200+
}
201+
202+
override visitText(ast: Text) {
203+
if (this.hasPipes(ast.value)) {
204+
this.count++;
205+
}
206+
}
207+
208+
private hasDirectives(input: string): boolean {
209+
return commonModuleDirectives.has(input);
210+
}
211+
212+
private hasPipes(input: string): boolean {
213+
return commonModulePipes.some(regexp => regexp.test(input));
214+
}
215+
}
216+
136217
/** Finds all elements with ngif structural directives. */
137218
export class ElementCollector extends RecursiveVisitor {
138219
readonly elements: ElementToMigrate[] = [];

0 commit comments

Comments
 (0)