@@ -59,6 +59,7 @@ import {
59
59
Scanner ,
60
60
setEmitFlags ,
61
61
some ,
62
+ sort ,
62
63
SortKind ,
63
64
SourceFile ,
64
65
stableSort ,
@@ -322,96 +323,111 @@ function coalesceImportsWorker(importGroup: readonly ImportDeclaration[], compar
322
323
return importGroup ;
323
324
}
324
325
325
- const { importWithoutClause, typeOnlyImports, regularImports } = getCategorizedImports ( importGroup ) ;
326
- const coalescedImports : ImportDeclaration [ ] = [ ] ;
327
-
328
- if ( importWithoutClause ) {
329
- coalescedImports . push ( importWithoutClause ) ;
330
- }
331
-
332
- for ( const group of [ regularImports , typeOnlyImports ] ) {
333
- const isTypeOnly = group === typeOnlyImports ;
334
- const { defaultImports, namespaceImports, namedImports } = group ;
335
- // Normally, we don't combine default and namespace imports, but it would be silly to
336
- // produce two import declarations in this special case.
337
- if ( ! isTypeOnly && defaultImports . length === 1 && namespaceImports . length === 1 && namedImports . length === 0 ) {
338
- // Add the namespace import to the existing default ImportDeclaration.
339
- const defaultImport = defaultImports [ 0 ] ;
340
- coalescedImports . push (
341
- updateImportDeclarationAndClause ( defaultImport , defaultImport . importClause . name , namespaceImports [ 0 ] . importClause . namedBindings ) ,
342
- ) ;
343
-
344
- continue ;
326
+ const importGroupsByAttributes = groupBy ( importGroup , decl => {
327
+ if ( decl . attributes ) {
328
+ let attrs = decl . attributes . token + " " ;
329
+ for ( const x of sort ( decl . attributes . elements , ( x , y ) => compareStringsCaseSensitive ( x . name . text , y . name . text ) ) ) {
330
+ attrs += x . name . text + ":" ;
331
+ attrs += isStringLiteralLike ( x . value ) ? `"${ x . value . text } "` : x . value . getText ( ) + " " ;
332
+ }
333
+ return attrs ;
345
334
}
335
+ return "" ;
336
+ } ) ;
346
337
347
- const sortedNamespaceImports = stableSort ( namespaceImports , ( i1 , i2 ) => comparer ( i1 . importClause . namedBindings . name . text , i2 . importClause . namedBindings . name . text ) ) ;
338
+ const coalescedImports : ImportDeclaration [ ] = [ ] ;
339
+ for ( const attribute in importGroupsByAttributes ) {
340
+ const importGroupSameAttrs = importGroupsByAttributes [ attribute ] as ImportDeclaration [ ] ;
341
+ const { importWithoutClause, typeOnlyImports, regularImports } = getCategorizedImports ( importGroupSameAttrs ) ;
342
+
343
+ if ( importWithoutClause ) {
344
+ coalescedImports . push ( importWithoutClause ) ;
345
+ }
346
+
347
+ for ( const group of [ regularImports , typeOnlyImports ] ) {
348
+ const isTypeOnly = group === typeOnlyImports ;
349
+ const { defaultImports, namespaceImports, namedImports } = group ;
350
+ // Normally, we don't combine default and namespace imports, but it would be silly to
351
+ // produce two import declarations in this special case.
352
+ if ( ! isTypeOnly && defaultImports . length === 1 && namespaceImports . length === 1 && namedImports . length === 0 ) {
353
+ // Add the namespace import to the existing default ImportDeclaration.
354
+ const defaultImport = defaultImports [ 0 ] ;
355
+ coalescedImports . push (
356
+ updateImportDeclarationAndClause ( defaultImport , defaultImport . importClause . name , namespaceImports [ 0 ] . importClause . namedBindings ) ,
357
+ ) ;
348
358
349
- for ( const namespaceImport of sortedNamespaceImports ) {
350
- // Drop the name, if any
351
- coalescedImports . push (
352
- updateImportDeclarationAndClause ( namespaceImport , /*name*/ undefined , namespaceImport . importClause . namedBindings ) ,
353
- ) ;
354
- }
359
+ continue ;
360
+ }
355
361
356
- const firstDefaultImport = firstOrUndefined ( defaultImports ) ;
357
- const firstNamedImport = firstOrUndefined ( namedImports ) ;
358
- const importDecl = firstDefaultImport ?? firstNamedImport ;
359
- if ( ! importDecl ) {
360
- continue ;
361
- }
362
+ const sortedNamespaceImports = stableSort ( namespaceImports , ( i1 , i2 ) => comparer ( i1 . importClause . namedBindings . name . text , i2 . importClause . namedBindings . name . text ) ) ;
362
363
363
- let newDefaultImport : Identifier | undefined ;
364
- const newImportSpecifiers : ImportSpecifier [ ] = [ ] ;
365
- if ( defaultImports . length === 1 ) {
366
- newDefaultImport = defaultImports [ 0 ] . importClause . name ;
367
- }
368
- else {
369
- for ( const defaultImport of defaultImports ) {
370
- newImportSpecifiers . push (
371
- factory . createImportSpecifier ( /*isTypeOnly*/ false , factory . createIdentifier ( "default" ) , defaultImport . importClause . name ) ,
364
+ for ( const namespaceImport of sortedNamespaceImports ) {
365
+ // Drop the name, if any
366
+ coalescedImports . push (
367
+ updateImportDeclarationAndClause ( namespaceImport , /*name*/ undefined , namespaceImport . importClause . namedBindings ) ,
372
368
) ;
373
369
}
374
- }
375
370
376
- newImportSpecifiers . push ( ...getNewImportSpecifiers ( namedImports ) ) ;
371
+ const firstDefaultImport = firstOrUndefined ( defaultImports ) ;
372
+ const firstNamedImport = firstOrUndefined ( namedImports ) ;
373
+ const importDecl = firstDefaultImport ?? firstNamedImport ;
374
+ if ( ! importDecl ) {
375
+ continue ;
376
+ }
377
377
378
- const sortedImportSpecifiers = factory . createNodeArray (
379
- sortSpecifiers ( newImportSpecifiers , comparer , preferences ) ,
380
- firstNamedImport ?. importClause . namedBindings . elements . hasTrailingComma ,
381
- ) ;
378
+ let newDefaultImport : Identifier | undefined ;
379
+ const newImportSpecifiers : ImportSpecifier [ ] = [ ] ;
380
+ if ( defaultImports . length === 1 ) {
381
+ newDefaultImport = defaultImports [ 0 ] . importClause . name ;
382
+ }
383
+ else {
384
+ for ( const defaultImport of defaultImports ) {
385
+ newImportSpecifiers . push (
386
+ factory . createImportSpecifier ( /*isTypeOnly*/ false , factory . createIdentifier ( "default" ) , defaultImport . importClause . name ) ,
387
+ ) ;
388
+ }
389
+ }
382
390
383
- const newNamedImports = sortedImportSpecifiers . length === 0
384
- ? newDefaultImport
385
- ? undefined
386
- : factory . createNamedImports ( emptyArray )
387
- : firstNamedImport
388
- ? factory . updateNamedImports ( firstNamedImport . importClause . namedBindings , sortedImportSpecifiers )
389
- : factory . createNamedImports ( sortedImportSpecifiers ) ;
390
-
391
- if (
392
- sourceFile &&
393
- newNamedImports &&
394
- firstNamedImport ?. importClause . namedBindings &&
395
- ! rangeIsOnSingleLine ( firstNamedImport . importClause . namedBindings , sourceFile )
396
- ) {
397
- setEmitFlags ( newNamedImports , EmitFlags . MultiLine ) ;
398
- }
391
+ newImportSpecifiers . push ( ...getNewImportSpecifiers ( namedImports ) ) ;
399
392
400
- // Type-only imports are not allowed to mix default, namespace, and named imports in any combination.
401
- // We could rewrite a default import as a named import (`import { default as name }`), but we currently
402
- // choose not to as a stylistic preference.
403
- if ( isTypeOnly && newDefaultImport && newNamedImports ) {
404
- coalescedImports . push (
405
- updateImportDeclarationAndClause ( importDecl , newDefaultImport , /*namedBindings*/ undefined ) ,
406
- ) ;
407
- coalescedImports . push (
408
- updateImportDeclarationAndClause ( firstNamedImport ?? importDecl , /*name*/ undefined , newNamedImports ) ,
409
- ) ;
410
- }
411
- else {
412
- coalescedImports . push (
413
- updateImportDeclarationAndClause ( importDecl , newDefaultImport , newNamedImports ) ,
393
+ const sortedImportSpecifiers = factory . createNodeArray (
394
+ sortSpecifiers ( newImportSpecifiers , comparer , preferences ) ,
395
+ firstNamedImport ?. importClause . namedBindings . elements . hasTrailingComma ,
414
396
) ;
397
+
398
+ const newNamedImports = sortedImportSpecifiers . length === 0
399
+ ? newDefaultImport
400
+ ? undefined
401
+ : factory . createNamedImports ( emptyArray )
402
+ : firstNamedImport
403
+ ? factory . updateNamedImports ( firstNamedImport . importClause . namedBindings , sortedImportSpecifiers )
404
+ : factory . createNamedImports ( sortedImportSpecifiers ) ;
405
+
406
+ if (
407
+ sourceFile &&
408
+ newNamedImports &&
409
+ firstNamedImport ?. importClause . namedBindings &&
410
+ ! rangeIsOnSingleLine ( firstNamedImport . importClause . namedBindings , sourceFile )
411
+ ) {
412
+ setEmitFlags ( newNamedImports , EmitFlags . MultiLine ) ;
413
+ }
414
+
415
+ // Type-only imports are not allowed to mix default, namespace, and named imports in any combination.
416
+ // We could rewrite a default import as a named import (`import { default as name }`), but we currently
417
+ // choose not to as a stylistic preference.
418
+ if ( isTypeOnly && newDefaultImport && newNamedImports ) {
419
+ coalescedImports . push (
420
+ updateImportDeclarationAndClause ( importDecl , newDefaultImport , /*namedBindings*/ undefined ) ,
421
+ ) ;
422
+ coalescedImports . push (
423
+ updateImportDeclarationAndClause ( firstNamedImport ?? importDecl , /*name*/ undefined , newNamedImports ) ,
424
+ ) ;
425
+ }
426
+ else {
427
+ coalescedImports . push (
428
+ updateImportDeclarationAndClause ( importDecl , newDefaultImport , newNamedImports ) ,
429
+ ) ;
430
+ }
415
431
}
416
432
}
417
433
0 commit comments