@@ -80,6 +80,7 @@ import {
80
80
isStringANonContextualKeyword ,
81
81
isStringLiteral ,
82
82
isStringLiteralLike ,
83
+ isTypeOnlyImportDeclaration ,
83
84
isTypeOnlyImportOrExportDeclaration ,
84
85
isUMDExportSymbol ,
85
86
isValidTypeOnlyAliasUseSite ,
@@ -359,7 +360,6 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
359
360
importClauseOrBindingPattern ,
360
361
defaultImport ,
361
362
arrayFrom ( namedImports . entries ( ) , ( [ name , addAsTypeOnly ] ) => ( { addAsTypeOnly, name } ) ) ,
362
- compilerOptions ,
363
363
preferences ) ;
364
364
} ) ;
365
365
@@ -690,7 +690,24 @@ function getAddAsTypeOnly(
690
690
}
691
691
692
692
function tryAddToExistingImport ( existingImports : readonly FixAddToExistingImportInfo [ ] , isValidTypeOnlyUseSite : boolean , checker : TypeChecker , compilerOptions : CompilerOptions ) : FixAddToExistingImport | undefined {
693
- return firstDefined ( existingImports , ( { declaration, importKind, symbol, targetFlags } ) : FixAddToExistingImport | undefined => {
693
+ let best : FixAddToExistingImport | undefined ;
694
+ for ( const existingImport of existingImports ) {
695
+ const fix = getAddToExistingImportFix ( existingImport ) ;
696
+ if ( ! fix ) continue ;
697
+ const isTypeOnly = isTypeOnlyImportDeclaration ( fix . importClauseOrBindingPattern ) ;
698
+ if (
699
+ fix . addAsTypeOnly !== AddAsTypeOnly . NotAllowed && isTypeOnly ||
700
+ fix . addAsTypeOnly === AddAsTypeOnly . NotAllowed && ! isTypeOnly
701
+ ) {
702
+ // Give preference to putting types in existing type-only imports and avoiding conversions
703
+ // of import statements to/from type-only.
704
+ return fix ;
705
+ }
706
+ best ??= fix ;
707
+ }
708
+ return best ;
709
+
710
+ function getAddToExistingImportFix ( { declaration, importKind, symbol, targetFlags } : FixAddToExistingImportInfo ) : FixAddToExistingImport | undefined {
694
711
if ( importKind === ImportKind . CommonJS || importKind === ImportKind . Namespace || declaration . kind === SyntaxKind . ImportEqualsDeclaration ) {
695
712
// These kinds of imports are not combinable with anything
696
713
return undefined ;
@@ -703,25 +720,32 @@ function tryAddToExistingImport(existingImports: readonly FixAddToExistingImport
703
720
}
704
721
705
722
const { importClause } = declaration ;
706
- if ( ! importClause || ! isStringLiteralLike ( declaration . moduleSpecifier ) ) return undefined ;
723
+ if ( ! importClause || ! isStringLiteralLike ( declaration . moduleSpecifier ) ) {
724
+ return undefined ;
725
+ }
707
726
const { name, namedBindings } = importClause ;
708
727
// A type-only import may not have both a default and named imports, so the only way a name can
709
728
// be added to an existing type-only import is adding a named import to existing named bindings.
710
- if ( importClause . isTypeOnly && ! ( importKind === ImportKind . Named && namedBindings ) ) return undefined ;
729
+ if ( importClause . isTypeOnly && ! ( importKind === ImportKind . Named && namedBindings ) ) {
730
+ return undefined ;
731
+ }
711
732
712
733
// N.B. we don't have to figure out whether to use the main program checker
713
734
// or the AutoImportProvider checker because we're adding to an existing import; the existence of
714
735
// the import guarantees the symbol came from the main program.
715
736
const addAsTypeOnly = getAddAsTypeOnly ( isValidTypeOnlyUseSite , /*isForNewImportDeclaration*/ false , symbol , targetFlags , checker , compilerOptions ) ;
716
737
717
738
if ( importKind === ImportKind . Default && (
718
- name || // Cannot add a default import to a declaration that already has one
739
+ name || // Cannot add a default import to a declaration that already has one
719
740
addAsTypeOnly === AddAsTypeOnly . Required && namedBindings // Cannot add a default import as type-only if the import already has named bindings
720
- ) ) return undefined ;
721
- if (
722
- importKind === ImportKind . Named &&
723
- namedBindings ?. kind === SyntaxKind . NamespaceImport // Cannot add a named import to a declaration that has a namespace import
724
- ) return undefined ;
741
+ ) ) {
742
+ return undefined ;
743
+ }
744
+ if ( importKind === ImportKind . Named &&
745
+ namedBindings ?. kind === SyntaxKind . NamespaceImport // Cannot add a named import to a declaration that has a namespace import
746
+ ) {
747
+ return undefined ;
748
+ }
725
749
726
750
return {
727
751
kind : ImportFixKind . AddToExisting ,
@@ -730,7 +754,7 @@ function tryAddToExistingImport(existingImports: readonly FixAddToExistingImport
730
754
moduleSpecifier : declaration . moduleSpecifier . text ,
731
755
addAsTypeOnly,
732
756
} ;
733
- } ) ;
757
+ }
734
758
}
735
759
736
760
function createExistingImportMap ( checker : TypeChecker , importingFile : SourceFile , compilerOptions : CompilerOptions ) {
@@ -1235,7 +1259,6 @@ function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile:
1235
1259
importClauseOrBindingPattern ,
1236
1260
importKind === ImportKind . Default ? { name : symbolName , addAsTypeOnly } : undefined ,
1237
1261
importKind === ImportKind . Named ? [ { name : symbolName , addAsTypeOnly } ] : emptyArray ,
1238
- compilerOptions ,
1239
1262
preferences ) ;
1240
1263
const moduleSpecifierWithoutQuotes = stripQuotes ( moduleSpecifier ) ;
1241
1264
return includeSymbolNameInDescription
@@ -1349,7 +1372,6 @@ function doAddExistingFix(
1349
1372
clause : ImportClause | ObjectBindingPattern ,
1350
1373
defaultImport : Import | undefined ,
1351
1374
namedImports : readonly Import [ ] ,
1352
- compilerOptions : CompilerOptions ,
1353
1375
preferences : UserPreferences ,
1354
1376
) : void {
1355
1377
if ( clause . kind === SyntaxKind . ObjectBindingPattern ) {
@@ -1364,13 +1386,6 @@ function doAddExistingFix(
1364
1386
1365
1387
const promoteFromTypeOnly = clause . isTypeOnly && some ( [ defaultImport , ...namedImports ] , i => i ?. addAsTypeOnly === AddAsTypeOnly . NotAllowed ) ;
1366
1388
const existingSpecifiers = clause . namedBindings && tryCast ( clause . namedBindings , isNamedImports ) ?. elements ;
1367
- // If we are promoting from a type-only import and `--isolatedModules` and `--preserveValueImports`
1368
- // are enabled, we need to make every existing import specifier type-only. It may be possible that
1369
- // some of them don't strictly need to be marked type-only (if they have a value meaning and are
1370
- // never used in an emitting position). These are allowed to be imported without being type-only,
1371
- // but the user has clearly already signified that they don't need them to be present at runtime
1372
- // by placing them in a type-only import. So, just mark each specifier as type-only.
1373
- const convertExistingToTypeOnly = promoteFromTypeOnly && importNameElisionDisabled ( compilerOptions ) ;
1374
1389
1375
1390
if ( defaultImport ) {
1376
1391
Debug . assert ( ! clause . name , "Cannot add a default import to an import clause that already has one" ) ;
@@ -1415,7 +1430,7 @@ function doAddExistingFix(
1415
1430
// Organize imports puts type-only import specifiers last, so if we're
1416
1431
// adding a non-type-only specifier and converting all the other ones to
1417
1432
// type-only, there's no need to ask for the insertion index - it's 0.
1418
- const insertionIndex = convertExistingToTypeOnly && ! spec . isTypeOnly
1433
+ const insertionIndex = promoteFromTypeOnly && ! spec . isTypeOnly
1419
1434
? 0
1420
1435
: OrganizeImports . getImportSpecifierInsertionIndex ( existingSpecifiers , spec , comparer ) ;
1421
1436
changes . insertImportSpecifierAtIndex ( sourceFile , spec , clause . namedBindings as NamedImports , insertionIndex ) ;
@@ -1441,7 +1456,11 @@ function doAddExistingFix(
1441
1456
1442
1457
if ( promoteFromTypeOnly ) {
1443
1458
changes . delete ( sourceFile , getTypeKeywordOfTypeOnlyImport ( clause , sourceFile ) ) ;
1444
- if ( convertExistingToTypeOnly && existingSpecifiers ) {
1459
+ if ( existingSpecifiers ) {
1460
+ // We used to convert existing specifiers to type-only only if compiler options indicated that
1461
+ // would be meaningful (see the `importNameElisionDisabled` utility function), but user
1462
+ // feedback indicated a preference for preserving the type-onlyness of existing specifiers
1463
+ // regardless of whether it would make a difference in emit.
1445
1464
for ( const specifier of existingSpecifiers ) {
1446
1465
changes . insertModifierBefore ( sourceFile , SyntaxKind . TypeKeyword , specifier ) ;
1447
1466
}
0 commit comments