@@ -80,6 +80,7 @@ import {
8080 isStringANonContextualKeyword ,
8181 isStringLiteral ,
8282 isStringLiteralLike ,
83+ isTypeOnlyImportDeclaration ,
8384 isTypeOnlyImportOrExportDeclaration ,
8485 isUMDExportSymbol ,
8586 isValidTypeOnlyAliasUseSite ,
@@ -359,7 +360,6 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
359360 importClauseOrBindingPattern ,
360361 defaultImport ,
361362 arrayFrom ( namedImports . entries ( ) , ( [ name , addAsTypeOnly ] ) => ( { addAsTypeOnly, name } ) ) ,
362- compilerOptions ,
363363 preferences ) ;
364364 } ) ;
365365
@@ -690,7 +690,24 @@ function getAddAsTypeOnly(
690690}
691691
692692function 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 {
694711 if ( importKind === ImportKind . CommonJS || importKind === ImportKind . Namespace || declaration . kind === SyntaxKind . ImportEqualsDeclaration ) {
695712 // These kinds of imports are not combinable with anything
696713 return undefined ;
@@ -703,25 +720,32 @@ function tryAddToExistingImport(existingImports: readonly FixAddToExistingImport
703720 }
704721
705722 const { importClause } = declaration ;
706- if ( ! importClause || ! isStringLiteralLike ( declaration . moduleSpecifier ) ) return undefined ;
723+ if ( ! importClause || ! isStringLiteralLike ( declaration . moduleSpecifier ) ) {
724+ return undefined ;
725+ }
707726 const { name, namedBindings } = importClause ;
708727 // A type-only import may not have both a default and named imports, so the only way a name can
709728 // 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+ }
711732
712733 // N.B. we don't have to figure out whether to use the main program checker
713734 // or the AutoImportProvider checker because we're adding to an existing import; the existence of
714735 // the import guarantees the symbol came from the main program.
715736 const addAsTypeOnly = getAddAsTypeOnly ( isValidTypeOnlyUseSite , /*isForNewImportDeclaration*/ false , symbol , targetFlags , checker , compilerOptions ) ;
716737
717738 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
719740 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+ }
725749
726750 return {
727751 kind : ImportFixKind . AddToExisting ,
@@ -730,7 +754,7 @@ function tryAddToExistingImport(existingImports: readonly FixAddToExistingImport
730754 moduleSpecifier : declaration . moduleSpecifier . text ,
731755 addAsTypeOnly,
732756 } ;
733- } ) ;
757+ }
734758}
735759
736760function createExistingImportMap ( checker : TypeChecker , importingFile : SourceFile , compilerOptions : CompilerOptions ) {
@@ -1235,7 +1259,6 @@ function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile:
12351259 importClauseOrBindingPattern ,
12361260 importKind === ImportKind . Default ? { name : symbolName , addAsTypeOnly } : undefined ,
12371261 importKind === ImportKind . Named ? [ { name : symbolName , addAsTypeOnly } ] : emptyArray ,
1238- compilerOptions ,
12391262 preferences ) ;
12401263 const moduleSpecifierWithoutQuotes = stripQuotes ( moduleSpecifier ) ;
12411264 return includeSymbolNameInDescription
@@ -1349,7 +1372,6 @@ function doAddExistingFix(
13491372 clause : ImportClause | ObjectBindingPattern ,
13501373 defaultImport : Import | undefined ,
13511374 namedImports : readonly Import [ ] ,
1352- compilerOptions : CompilerOptions ,
13531375 preferences : UserPreferences ,
13541376) : void {
13551377 if ( clause . kind === SyntaxKind . ObjectBindingPattern ) {
@@ -1364,13 +1386,6 @@ function doAddExistingFix(
13641386
13651387 const promoteFromTypeOnly = clause . isTypeOnly && some ( [ defaultImport , ...namedImports ] , i => i ?. addAsTypeOnly === AddAsTypeOnly . NotAllowed ) ;
13661388 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 ) ;
13741389
13751390 if ( defaultImport ) {
13761391 Debug . assert ( ! clause . name , "Cannot add a default import to an import clause that already has one" ) ;
@@ -1415,7 +1430,7 @@ function doAddExistingFix(
14151430 // Organize imports puts type-only import specifiers last, so if we're
14161431 // adding a non-type-only specifier and converting all the other ones to
14171432 // 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
14191434 ? 0
14201435 : OrganizeImports . getImportSpecifierInsertionIndex ( existingSpecifiers , spec , comparer ) ;
14211436 changes . insertImportSpecifierAtIndex ( sourceFile , spec , clause . namedBindings as NamedImports , insertionIndex ) ;
@@ -1441,7 +1456,11 @@ function doAddExistingFix(
14411456
14421457 if ( promoteFromTypeOnly ) {
14431458 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.
14451464 for ( const specifier of existingSpecifiers ) {
14461465 changes . insertModifierBefore ( sourceFile , SyntaxKind . TypeKeyword , specifier ) ;
14471466 }
0 commit comments