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