@@ -118,6 +118,7 @@ import {
118
118
skipAlias ,
119
119
some ,
120
120
sort ,
121
+ SortKind ,
121
122
SourceFile ,
122
123
stableSort ,
123
124
startsWith ,
@@ -164,15 +165,14 @@ registerCodeFix({
164
165
const { errorCode, preferences, sourceFile, span, program } = context ;
165
166
const info = getFixInfos ( context , errorCode , span . start , /*useAutoImportProvider*/ true ) ;
166
167
if ( ! info ) return undefined ;
167
- const quotePreference = getQuotePreference ( sourceFile , preferences ) ;
168
168
return info . map ( ( { fix, symbolName, errorIdentifierText } ) => codeActionForFix (
169
169
context ,
170
170
sourceFile ,
171
171
symbolName ,
172
172
fix ,
173
173
/*includeSymbolNameInDescription*/ symbolName !== errorIdentifierText ,
174
- quotePreference ,
175
- program . getCompilerOptions ( ) ) ) ;
174
+ program . getCompilerOptions ( ) ,
175
+ preferences ) ) ;
176
176
} ,
177
177
fixIds : [ importFixId ] ,
178
178
getAllCodeActions : context => {
@@ -358,7 +358,8 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
358
358
importClauseOrBindingPattern ,
359
359
defaultImport ,
360
360
arrayFrom ( namedImports . entries ( ) , ( [ name , addAsTypeOnly ] ) => ( { addAsTypeOnly, name } ) ) ,
361
- compilerOptions ) ;
361
+ compilerOptions ,
362
+ preferences ) ;
362
363
} ) ;
363
364
364
365
let newDeclarations : AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement [ ] | undefined ;
@@ -516,7 +517,8 @@ export function getImportCompletionAction(
516
517
symbolName ,
517
518
fix ,
518
519
/*includeSymbolNameInDescription*/ false ,
519
- getQuotePreference ( sourceFile , preferences ) , compilerOptions ) )
520
+ compilerOptions ,
521
+ preferences ) )
520
522
} ;
521
523
}
522
524
@@ -526,7 +528,7 @@ export function getPromoteTypeOnlyCompletionAction(sourceFile: SourceFile, symbo
526
528
const symbolName = single ( getSymbolNamesToImport ( sourceFile , program . getTypeChecker ( ) , symbolToken , compilerOptions ) ) ;
527
529
const fix = getTypeOnlyPromotionFix ( sourceFile , symbolToken , symbolName , program ) ;
528
530
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 ) ) ;
530
532
}
531
533
532
534
function 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
1176
1178
return allowSyntheticDefaults ? ImportKind . Default : ImportKind . CommonJS ;
1177
1179
}
1178
1180
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 {
1180
1182
let diag ! : DiagnosticAndArguments ;
1181
1183
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 ) ;
1183
1185
} ) ;
1184
1186
return createCodeFixAction ( importFixName , changes , diag , importFixId , Diagnostics . Add_all_missing_imports ) ;
1185
1187
}
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 ) ;
1187
1190
switch ( fix . kind ) {
1188
1191
case ImportFixKind . UseNamespace :
1189
1192
addNamespaceQualifier ( changes , sourceFile , fix ) ;
@@ -1199,7 +1202,8 @@ function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile:
1199
1202
importClauseOrBindingPattern ,
1200
1203
importKind === ImportKind . Default ? { name : symbolName , addAsTypeOnly } : undefined ,
1201
1204
importKind === ImportKind . Named ? [ { name : symbolName , addAsTypeOnly } ] : emptyArray ,
1202
- compilerOptions ) ;
1205
+ compilerOptions ,
1206
+ preferences ) ;
1203
1207
const moduleSpecifierWithoutQuotes = stripQuotes ( moduleSpecifier ) ;
1204
1208
return includeSymbolNameInDescription
1205
1209
? [ Diagnostics . Import_0_from_1 , symbolName , moduleSpecifierWithoutQuotes ]
@@ -1240,10 +1244,11 @@ function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaratio
1240
1244
switch ( aliasDeclaration . kind ) {
1241
1245
case SyntaxKind . ImportSpecifier :
1242
1246
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 ) {
1244
1249
changes . delete ( sourceFile , aliasDeclaration ) ;
1245
1250
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 ) ;
1247
1252
changes . insertImportSpecifierAtIndex ( sourceFile , newSpecifier , aliasDeclaration . parent , insertionIndex ) ;
1248
1253
}
1249
1254
else {
@@ -1274,7 +1279,7 @@ function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaratio
1274
1279
if ( convertExistingToTypeOnly ) {
1275
1280
const namedImports = tryCast ( importClause . namedBindings , isNamedImports ) ;
1276
1281
if ( namedImports && namedImports . elements . length > 1 ) {
1277
- if ( OrganizeImports . importSpecifiersAreSorted ( namedImports . elements ) &&
1282
+ if ( OrganizeImports . detectImportSpecifierSorting ( namedImports . elements ) &&
1278
1283
aliasDeclaration . kind === SyntaxKind . ImportSpecifier &&
1279
1284
namedImports . elements . indexOf ( aliasDeclaration ) !== 0
1280
1285
) {
@@ -1300,6 +1305,7 @@ function doAddExistingFix(
1300
1305
defaultImport : Import | undefined ,
1301
1306
namedImports : readonly Import [ ] ,
1302
1307
compilerOptions : CompilerOptions ,
1308
+ preferences : UserPreferences ,
1303
1309
) : void {
1304
1310
if ( clause . kind === SyntaxKind . ObjectBindingPattern ) {
1305
1311
if ( defaultImport ) {
@@ -1327,21 +1333,45 @@ function doAddExistingFix(
1327
1333
}
1328
1334
1329
1335
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
+
1330
1354
const newSpecifiers = stableSort (
1331
1355
namedImports . map ( namedImport => factory . createImportSpecifier (
1332
1356
( ! clause . isTypeOnly || promoteFromTypeOnly ) && needsTypeOnly ( namedImport ) ,
1333
1357
/*propertyName*/ undefined ,
1334
1358
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 ) ) {
1338
1368
for ( const spec of newSpecifiers ) {
1339
1369
// Organize imports puts type-only import specifiers last, so if we're
1340
1370
// adding a non-type-only specifier and converting all the other ones to
1341
1371
// type-only, there's no need to ask for the insertion index - it's 0.
1342
1372
const insertionIndex = convertExistingToTypeOnly && ! spec . isTypeOnly
1343
1373
? 0
1344
- : OrganizeImports . getImportSpecifierInsertionIndex ( existingSpecifiers , spec ) ;
1374
+ : OrganizeImports . getImportSpecifierInsertionIndex ( existingSpecifiers , spec , ignoreCaseForSorting ) ;
1345
1375
changes . insertImportSpecifierAtIndex ( sourceFile , spec , clause . namedBindings as NamedImports , insertionIndex ) ;
1346
1376
}
1347
1377
}
0 commit comments