@@ -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. */
6763export 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 } ) ;
0 commit comments