Add option for organize imports case sensitivity (#51733)

* Add ignore case option to organizeImports

* Adopt in auto-imports, use same case-insensitive comparison as eslint

* Fix build/lint

* Mark functions internal

* Update affected auto import test

* Update API baseline

* Update protocol

* Update API baseline

* Short-circuit comparisons that have already failed
This commit is contained in:
Andrew Branch
2022-12-13 14:37:29 -08:00
committed by GitHub
parent a5dde88dce
commit 4076ff8fd6
19 changed files with 430 additions and 109 deletions

View File

@@ -118,6 +118,7 @@ import {
skipAlias,
some,
sort,
SortKind,
SourceFile,
stableSort,
startsWith,
@@ -164,15 +165,14 @@ registerCodeFix({
const { errorCode, preferences, sourceFile, span, program } = context;
const info = getFixInfos(context, errorCode, span.start, /*useAutoImportProvider*/ true);
if (!info) return undefined;
const quotePreference = getQuotePreference(sourceFile, preferences);
return info.map(({ fix, symbolName, errorIdentifierText }) => codeActionForFix(
context,
sourceFile,
symbolName,
fix,
/*includeSymbolNameInDescription*/ symbolName !== errorIdentifierText,
quotePreference,
program.getCompilerOptions()));
program.getCompilerOptions(),
preferences));
},
fixIds: [importFixId],
getAllCodeActions: context => {
@@ -358,7 +358,8 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
importClauseOrBindingPattern,
defaultImport,
arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })),
compilerOptions);
compilerOptions,
preferences);
});
let newDeclarations: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[] | undefined;
@@ -516,7 +517,8 @@ export function getImportCompletionAction(
symbolName,
fix,
/*includeSymbolNameInDescription*/ false,
getQuotePreference(sourceFile, preferences), compilerOptions))
compilerOptions,
preferences))
};
}
@@ -526,7 +528,7 @@ export function getPromoteTypeOnlyCompletionAction(sourceFile: SourceFile, symbo
const symbolName = single(getSymbolNamesToImport(sourceFile, program.getTypeChecker(), symbolToken, compilerOptions));
const fix = getTypeOnlyPromotionFix(sourceFile, symbolToken, symbolName, program);
const includeSymbolNameInDescription = symbolName !== symbolToken.text;
return fix && codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, includeSymbolNameInDescription, QuotePreference.Double, compilerOptions));
return fix && codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, includeSymbolNameInDescription, compilerOptions, preferences));
}
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
return allowSyntheticDefaults ? ImportKind.Default : ImportKind.CommonJS;
}
function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: SourceFile, symbolName: string, fix: ImportFix, includeSymbolNameInDescription: boolean, quotePreference: QuotePreference, compilerOptions: CompilerOptions): CodeFixAction {
function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: SourceFile, symbolName: string, fix: ImportFix, includeSymbolNameInDescription: boolean, compilerOptions: CompilerOptions, preferences: UserPreferences): CodeFixAction {
let diag!: DiagnosticAndArguments;
const changes = textChanges.ChangeTracker.with(context, tracker => {
diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, includeSymbolNameInDescription, quotePreference, compilerOptions);
diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, includeSymbolNameInDescription, compilerOptions, preferences);
});
return createCodeFixAction(importFixName, changes, diag, importFixId, Diagnostics.Add_all_missing_imports);
}
function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, includeSymbolNameInDescription: boolean, quotePreference: QuotePreference, compilerOptions: CompilerOptions): DiagnosticAndArguments {
function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, includeSymbolNameInDescription: boolean, compilerOptions: CompilerOptions, preferences: UserPreferences): DiagnosticAndArguments {
const quotePreference = getQuotePreference(sourceFile, preferences);
switch (fix.kind) {
case ImportFixKind.UseNamespace:
addNamespaceQualifier(changes, sourceFile, fix);
@@ -1199,7 +1202,8 @@ function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile:
importClauseOrBindingPattern,
importKind === ImportKind.Default ? { name: symbolName, addAsTypeOnly } : undefined,
importKind === ImportKind.Named ? [{ name: symbolName, addAsTypeOnly }] : emptyArray,
compilerOptions);
compilerOptions,
preferences);
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
return includeSymbolNameInDescription
? [Diagnostics.Import_0_from_1, symbolName, moduleSpecifierWithoutQuotes]
@@ -1240,10 +1244,11 @@ function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaratio
switch (aliasDeclaration.kind) {
case SyntaxKind.ImportSpecifier:
if (aliasDeclaration.isTypeOnly) {
if (aliasDeclaration.parent.elements.length > 1 && OrganizeImports.importSpecifiersAreSorted(aliasDeclaration.parent.elements)) {
const sortKind = OrganizeImports.detectImportSpecifierSorting(aliasDeclaration.parent.elements);
if (aliasDeclaration.parent.elements.length > 1 && sortKind) {
changes.delete(sourceFile, aliasDeclaration);
const newSpecifier = factory.updateImportSpecifier(aliasDeclaration, /*isTypeOnly*/ false, aliasDeclaration.propertyName, aliasDeclaration.name);
const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(aliasDeclaration.parent.elements, newSpecifier);
const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(aliasDeclaration.parent.elements, newSpecifier, sortKind === SortKind.CaseInsensitive);
changes.insertImportSpecifierAtIndex(sourceFile, newSpecifier, aliasDeclaration.parent, insertionIndex);
}
else {
@@ -1274,7 +1279,7 @@ function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaratio
if (convertExistingToTypeOnly) {
const namedImports = tryCast(importClause.namedBindings, isNamedImports);
if (namedImports && namedImports.elements.length > 1) {
if (OrganizeImports.importSpecifiersAreSorted(namedImports.elements) &&
if (OrganizeImports.detectImportSpecifierSorting(namedImports.elements) &&
aliasDeclaration.kind === SyntaxKind.ImportSpecifier &&
namedImports.elements.indexOf(aliasDeclaration) !== 0
) {
@@ -1300,6 +1305,7 @@ function doAddExistingFix(
defaultImport: Import | undefined,
namedImports: readonly Import[],
compilerOptions: CompilerOptions,
preferences: UserPreferences,
): void {
if (clause.kind === SyntaxKind.ObjectBindingPattern) {
if (defaultImport) {
@@ -1327,21 +1333,45 @@ function doAddExistingFix(
}
if (namedImports.length) {
// sort case sensitivity:
// - if the user preference is explicit, use that
// - otherwise, if there are enough existing import specifiers in this import to detect unambiguously, use that
// - otherwise, detect from other imports in the file
let ignoreCaseForSorting: boolean | undefined;
if (typeof preferences.organizeImportsIgnoreCase === "boolean") {
ignoreCaseForSorting = preferences.organizeImportsIgnoreCase;
}
else if (existingSpecifiers) {
const targetImportSorting = OrganizeImports.detectImportSpecifierSorting(existingSpecifiers);
if (targetImportSorting !== SortKind.Both) {
ignoreCaseForSorting = targetImportSorting === SortKind.CaseInsensitive;
}
}
if (ignoreCaseForSorting === undefined) {
ignoreCaseForSorting = OrganizeImports.detectSorting(sourceFile) === SortKind.CaseInsensitive;
}
const newSpecifiers = stableSort(
namedImports.map(namedImport => factory.createImportSpecifier(
(!clause.isTypeOnly || promoteFromTypeOnly) && needsTypeOnly(namedImport),
/*propertyName*/ undefined,
factory.createIdentifier(namedImport.name))),
OrganizeImports.compareImportOrExportSpecifiers);
(s1, s2) => OrganizeImports.compareImportOrExportSpecifiers(s1, s2, ignoreCaseForSorting));
if (existingSpecifiers?.length && OrganizeImports.importSpecifiersAreSorted(existingSpecifiers)) {
// The sorting preference computed earlier may or may not have validated that these particular
// import specifiers are sorted. If they aren't, `getImportSpecifierInsertionIndex` will return
// nonsense. So if there are existing specifiers, even if we know the sorting preference, we
// need to ensure that the existing specifiers are sorted according to the preference in order
// to do a sorted insertion.
const specifierSort = existingSpecifiers?.length && OrganizeImports.detectImportSpecifierSorting(existingSpecifiers);
if (specifierSort && !(ignoreCaseForSorting && specifierSort === SortKind.CaseSensitive)) {
for (const spec of newSpecifiers) {
// Organize imports puts type-only import specifiers last, so if we're
// adding a non-type-only specifier and converting all the other ones to
// type-only, there's no need to ask for the insertion index - it's 0.
const insertionIndex = convertExistingToTypeOnly && !spec.isTypeOnly
? 0
: OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec);
: OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec, ignoreCaseForSorting);
changes.insertImportSpecifierAtIndex(sourceFile, spec, clause.namedBindings as NamedImports, insertionIndex);
}
}

View File

@@ -3,10 +3,12 @@ import {
arrayIsSorted,
binarySearch,
compareBooleans,
compareStringsCaseInsensitive,
compareStringsCaseInsensitiveEslintCompatible,
compareStringsCaseSensitive,
compareValues,
Comparison,
createScanner,
detectSortCaseSensitivity,
EmitFlags,
emptyArray,
ExportDeclaration,
@@ -14,6 +16,7 @@ import {
Expression,
factory,
FileTextChanges,
find,
FindAllReferences,
flatMap,
formatting,
@@ -39,6 +42,7 @@ import {
LanguageServiceHost,
length,
map,
memoizeWeak,
NamedImportBindings,
NamedImports,
NamespaceImport,
@@ -48,7 +52,7 @@ import {
Scanner,
setEmitFlags,
some,
SortedReadonlyArray,
SortKind,
SourceFile,
stableSort,
suppressLeadingTrivia,
@@ -81,22 +85,26 @@ export function organizeImports(
const shouldRemove = mode === OrganizeImportsMode.RemoveUnused || mode === OrganizeImportsMode.All;
const maybeRemove = shouldRemove ? removeUnusedImports : identity;
const maybeCoalesce = shouldCombine ? coalesceImports : identity;
// All of the old ImportDeclarations in the file, in syntactic order.
const topLevelImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration));
const ignoreCase = typeof preferences.organizeImportsIgnoreCase === "boolean"
? preferences.organizeImportsIgnoreCase
: shouldSort && detectSortingWorker(topLevelImportGroupDecls) === SortKind.CaseInsensitive;
const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => {
const processedDeclarations = maybeCoalesce(maybeRemove(importGroup, sourceFile, program), sourceFile);
const processedDeclarations = maybeCoalesce(maybeRemove(importGroup, sourceFile, program), ignoreCase, sourceFile);
return shouldSort
? stableSort(processedDeclarations, (s1, s2) => compareImportsOrRequireStatements(s1, s2))
: processedDeclarations;
};
// All of the old ImportDeclarations in the file, in syntactic order.
const topLevelImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration));
topLevelImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier));
// Exports are always used
if (mode !== OrganizeImportsMode.RemoveUnused) {
// All of the old ExportDeclarations in the file, in syntactic order.
const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration);
organizeImportsWorker(topLevelExportDecls, coalesceExports);
organizeImportsWorker(topLevelExportDecls, group => coalesceExports(group, ignoreCase));
}
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
@@ -108,7 +116,7 @@ export function organizeImports(
// Exports are always used
if (mode !== OrganizeImportsMode.RemoveUnused) {
const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration);
organizeImportsWorker(ambientModuleExportDecls, coalesceExports);
organizeImportsWorker(ambientModuleExportDecls, group => coalesceExports(group, ignoreCase));
}
}
@@ -130,13 +138,13 @@ export function organizeImports(
suppressLeadingTrivia(oldImportDecls[0]);
const oldImportGroups = shouldCombine
? group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier!)!)
? group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier)!)
: [oldImportDecls];
const sortedImportGroups = shouldSort
? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier))
: oldImportGroups;
const newImportDecls = flatMap(sortedImportGroups, importGroup =>
getExternalModuleName(importGroup[0].moduleSpecifier!)
getExternalModuleName(importGroup[0].moduleSpecifier)
? coalesce(importGroup)
: importGroup);
@@ -170,7 +178,7 @@ function groupImportsByNewlineContiguous(sourceFile: SourceFile, importDecls: Im
const groupImports: ImportDeclaration[][] = [];
let groupIndex = 0;
for (const topLevelImportDecl of importDecls) {
if (isNewGroup(sourceFile, topLevelImportDecl, scanner)) {
if (groupImports[groupIndex] && isNewGroup(sourceFile, topLevelImportDecl, scanner)) {
groupIndex++;
}
@@ -288,7 +296,7 @@ function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpe
&& moduleName.text === moduleSpecifierText);
}
function getExternalModuleName(specifier: Expression) {
function getExternalModuleName(specifier: Expression | undefined) {
return specifier !== undefined && isStringLiteralLike(specifier)
? specifier.text
: undefined;
@@ -300,13 +308,13 @@ function getExternalModuleName(specifier: Expression) {
*
* @internal
*/
export function coalesceImports(importGroup: readonly ImportDeclaration[], sourceFile?: SourceFile) {
export function coalesceImports(importGroup: readonly ImportDeclaration[], ignoreCase?: boolean, sourceFile?: SourceFile): readonly ImportDeclaration[] {
if (importGroup.length === 0) {
return importGroup;
}
const { importWithoutClause, typeOnlyImports, regularImports } = getCategorizedImports(importGroup);
const compareIdentifiers = ignoreCase ? compareIdentifiersCaseInsensitive : compareIdentifiersCaseSensitive;
const coalescedImports: ImportDeclaration[] = [];
if (importWithoutClause) {
@@ -355,7 +363,7 @@ export function coalesceImports(importGroup: readonly ImportDeclaration[], sourc
newImportSpecifiers.push(...getNewImportSpecifiers(namedImports));
const sortedImportSpecifiers = factory.createNodeArray(
sortSpecifiers(newImportSpecifiers),
sortSpecifiers(newImportSpecifiers, ignoreCase),
(namedImports[0]?.importClause!.namedBindings as NamedImports)?.elements.hasTrailingComma
);
@@ -454,7 +462,7 @@ function getCategorizedImports(importGroup: readonly ImportDeclaration[]) {
*
* @internal
*/
export function coalesceExports(exportGroup: readonly ExportDeclaration[]) {
export function coalesceExports(exportGroup: readonly ExportDeclaration[], ignoreCase: boolean) {
if (exportGroup.length === 0) {
return exportGroup;
}
@@ -474,7 +482,7 @@ export function coalesceExports(exportGroup: readonly ExportDeclaration[]) {
const newExportSpecifiers: ExportSpecifier[] = [];
newExportSpecifiers.push(...flatMap(exportGroup, i => i.exportClause && isNamedExports(i.exportClause) ? i.exportClause.elements : emptyArray));
const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers);
const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers, ignoreCase);
const exportDecl = exportGroup[0];
coalescedExports.push(
@@ -538,15 +546,22 @@ function updateImportDeclarationAndClause(
importDeclaration.assertClause);
}
function sortSpecifiers<T extends ImportOrExportSpecifier>(specifiers: readonly T[]) {
return stableSort(specifiers, compareImportOrExportSpecifiers);
function sortSpecifiers<T extends ImportOrExportSpecifier>(specifiers: readonly T[], ignoreCase?: boolean) {
return stableSort(specifiers, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, ignoreCase));
}
/** @internal */
export function compareImportOrExportSpecifiers<T extends ImportOrExportSpecifier>(s1: T, s2: T): Comparison {
return compareBooleans(s1.isTypeOnly, s2.isTypeOnly)
|| compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name)
|| compareIdentifiers(s1.name, s2.name);
export function compareImportOrExportSpecifiers<T extends ImportOrExportSpecifier>(s1: T, s2: T, ignoreCase?: boolean): Comparison {
const compareIdentifiers = ignoreCase ? compareIdentifiersCaseInsensitive : compareIdentifiersCaseSensitive;
return compareBooleans(s1.isTypeOnly, s2.isTypeOnly) || compareIdentifiers(s1.name, s2.name);
}
function compareIdentifiersCaseSensitive(s1: Identifier, s2: Identifier) {
return compareStringsCaseSensitive(s1.text, s2.text);
}
function compareIdentifiersCaseInsensitive(s1: Identifier, s2: Identifier) {
return compareStringsCaseInsensitiveEslintCompatible(s1.text, s2.text);
}
/**
@@ -554,16 +569,14 @@ export function compareImportOrExportSpecifiers<T extends ImportOrExportSpecifie
*
* @internal
*/
export function compareModuleSpecifiers(m1: Expression | undefined, m2: Expression | undefined) {
export function compareModuleSpecifiers(m1: Expression | undefined, m2: Expression | undefined, ignoreCase?: boolean) {
const name1 = m1 === undefined ? undefined : getExternalModuleName(m1);
const name2 = m2 === undefined ? undefined : getExternalModuleName(m2);
const compareStrings = ignoreCase ? compareStringsCaseInsensitiveEslintCompatible : compareStringsCaseSensitive;
return compareBooleans(name1 === undefined, name2 === undefined) ||
compareBooleans(isExternalModuleNameRelative(name1!), isExternalModuleNameRelative(name2!)) ||
compareStringsCaseInsensitive(name1!, name2!);
}
function compareIdentifiers(s1: Identifier, s2: Identifier) {
return compareStringsCaseInsensitive(s1.text, s2.text);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
compareStrings(name1!, name2!); // I don't know why eslint is wrong but this one is necessary
}
function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement): Expression | undefined {
@@ -578,30 +591,71 @@ function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement):
}
/** @internal */
export function importsAreSorted(imports: readonly AnyImportOrRequireStatement[]): imports is SortedReadonlyArray<AnyImportOrRequireStatement> {
return arrayIsSorted(imports, compareImportsOrRequireStatements);
export function detectSorting(sourceFile: SourceFile): SortKind {
return detectSortingWorker(
groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)));
}
function detectSortingWorker(importGroups: ImportDeclaration[][]): SortKind {
let sortState = SortKind.Both;
for (const importGroup of importGroups) {
// Check module specifiers
if (importGroup.length > 1) {
sortState &= detectSortCaseSensitivity(importGroup, /*useEslintOrdering*/ true, i => tryCast(i.moduleSpecifier, isStringLiteral)?.text ?? "");
if (!sortState) {
return sortState;
}
}
// Check import specifiers
const declarationWithNamedImports = find(
importGroup,
i => tryCast(i.importClause?.namedBindings, isNamedImports)?.elements.length! > 1);
if (declarationWithNamedImports) {
sortState &= detectImportSpecifierSorting((declarationWithNamedImports.importClause!.namedBindings as NamedImports).elements);
if (!sortState) {
return sortState;
}
}
// Quit as soon as we've disambiguated. There's a chance that something later will disagree with what we've
// found so far, but this function is only intended to infer a preference, not validate the whole file for
// consistent and correct sorting.
if (sortState !== SortKind.Both) {
return sortState;
}
}
return sortState;
}
/** @internal */
export function importSpecifiersAreSorted(imports: readonly ImportSpecifier[]): imports is SortedReadonlyArray<ImportSpecifier> {
return arrayIsSorted(imports, compareImportOrExportSpecifiers);
export function detectImportDeclarationSorting(imports: readonly AnyImportOrRequireStatement[]): SortKind {
return detectSortCaseSensitivity(imports, /*useEslintOrdering*/ true, s => getExternalModuleName(getModuleSpecifierExpression(s)) || "");
}
/** @internal */
export function getImportDeclarationInsertionIndex(sortedImports: SortedReadonlyArray<AnyImportOrRequireStatement>, newImport: AnyImportOrRequireStatement) {
const index = binarySearch(sortedImports, newImport, identity, compareImportsOrRequireStatements);
export const detectImportSpecifierSorting = memoizeWeak((specifiers: readonly ImportSpecifier[]): SortKind => {
if (!arrayIsSorted(specifiers, (s1, s2) => compareBooleans(s1.isTypeOnly, s2.isTypeOnly))) {
return SortKind.None;
}
return detectSortCaseSensitivity(specifiers, /*useEslintOrdering*/ true, specifier => specifier.name.text);
});
/** @internal */
export function getImportDeclarationInsertionIndex(sortedImports: readonly AnyImportOrRequireStatement[], newImport: AnyImportOrRequireStatement, ignoreCase?: boolean) {
const index = binarySearch(sortedImports, newImport, identity, (a, b) => compareImportsOrRequireStatements(a, b, ignoreCase));
return index < 0 ? ~index : index;
}
/** @internal */
export function getImportSpecifierInsertionIndex(sortedImports: SortedReadonlyArray<ImportSpecifier>, newImport: ImportSpecifier) {
const index = binarySearch(sortedImports, newImport, identity, compareImportOrExportSpecifiers);
export function getImportSpecifierInsertionIndex(sortedImports: readonly ImportSpecifier[], newImport: ImportSpecifier, ignoreCase?: boolean) {
const index = binarySearch(sortedImports, newImport, identity, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, ignoreCase));
return index < 0 ? ~index : index;
}
/** @internal */
export function compareImportsOrRequireStatements(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) {
return compareModuleSpecifiers(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2)) || compareImportKind(s1, s2);
export function compareImportsOrRequireStatements(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement, ignoreCase?: boolean) {
return compareModuleSpecifiers(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2), ignoreCase) || compareImportKind(s1, s2);
}
function compareImportKind(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) {

View File

@@ -2541,7 +2541,7 @@ export function insertImports(changes: textChanges.ChangeTracker, sourceFile: So
if (!existingImportStatements.length) {
changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween);
}
else if (existingImportStatements && OrganizeImports.importsAreSorted(existingImportStatements)) {
else if (existingImportStatements && OrganizeImports.detectImportDeclarationSorting(existingImportStatements)) {
for (const newImport of sortedNewImports) {
const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport);
if (insertionIndex === 0) {