@@ -13,6 +13,7 @@ import {
13
13
MigrationStats ,
14
14
ProgramInfo ,
15
15
projectFile ,
16
+ ProjectFileID ,
16
17
Replacement ,
17
18
Serializable ,
18
19
TextUpdate ,
@@ -28,8 +29,8 @@ export interface CompilationUnitData {
28
29
/** Text changes that should be performed. */
29
30
replacements : Replacement [ ] ;
30
31
31
- /** Total number of imports that were removed . */
32
- removedImports : number ;
32
+ /** Identifiers that have been removed from each file . */
33
+ removedIdentifiers : NodeID [ ] ;
33
34
34
35
/** Total number of files that were changed. */
35
36
changedFiles : number ;
@@ -44,7 +45,7 @@ interface RemovalLocations {
44
45
partialRemovals : Map < ts . ArrayLiteralExpression , Set < ts . Expression > > ;
45
46
46
47
/** Text of all identifiers that have been removed. */
47
- allRemovedIdentifiers : Set < string > ;
48
+ allRemovedIdentifiers : Set < ts . Identifier > ;
48
49
}
49
50
50
51
/** Tracks how identifiers are used across a single file. */
@@ -60,6 +61,9 @@ interface UsageAnalysis {
60
61
identifierCounts : Map < string , number > ;
61
62
}
62
63
64
+ /** ID of a node based on its location. */
65
+ type NodeID = string & { __nodeID : true } ;
66
+
63
67
/** Migration that cleans up unused imports from a project. */
64
68
export class UnusedImportsMigration extends TsurgeFunnelMigration <
65
69
CompilationUnitData ,
@@ -81,7 +85,7 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
81
85
override async analyze ( info : ProgramInfo ) : Promise < Serializable < CompilationUnitData > > {
82
86
const nodePositions = new Map < ts . SourceFile , Set < string > > ( ) ;
83
87
const replacements : Replacement [ ] = [ ] ;
84
- let removedImports = 0 ;
88
+ const removedIdentifiers : NodeID [ ] = [ ] ;
85
89
let changedFiles = 0 ;
86
90
87
91
info . ngCompiler ?. getDiagnostics ( ) . forEach ( ( diag ) => {
@@ -94,7 +98,7 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
94
98
if ( ! nodePositions . has ( diag . file ) ) {
95
99
nodePositions . set ( diag . file , new Set ( ) ) ;
96
100
}
97
- nodePositions . get ( diag . file ) ! . add ( this . getNodeKey ( diag . start , diag . length ) ) ;
101
+ nodePositions . get ( diag . file ) ! . add ( this . getNodeID ( diag . start , diag . length ) ) ;
98
102
}
99
103
} ) ;
100
104
@@ -103,14 +107,15 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
103
107
const usageAnalysis = this . analyzeUsages ( sourceFile , resolvedLocations ) ;
104
108
105
109
if ( resolvedLocations . allRemovedIdentifiers . size > 0 ) {
106
- removedImports += resolvedLocations . allRemovedIdentifiers . size ;
107
110
changedFiles ++ ;
111
+ resolvedLocations . allRemovedIdentifiers . forEach ( ( identifier ) => {
112
+ removedIdentifiers . push ( this . getNodeID ( identifier . getStart ( ) , identifier . getWidth ( ) ) ) ;
113
+ } ) ;
108
114
}
109
-
110
115
this . generateReplacements ( sourceFile , resolvedLocations , usageAnalysis , info , replacements ) ;
111
116
} ) ;
112
117
113
- return confirmAsSerializable ( { replacements, removedImports , changedFiles} ) ;
118
+ return confirmAsSerializable ( { replacements, removedIdentifiers , changedFiles} ) ;
114
119
}
115
120
116
121
override async migrate ( globalData : CompilationUnitData ) {
@@ -121,10 +126,34 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
121
126
unitA : CompilationUnitData ,
122
127
unitB : CompilationUnitData ,
123
128
) : Promise < Serializable < CompilationUnitData > > {
129
+ const combinedReplacements : Replacement [ ] = [ ] ;
130
+ const combinedRemovedIdentifiers : NodeID [ ] = [ ] ;
131
+ const seenReplacements = new Set < string > ( ) ;
132
+ const seenIdentifiers = new Set < NodeID > ( ) ;
133
+ const changedFileIds = new Set < ProjectFileID > ( ) ;
134
+
135
+ [ unitA , unitB ] . forEach ( ( unit ) => {
136
+ for ( const replacement of unit . replacements ) {
137
+ const key = this . getReplacementID ( replacement ) ;
138
+ changedFileIds . add ( replacement . projectFile . id ) ;
139
+ if ( ! seenReplacements . has ( key ) ) {
140
+ seenReplacements . add ( key ) ;
141
+ combinedReplacements . push ( replacement ) ;
142
+ }
143
+ }
144
+
145
+ for ( const identifier of unit . removedIdentifiers ) {
146
+ if ( ! seenIdentifiers . has ( identifier ) ) {
147
+ seenIdentifiers . add ( identifier ) ;
148
+ combinedRemovedIdentifiers . push ( identifier ) ;
149
+ }
150
+ }
151
+ } ) ;
152
+
124
153
return confirmAsSerializable ( {
125
- replacements : [ ... unitA . replacements , ... unitB . replacements ] ,
126
- removedImports : unitA . removedImports + unitB . removedImports ,
127
- changedFiles : unitA . changedFiles + unitB . changedFiles ,
154
+ replacements : combinedReplacements ,
155
+ removedIdentifiers : combinedRemovedIdentifiers ,
156
+ changedFiles : changedFileIds . size ,
128
157
} ) ;
129
158
}
130
159
@@ -137,15 +166,21 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
137
166
override async stats ( globalMetadata : CompilationUnitData ) : Promise < MigrationStats > {
138
167
return {
139
168
counters : {
140
- removedImports : globalMetadata . removedImports ,
169
+ removedImports : globalMetadata . removedIdentifiers . length ,
141
170
changedFiles : globalMetadata . changedFiles ,
142
171
} ,
143
172
} ;
144
173
}
145
174
146
- /** Gets a key that can be used to look up a node based on its location. */
147
- private getNodeKey ( start : number , length : number ) : string {
148
- return `${ start } /${ length } ` ;
175
+ /** Gets an ID that can be used to look up a node based on its location. */
176
+ private getNodeID ( start : number , length : number ) : NodeID {
177
+ return `${ start } /${ length } ` as NodeID ;
178
+ }
179
+
180
+ /** Gets a unique ID for a replacement. */
181
+ private getReplacementID ( replacement : Replacement ) : string {
182
+ const { position, end, toInsert} = replacement . update . data ;
183
+ return replacement . projectFile . id + '/' + position + '/' + end + '/' + toInsert ;
149
184
}
150
185
151
186
/**
@@ -176,7 +211,7 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
176
211
return ;
177
212
}
178
213
179
- if ( locations . has ( this . getNodeKey ( node . getStart ( ) , node . getWidth ( ) ) ) ) {
214
+ if ( locations . has ( this . getNodeID ( node . getStart ( ) , node . getWidth ( ) ) ) ) {
180
215
// When the entire array needs to be cleared, the diagnostic is
181
216
// reported on the property assignment, rather than an array element.
182
217
if (
@@ -187,15 +222,15 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
187
222
result . fullRemovals . add ( parent . initializer ) ;
188
223
parent . initializer . elements . forEach ( ( element ) => {
189
224
if ( ts . isIdentifier ( element ) ) {
190
- result . allRemovedIdentifiers . add ( element . text ) ;
225
+ result . allRemovedIdentifiers . add ( element ) ;
191
226
}
192
227
} ) ;
193
228
} else if ( ts . isArrayLiteralExpression ( parent ) ) {
194
229
if ( ! result . partialRemovals . has ( parent ) ) {
195
230
result . partialRemovals . set ( parent , new Set ( ) ) ;
196
231
}
197
232
result . partialRemovals . get ( parent ) ! . add ( node ) ;
198
- result . allRemovedIdentifiers . add ( node . text ) ;
233
+ result . allRemovedIdentifiers . add ( node ) ;
199
234
}
200
235
}
201
236
} ;
@@ -326,8 +361,13 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
326
361
names . forEach ( ( symbolName , localName ) => {
327
362
// Note that in the `identifierCounts` lookup both zero and undefined
328
363
// are valid and mean that the identifiers isn't being used anymore.
329
- if ( allRemovedIdentifiers . has ( localName ) && ! identifierCounts . get ( localName ) ) {
330
- importManager . removeImport ( sourceFile , symbolName , moduleName ) ;
364
+ if ( ! identifierCounts . get ( localName ) ) {
365
+ for ( const identifier of allRemovedIdentifiers ) {
366
+ if ( identifier . text === localName ) {
367
+ importManager . removeImport ( sourceFile , symbolName , moduleName ) ;
368
+ break ;
369
+ }
370
+ }
331
371
}
332
372
} ) ;
333
373
} ) ;
0 commit comments