Skip to content

Commit 2ae69f7

Browse files
JeanMecheatscott
authored andcommitted
refactor: ensure tsurge migrations have clear ownership of files (#61612)
This is a patch port of #61421 PR Close #61612
1 parent c101a3a commit 2ae69f7

File tree

3 files changed

+21
-70
lines changed

3 files changed

+21
-70
lines changed

packages/core/schematics/ng-generate/cleanup-unused-imports/unused_imports_migration.ts

Lines changed: 20 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
MigrationStats,
1313
ProgramInfo,
1414
projectFile,
15-
ProjectFileID,
1615
Replacement,
1716
Serializable,
1817
TextUpdate,
@@ -28,8 +27,8 @@ export interface CompilationUnitData {
2827
/** Text changes that should be performed. */
2928
replacements: Replacement[];
3029

31-
/** Identifiers that have been removed from each file. */
32-
removedIdentifiers: NodeID[];
30+
/** Total number of imports that were removed. */
31+
removedImports: number;
3332

3433
/** Total number of files that were changed. */
3534
changedFiles: number;
@@ -44,7 +43,7 @@ interface RemovalLocations {
4443
partialRemovals: Map<ts.ArrayLiteralExpression, Set<ts.Expression>>;
4544

4645
/** Text of all identifiers that have been removed. */
47-
allRemovedIdentifiers: Set<ts.Identifier>;
46+
allRemovedIdentifiers: Set<string>;
4847
}
4948

5049
/** Tracks how identifiers are used across a single file. */
@@ -60,9 +59,6 @@ interface UsageAnalysis {
6059
identifierCounts: Map<string, number>;
6160
}
6261

63-
/** ID of a node based on its location. */
64-
type NodeID = string & {__nodeID: true};
65-
6662
/** Migration that cleans up unused imports from a project. */
6763
export class UnusedImportsMigration extends TsurgeFunnelMigration<
6864
CompilationUnitData,
@@ -84,7 +80,7 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
8480
override async analyze(info: ProgramInfo): Promise<Serializable<CompilationUnitData>> {
8581
const nodePositions = new Map<ts.SourceFile, Set<string>>();
8682
const replacements: Replacement[] = [];
87-
const removedIdentifiers: NodeID[] = [];
83+
let removedImports = 0;
8884
let changedFiles = 0;
8985

9086
info.ngCompiler?.getDiagnostics().forEach((diag) => {
@@ -102,7 +98,7 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
10298
if (!nodePositions.has(diag.file)) {
10399
nodePositions.set(diag.file, new Set());
104100
}
105-
nodePositions.get(diag.file)!.add(this.getNodeID(diag.start, diag.length));
101+
nodePositions.get(diag.file)!.add(this.getNodeKey(diag.start, diag.length));
106102
}
107103
});
108104

@@ -111,15 +107,14 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
111107
const usageAnalysis = this.analyzeUsages(sourceFile, resolvedLocations);
112108

113109
if (resolvedLocations.allRemovedIdentifiers.size > 0) {
110+
removedImports += resolvedLocations.allRemovedIdentifiers.size;
114111
changedFiles++;
115-
resolvedLocations.allRemovedIdentifiers.forEach((identifier) => {
116-
removedIdentifiers.push(this.getNodeID(identifier.getStart(), identifier.getWidth()));
117-
});
118112
}
113+
119114
this.generateReplacements(sourceFile, resolvedLocations, usageAnalysis, info, replacements);
120115
});
121116

122-
return confirmAsSerializable({replacements, removedIdentifiers, changedFiles});
117+
return confirmAsSerializable({replacements, removedImports, changedFiles});
123118
}
124119

