@@ -17,9 +17,10 @@ import {
17
17
TsurgeFunnelMigration ,
18
18
} from '../../utils/tsurge' ;
19
19
import { ErrorCode , FileSystem , ngErrorCode } from '@angular/compiler-cli' ;
20
- import { DiagnosticCategoryLabel , NgCompilerOptions } from '@angular/compiler-cli/src/ngtsc/core/api' ;
20
+ import { DiagnosticCategoryLabel } from '@angular/compiler-cli/src/ngtsc/core/api' ;
21
21
import { ImportManager } from '@angular/compiler-cli/private/migrations' ;
22
22
import { applyImportManagerChanges } from '../../utils/tsurge/helpers/apply_import_manager' ;
23
+ import { getLeadingLineWhitespaceOfNode } from '../../utils/tsurge/helpers/ast/leading_space' ;
23
24
24
25
/** Data produced by the migration for each compilation unit. */
25
26
export interface CompilationUnitData {
@@ -283,6 +284,7 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
283
284
const { fullRemovals, partialRemovals, allRemovedIdentifiers} = removalLocations ;
284
285
const { importedSymbols, identifierCounts} = usages ;
285
286
const importManager = new ImportManager ( ) ;
287
+ const sourceText = sourceFile . getFullText ( ) ;
286
288
287
289
// Replace full arrays with empty ones. This allows preserves more of the user's formatting.
288
290
fullRemovals . forEach ( ( node ) => {
@@ -299,22 +301,15 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
299
301
} ) ;
300
302
301
303
// Filter out the unused identifiers from an array.
302
- partialRemovals . forEach ( ( toRemove , node ) => {
303
- const newNode = ts . factory . updateArrayLiteralExpression (
304
- node ,
305
- node . elements . filter ( ( el ) => ! toRemove . has ( el ) ) ,
306
- ) ;
307
-
308
- replacements . push (
309
- new Replacement (
310
- projectFile ( sourceFile , info ) ,
311
- new TextUpdate ( {
312
- position : node . getStart ( ) ,
313
- end : node . getEnd ( ) ,
314
- toInsert : this . printer . printNode ( ts . EmitHint . Unspecified , newNode , sourceFile ) ,
315
- } ) ,
316
- ) ,
317
- ) ;
304
+ partialRemovals . forEach ( ( toRemove , parent ) => {
305
+ toRemove . forEach ( ( node ) => {
306
+ replacements . push (
307
+ new Replacement (
308
+ projectFile ( sourceFile , info ) ,
309
+ getArrayElementRemovalUpdate ( node , parent , sourceText ) ,
310
+ ) ,
311
+ ) ;
312
+ } ) ;
318
313
} ) ;
319
314
320
315
// Attempt to clean up unused import declarations. Note that this isn't foolproof, because we
@@ -336,3 +331,49 @@ export class UnusedImportsMigration extends TsurgeFunnelMigration<
336
331
applyImportManagerChanges ( importManager , replacements , [ sourceFile ] , info ) ;
337
332
}
338
333
}
334
+
335
+ /** Generates a `TextUpdate` for the removal of an array element. */
336
+ function getArrayElementRemovalUpdate (
337
+ node : ts . Expression ,
338
+ parent : ts . ArrayLiteralExpression ,
339
+ sourceText : string ,
340
+ ) : TextUpdate {
341
+ let position = node . getStart ( ) ;
342
+ let end = node . getEnd ( ) ;
343
+ let toInsert = '' ;
344
+ const whitespaceOrLineFeed = / \s / ;
345
+
346
+ // Usually the way we'd remove the nodes would be to recreate the `parent` while excluding
347
+ // the nodes that should be removed. The problem with this is that it'll strip out comments
348
+ // inside the array which can have special meaning internally. We work around it by removing
349
+ // only the node's own offsets. This comes with another problem in that it won't remove the commas
350
+ // that separate array elements which in turn can look weird if left in place (e.g.
351
+ // `[One, Two, Three, Four]` can turn into `[One,,Four]`). To account for them, we start with the
352
+ // node's end offset and then expand it to include trailing commas, whitespace and line breaks.
353
+ for ( let i = end ; i < sourceText . length ; i ++ ) {
354
+ if ( sourceText [ i ] === ',' || whitespaceOrLineFeed . test ( sourceText [ i ] ) ) {
355
+ end ++ ;
356
+ } else {
357
+ break ;
358
+ }
359
+ }
360
+
361
+ // If we're removing the last element in the array, adjust the starting offset so that
362
+ // it includes the previous comma on the same line. This avoids turning something like
363
+ // `[One, Two, Three]` into `[One,]`. We only do this within the same like, because
364
+ // trailing comma at the end of the line is fine.
365
+ if ( parent . elements [ parent . elements . length - 1 ] === node ) {
366
+ for ( let i = position - 1 ; i >= 0 ; i -- ) {
367
+ if ( sourceText [ i ] === ',' || sourceText [ i ] === ' ' ) {
368
+ position -- ;
369
+ } else {
370
+ break ;
371
+ }
372
+ }
373
+
374
+ // Replace the node with its leading whitespace to preserve the formatting.
375
+ toInsert = getLeadingLineWhitespaceOfNode ( node ) ;
376
+ }
377
+
378
+ return new TextUpdate ( { position, end, toInsert} ) ;
379
+ }
0 commit comments