@@ -118,6 +118,7 @@ import {
118118 skipAlias ,
119119 some ,
120120 sort ,
121+ SortKind ,
121122 SourceFile ,
122123 stableSort ,
123124 startsWith ,
@@ -164,15 +165,14 @@ registerCodeFix({
164165 const { errorCode, preferences, sourceFile, span, program } = context ;
165166 const info = getFixInfos ( context , errorCode , span . start , /*useAutoImportProvider*/ true ) ;
166167 if ( ! info ) return undefined ;
167- const quotePreference = getQuotePreference ( sourceFile , preferences ) ;
168168 return info . map ( ( { fix, symbolName, errorIdentifierText } ) => codeActionForFix (
169169 context ,
170170 sourceFile ,
171171 symbolName ,
172172 fix ,
173173 /*includeSymbolNameInDescription*/ symbolName !== errorIdentifierText ,
174- quotePreference ,
175- program . getCompilerOptions ( ) ) ) ;
174+ program . getCompilerOptions ( ) ,
175+ preferences ) ) ;
176176 } ,
177177 fixIds : [ importFixId ] ,
178178 getAllCodeActions : context => {
@@ -358,7 +358,8 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
358358 importClauseOrBindingPattern ,
359359 defaultImport ,
360360 arrayFrom ( namedImports . entries ( ) , ( [ name , addAsTypeOnly ] ) => ( { addAsTypeOnly, name } ) ) ,
361- compilerOptions ) ;
361+ compilerOptions ,
362+ preferences ) ;
362363 } ) ;
363364
364365 let newDeclarations : AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement [ ] | undefined ;
@@ -516,7 +517,8 @@ export function getImportCompletionAction(
516517 symbolName ,
517518 fix ,
518519 /*includeSymbolNameInDescription*/ false ,
519- getQuotePreference ( sourceFile , preferences ) , compilerOptions ) )
520+ compilerOptions ,
521+ preferences ) )
520522 } ;
521523}
522524
@@ -526,7 +528,7 @@ export function getPromoteTypeOnlyCompletionAction(sourceFile: SourceFile, symbo
526528 const symbolName = single ( getSymbolNamesToImport ( sourceFile , program . getTypeChecker ( ) , symbolToken , compilerOptions ) ) ;
527529 const fix = getTypeOnlyPromotionFix ( sourceFile , symbolToken , symbolName , program ) ;
528530 const includeSymbolNameInDescription = symbolName !== symbolToken . text ;
529- return fix && codeFixActionToCodeAction ( codeActionForFix ( { host, formatContext, preferences } , sourceFile , symbolName , fix , includeSymbolNameInDescription , QuotePreference . Double , compilerOptions ) ) ;
531+ return fix && codeFixActionToCodeAction ( codeActionForFix ( { host, formatContext, preferences } , sourceFile , symbolName , fix , includeSymbolNameInDescription , compilerOptions , preferences ) ) ;
530532}
531533
532534function getImportFixForSymbol ( sourceFile : SourceFile , exportInfos : readonly SymbolExportInfo [ ] , moduleSymbol : Symbol , program : Program , useNamespaceInfo : { position : number , symbolName : string } | undefined , isValidTypeOnlyUseSite : boolean , useRequire : boolean , host : LanguageServiceHost , preferences : UserPreferences ) {
@@ -1176,14 +1178,15 @@ function getExportEqualsImportKind(importingFile: SourceFile, compilerOptions: C
11761178 return allowSyntheticDefaults ? ImportKind . Default : ImportKind . CommonJS ;
11771179}
11781180
1179- function codeActionForFix ( context : textChanges . TextChangesContext , sourceFile : SourceFile , symbolName : string , fix : ImportFix , includeSymbolNameInDescription : boolean , quotePreference : QuotePreference , compilerOptions : CompilerOptions ) : CodeFixAction {
1181+ function codeActionForFix ( context : textChanges . TextChangesContext , sourceFile : SourceFile , symbolName : string , fix : ImportFix , includeSymbolNameInDescription : boolean , compilerOptions : CompilerOptions , preferences : UserPreferences ) : CodeFixAction {
11801182 let diag ! : DiagnosticAndArguments ;
11811183 const changes = textChanges . ChangeTracker . with ( context , tracker => {
1182- diag = codeActionForFixWorker ( tracker , sourceFile , symbolName , fix , includeSymbolNameInDescription , quotePreference , compilerOptions ) ;
1184+ diag = codeActionForFixWorker ( tracker , sourceFile , symbolName , fix , includeSymbolNameInDescription , compilerOptions , preferences ) ;
11831185 } ) ;
11841186 return createCodeFixAction ( importFixName , changes , diag , importFixId , Diagnostics . Add_all_missing_imports ) ;
11851187}
1186- function codeActionForFixWorker ( changes : textChanges . ChangeTracker , sourceFile : SourceFile , symbolName : string , fix : ImportFix , includeSymbolNameInDescription : boolean , quotePreference : QuotePreference , compilerOptions : CompilerOptions ) : DiagnosticAndArguments {
1188+ function codeActionForFixWorker ( changes : textChanges . ChangeTracker , sourceFile : SourceFile , symbolName : string , fix : ImportFix , includeSymbolNameInDescription : boolean , compilerOptions : CompilerOptions , preferences : UserPreferences ) : DiagnosticAndArguments {
1189+ const quotePreference = getQuotePreference ( sourceFile , preferences ) ;
11871190 switch ( fix . kind ) {
11881191 case ImportFixKind . UseNamespace :
11891192 addNamespaceQualifier ( changes , sourceFile , fix ) ;
@@ -1199,7 +1202,8 @@ function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile:
11991202 importClauseOrBindingPattern ,
12001203 importKind === ImportKind . Default ? { name : symbolName , addAsTypeOnly } : undefined ,
12011204 importKind === ImportKind . Named ? [ { name : symbolName , addAsTypeOnly } ] : emptyArray ,
1202- compilerOptions ) ;
1205+ compilerOptions ,
1206+ preferences ) ;
12031207 const moduleSpecifierWithoutQuotes = stripQuotes ( moduleSpecifier ) ;
12041208 return includeSymbolNameInDescription
12051209 ? [ Diagnostics . Import_0_from_1 , symbolName , moduleSpecifierWithoutQuotes ]
@@ -1240,10 +1244,11 @@ function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaratio
12401244 switch ( aliasDeclaration . kind ) {
12411245 case SyntaxKind . ImportSpecifier :
12421246 if ( aliasDeclaration . isTypeOnly ) {
1243- if ( aliasDeclaration . parent . elements . length > 1 && OrganizeImports . importSpecifiersAreSorted ( aliasDeclaration . parent . elements ) ) {
1247+ const sortKind = OrganizeImports . detectImportSpecifierSorting ( aliasDeclaration . parent . elements ) ;
1248+ if ( aliasDeclaration . parent . elements . length > 1 && sortKind ) {
12441249 changes . delete ( sourceFile , aliasDeclaration ) ;
12451250 const newSpecifier = factory . updateImportSpecifier ( aliasDeclaration , /*isTypeOnly*/ false , aliasDeclaration . propertyName , aliasDeclaration . name ) ;
1246- const insertionIndex = OrganizeImports . getImportSpecifierInsertionIndex ( aliasDeclaration . parent . elements , newSpecifier ) ;
1251+ const insertionIndex = OrganizeImports . getImportSpecifierInsertionIndex ( aliasDeclaration . parent . elements , newSpecifier , sortKind === SortKind . CaseInsensitive ) ;
12471252 changes . insertImportSpecifierAtIndex ( sourceFile , newSpecifier , aliasDeclaration . parent , insertionIndex ) ;
12481253 }
12491254 else {
@@ -1274,7 +1279,7 @@ function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaratio
12741279 if ( convertExistingToTypeOnly ) {
12751280 const namedImports = tryCast ( importClause . namedBindings , isNamedImports ) ;
12761281 if ( namedImports && namedImports . elements . length > 1 ) {
1277- if ( OrganizeImports . importSpecifiersAreSorted ( namedImports . elements ) &&
1282+ if ( OrganizeImports . detectImportSpecifierSorting ( namedImports . elements ) &&
12781283 aliasDeclaration . kind === SyntaxKind . ImportSpecifier &&
12791284 namedImports . elements . indexOf ( aliasDeclaration ) !== 0
12801285 ) {
@@ -1300,6 +1305,7 @@ function doAddExistingFix(
13001305 defaultImport : Import | undefined ,
13011306 namedImports : readonly Import [ ] ,
13021307 compilerOptions : CompilerOptions ,
1308+ preferences : UserPreferences ,
13031309) : void {
13041310 if ( clause . kind === SyntaxKind . ObjectBindingPattern ) {
13051311 if ( defaultImport ) {
@@ -1327,21 +1333,45 @@ function doAddExistingFix(
13271333 }
13281334
13291335 if ( namedImports . length ) {
1336+ // sort case sensitivity:
1337+ // - if the user preference is explicit, use that
1338+ // - otherwise, if there are enough existing import specifiers in this import to detect unambiguously, use that
1339+ // - otherwise, detect from other imports in the file
1340+ let ignoreCaseForSorting : boolean | undefined ;
1341+ if ( typeof preferences . organizeImportsIgnoreCase === "boolean" ) {
1342+ ignoreCaseForSorting = preferences . organizeImportsIgnoreCase ;
1343+ }
1344+ else if ( existingSpecifiers ) {
1345+ const targetImportSorting = OrganizeImports . detectImportSpecifierSorting ( existingSpecifiers ) ;
1346+ if ( targetImportSorting !== SortKind . Both ) {
1347+ ignoreCaseForSorting = targetImportSorting === SortKind . CaseInsensitive ;
1348+ }
1349+ }
1350+ if ( ignoreCaseForSorting === undefined ) {
1351+ ignoreCaseForSorting = OrganizeImports . detectSorting ( sourceFile ) === SortKind . CaseInsensitive ;
1352+ }
1353+
13301354 const newSpecifiers = stableSort (
13311355 namedImports . map ( namedImport => factory . createImportSpecifier (
13321356 ( ! clause . isTypeOnly || promoteFromTypeOnly ) && needsTypeOnly ( namedImport ) ,
13331357 /*propertyName*/ undefined ,
13341358 factory . createIdentifier ( namedImport . name ) ) ) ,
1335- OrganizeImports . compareImportOrExportSpecifiers ) ;
1336-
1337- if ( existingSpecifiers ?. length && OrganizeImports . importSpecifiersAreSorted ( existingSpecifiers ) ) {
1359+ ( s1 , s2 ) => OrganizeImports . compareImportOrExportSpecifiers ( s1 , s2 , ignoreCaseForSorting ) ) ;
1360+
1361+ // The sorting preference computed earlier may or may not have validated that these particular
1362+ // import specifiers are sorted. If they aren't, `getImportSpecifierInsertionIndex` will return
1363+ // nonsense. So if there are existing specifiers, even if we know the sorting preference, we
1364+ // need to ensure that the existing specifiers are sorted according to the preference in order
1365+ // to do a sorted insertion.
1366+ const specifierSort = existingSpecifiers ?. length && OrganizeImports . detectImportSpecifierSorting ( existingSpecifiers ) ;
1367+ if ( specifierSort && ! ( ignoreCaseForSorting && specifierSort === SortKind . CaseSensitive ) ) {
13381368 for ( const spec of newSpecifiers ) {
13391369 // Organize imports puts type-only import specifiers last, so if we're
13401370 // adding a non-type-only specifier and converting all the other ones to
13411371 // type-only, there's no need to ask for the insertion index - it's 0.
13421372 const insertionIndex = convertExistingToTypeOnly && ! spec . isTypeOnly
13431373 ? 0
1344- : OrganizeImports . getImportSpecifierInsertionIndex ( existingSpecifiers , spec ) ;
1374+ : OrganizeImports . getImportSpecifierInsertionIndex ( existingSpecifiers , spec , ignoreCaseForSorting ) ;
13451375 changes . insertImportSpecifierAtIndex ( sourceFile , spec , clause . namedBindings as NamedImports , insertionIndex ) ;
13461376 }
13471377 }
0 commit comments