125120
override async migrate(globalData: CompilationUnitData) {
@@ -130,34 +125,10 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
130125
unitA: CompilationUnitData,
131126
unitB: CompilationUnitData,
132127
): Promise<Serializable<CompilationUnitData>> {
133-
const combinedReplacements: Replacement[] = [];
134-
const combinedRemovedIdentifiers: NodeID[] = [];
135-
const seenReplacements = new Set<string>();
136-
const seenIdentifiers = new Set<NodeID>();
137-
const changedFileIds = new Set<ProjectFileID>();
138-
139-
[unitA, unitB].forEach((unit) => {
140-
for (const replacement of unit.replacements) {
141-
const key = this.getReplacementID(replacement);
142-
changedFileIds.add(replacement.projectFile.id);
143-
if (!seenReplacements.has(key)) {
144-
seenReplacements.add(key);
145-
combinedReplacements.push(replacement);
146-
}
147-
}
148-
149-
for (const identifier of unit.removedIdentifiers) {
150-
if (!seenIdentifiers.has(identifier)) {
151-
seenIdentifiers.add(identifier);
152-
combinedRemovedIdentifiers.push(identifier);
153-
}
154-
}
155-
});
156-
157128
return confirmAsSerializable({
158-
replacements: combinedReplacements,
159-
removedIdentifiers: combinedRemovedIdentifiers,
160-
changedFiles: changedFileIds.size,
129+
replacements: [...unitA.replacements, ...unitB.replacements],
130+
removedImports: unitA.removedImports + unitB.removedImports,
131+
changedFiles: unitA.changedFiles + unitB.changedFiles,
161132
});
162133
}
163134

@@ -170,21 +141,15 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
170141
override async stats(globalMetadata: CompilationUnitData): Promise<MigrationStats> {
171142
return {
172143
counters: {
173-
removedImports: globalMetadata.removedIdentifiers.length,
144+
removedImports: globalMetadata.removedImports,
174145
changedFiles: globalMetadata.changedFiles,
175146
},
176147
};
177148
}
178149

179-
/** Gets an ID that can be used to look up a node based on its location. */
180-
private getNodeID(start: number, length: number): NodeID {
181-
return `${start}/${length}` as NodeID;
182-
}
183-
184-
/** Gets a unique ID for a replacement. */
185-
private getReplacementID(replacement: Replacement): string {
186-
const {position, end, toInsert} = replacement.update.data;
187-
return replacement.projectFile.id + '/' + position + '/' + end + '/' + toInsert;
150+
/** Gets a key that can be used to look up a node based on its location. */
151+
private getNodeKey(start: number, length: number): string {
152+
return `${start}/${length}`;
188153
}
189154

190155
/**
@@ -215,7 +180,7 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
215180
return;
216181
}
217182

218-
if (locations.has(this.getNodeID(node.getStart(), node.getWidth()))) {
183+
if (locations.has(this.getNodeKey(node.getStart(), node.getWidth()))) {
219184
// When the entire array needs to be cleared, the diagnostic is
220185
// reported on the property assignment, rather than an array element.
221186
if (
@@ -226,15 +191,15 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
226191
result.fullRemovals.add(parent.initializer);
227192
parent.initializer.elements.forEach((element) => {
228193
if (ts.isIdentifier(element)) {
229-
result.allRemovedIdentifiers.add(element);
194+
result.allRemovedIdentifiers.add(element.text);
230195
}
231196
});
232197
} else if (ts.isArrayLiteralExpression(parent)) {
233198
if (!result.partialRemovals.has(parent)) {
234199
result.partialRemovals.set(parent, new Set());
235200
}
236201
result.partialRemovals.get(parent)!.add(node);
237-
result.allRemovedIdentifiers.add(node);
202+
result.allRemovedIdentifiers.add(node.text);
238203
}
239204
}
240205
};
@@ -365,13 +330,8 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
365330
names.forEach((symbolName, localName) => {
366331
// Note that in the `identifierCounts` lookup both zero and undefined
367332
// are valid and mean that the identifiers isn't being used anymore.
368-
if (!identifierCounts.get(localName)) {
369-
for (const identifier of allRemovedIdentifiers) {
370-
if (identifier.text === localName) {
371-
importManager.removeImport(sourceFile, symbolName, moduleName);
372-
break;
373-
}
374-
}
333+
if (allRemovedIdentifiers.has(localName) && !identifierCounts.get(localName)) {
334+
importManager.removeImport(sourceFile, symbolName, moduleName);
375335
}
376336
});
377337
});

packages/core/schematics/test/cleanup_unused_imports_migration_spec.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ describe('cleanup unused imports schematic', () => {
1919
let tree: UnitTestTree;
2020
let tmpDirPath: string;
2121
let previousWorkingDir: string;
22-
let logs: string[];
2322

2423
function writeFile(filePath: string, contents: string) {
2524
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
@@ -37,7 +36,6 @@ describe('cleanup unused imports schematic', () => {
3736
runner = new SchematicTestRunner('test', runfiles.resolvePackageRelative('../collection.json'));
3837
host = new TempScopedNodeJsSyncHost();
3938
tree = new UnitTestTree(new HostTree(host));
40-
logs = [];
4139

4240
writeFile('/tsconfig.json', '{}');
4341
writeFile(
@@ -50,7 +48,6 @@ describe('cleanup unused imports schematic', () => {
5048

5149
previousWorkingDir = shx.pwd();
5250
tmpDirPath = getSystemPath(host.root);
53-
runner.logger.subscribe((log) => logs.push(log.message));
5451

5552
// Switch into the temporary directory path. This allows us to run
5653
// the schematic against our custom unit test tree.
@@ -95,7 +92,6 @@ describe('cleanup unused imports schematic', () => {
9592

9693
await runMigration();
9794

98-
expect(logs.pop()).toBe('Removed 2 imports in 1 file');
9995
expect(stripWhitespace(tree.readContent('comp.ts'))).toBe(
10096
stripWhitespace(`
10197
import {Component} from '@angular/core';
@@ -127,7 +123,6 @@ describe('cleanup unused imports schematic', () => {
127123

128124
await runMigration();
129125

130-
expect(logs.pop()).toBe('Removed 3 imports in 1 file');
131126
expect(stripWhitespace(tree.readContent('comp.ts'))).toBe(
132127
stripWhitespace(`
133128
import {Component} from '@angular/core';
@@ -158,7 +153,6 @@ describe('cleanup unused imports schematic', () => {
158153

159154
await runMigration();
160155

161-
expect(logs.pop()).toBe('Removed 2 imports in 1 file');
162156
expect(stripWhitespace(tree.readContent('comp.ts'))).toBe(
163157
stripWhitespace(`
164158
import {Component} from '@angular/core';
@@ -196,7 +190,6 @@ describe('cleanup unused imports schematic', () => {
196190

197191
await runMigration();
198192

199-
expect(logs.pop()).toBe('Removed 1 import in 1 file');
200193
expect(stripWhitespace(tree.readContent('comp.ts'))).toBe(
201194
stripWhitespace(`
202195
import {Component} from '@angular/core';
@@ -233,7 +226,6 @@ describe('cleanup unused imports schematic', () => {
233226

234227
await runMigration();
235228

236-
expect(logs.pop()).toBe('Schematic could not find unused imports in the project');
237229
expect(tree.readContent('comp.ts')).toBe(initialContent);
238230
});
239231

@@ -250,7 +242,6 @@ describe('cleanup unused imports schematic', () => {
250242

251243
await runMigration();
252244

253-
expect(logs.pop()).toBe('Schematic could not find unused imports in the project');
254245
expect(tree.readContent('comp.ts')).toBe(initialContent);
255246
});
256247

@@ -283,7 +274,6 @@ describe('cleanup unused imports schematic', () => {
283274

284275
await runMigration();
285276

286-
expect(logs.pop()).toBe('Removed 2 imports in 1 file');
287277
expect(stripWhitespace(tree.readContent('comp.ts'))).toBe(
288278
stripWhitespace(`
289279
import {Component} from '@angular/core';

packages/core/schematics/utils/tsurge/helpers/angular_devkit/run_in_devkit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {ProjectRootRelativePath} from '../../project_paths';
1717
import {ProgramInfo} from '../../program_info';
1818
import {getProjectTsConfigPaths} from '../../../../utils/project_tsconfig_paths';
1919
import ts from 'typescript';
20+
import {MigrationStats} from '../../base_migration';
2021

2122
export enum MigrationStage {
2223
/** The migration is analyzing an entrypoint */

0 commit comments

Comments
 (0)