mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
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:
parent
a5dde88dce
commit
4076ff8fd6
@ -944,16 +944,53 @@ export function sortAndDeduplicate<T>(array: readonly T[], comparer?: Comparer<T
|
||||
/** @internal */
|
||||
export function arrayIsSorted<T>(array: readonly T[], comparer: Comparer<T>) {
|
||||
if (array.length < 2) return true;
|
||||
let prevElement = array[0];
|
||||
for (const element of array.slice(1)) {
|
||||
if (comparer(prevElement, element) === Comparison.GreaterThan) {
|
||||
for (let i = 1, len = array.length; i < len; i++) {
|
||||
if (comparer(array[i - 1], array[i]) === Comparison.GreaterThan) {
|
||||
return false;
|
||||
}
|
||||
prevElement = element;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const enum SortKind {
|
||||
None = 0,
|
||||
CaseSensitive = 1 << 0,
|
||||
CaseInsensitive = 1 << 1,
|
||||
Both = CaseSensitive | CaseInsensitive,
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function detectSortCaseSensitivity(array: readonly string[], useEslintOrdering?: boolean): SortKind;
|
||||
/** @internal */
|
||||
export function detectSortCaseSensitivity<T>(array: readonly T[], useEslintOrdering: boolean, getString: (element: T) => string): SortKind;
|
||||
/** @internal */
|
||||
export function detectSortCaseSensitivity<T>(array: readonly T[], useEslintOrdering: boolean, getString?: (element: T) => string): SortKind {
|
||||
let kind = SortKind.Both;
|
||||
if (array.length < 2) return kind;
|
||||
const caseSensitiveComparer = getString
|
||||
? (a: T, b: T) => compareStringsCaseSensitive(getString(a), getString(b))
|
||||
: compareStringsCaseSensitive as (a: T | undefined, b: T | undefined) => Comparison;
|
||||
const compareCaseInsensitive = useEslintOrdering ? compareStringsCaseInsensitiveEslintCompatible : compareStringsCaseInsensitive;
|
||||
const caseInsensitiveComparer = getString
|
||||
? (a: T, b: T) => compareCaseInsensitive(getString(a), getString(b))
|
||||
: compareCaseInsensitive as (a: T | undefined, b: T | undefined) => Comparison;
|
||||
for (let i = 1, len = array.length; i < len; i++) {
|
||||
const prevElement = array[i - 1];
|
||||
const element = array[i];
|
||||
if (kind & SortKind.CaseSensitive && caseSensitiveComparer(prevElement, element) === Comparison.GreaterThan) {
|
||||
kind &= ~SortKind.CaseSensitive;
|
||||
}
|
||||
if (kind & SortKind.CaseInsensitive && caseInsensitiveComparer(prevElement, element) === Comparison.GreaterThan) {
|
||||
kind &= ~SortKind.CaseInsensitive;
|
||||
}
|
||||
if (kind === SortKind.None) {
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
return kind;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function arrayIsEqualTo<T>(array1: readonly T[] | undefined, array2: readonly T[] | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean {
|
||||
if (!array1 || !array2) {
|
||||
@ -2144,6 +2181,23 @@ export function memoizeOne<A extends string | number | boolean | undefined, T>(c
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of `memoize` that supports a single non-primitive argument, stored as keys of a WeakMap.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function memoizeWeak<A extends object, T>(callback: (arg: A) => T): (arg: A) => T {
|
||||
const map = new WeakMap<A, T>();
|
||||
return (arg: A) => {
|
||||
let value = map.get(arg);
|
||||
if (value === undefined && !map.has(arg)) {
|
||||
value = callback(arg);
|
||||
map.set(arg, value);
|
||||
}
|
||||
return value!;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* High-order function, composes functions. Note that functions are composed inside-out;
|
||||
* for example, `compose(a, b)` is the equivalent of `x => b(a(x))`.
|
||||
@ -2293,6 +2347,27 @@ export function compareStringsCaseInsensitive(a: string, b: string) {
|
||||
return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* `compareStringsCaseInsensitive` transforms letters to uppercase for unicode reasons,
|
||||
* while eslint's `sort-imports` rule transforms letters to lowercase. Which one you choose
|
||||
* affects the relative order of letters and ASCII characters 91-96, of which `_` is a
|
||||
* valid character in an identifier. So if we used `compareStringsCaseInsensitive` for
|
||||
* import sorting, TypeScript and eslint would disagree about the correct case-insensitive
|
||||
* sort order for `__String` and `Foo`. Since eslint's whole job is to create consistency
|
||||
* by enforcing nitpicky details like this, it makes way more sense for us to just adopt
|
||||
* their convention so users can have auto-imports without making eslint angry.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function compareStringsCaseInsensitiveEslintCompatible(a: string, b: string) {
|
||||
if (a === b) return Comparison.EqualTo;
|
||||
if (a === undefined) return Comparison.LessThan;
|
||||
if (b === undefined) return Comparison.GreaterThan;
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two strings using a case-sensitive ordinal comparison.
|
||||
*
|
||||
|
||||
@ -9740,6 +9740,7 @@ export interface UserPreferences {
|
||||
readonly includeInlayEnumMemberValueHints?: boolean;
|
||||
readonly allowRenameOfImportPath?: boolean;
|
||||
readonly autoImportFileExcludePatterns?: string[];
|
||||
readonly organizeImportsIgnoreCase?: "auto" | boolean;
|
||||
}
|
||||
|
||||
/** Represents a bigint literal value without requiring bigint support */
|
||||
|
||||
@ -535,8 +535,8 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
public verifyOrganizeImports(newContent: string, mode?: ts.OrganizeImportsMode) {
|
||||
const changes = this.languageService.organizeImports({ fileName: this.activeFile.fileName, type: "file", mode }, this.formatCodeSettings, ts.emptyOptions);
|
||||
public verifyOrganizeImports(newContent: string, mode?: ts.OrganizeImportsMode, preferences?: ts.UserPreferences) {
|
||||
const changes = this.languageService.organizeImports({ fileName: this.activeFile.fileName, type: "file", mode }, this.formatCodeSettings, preferences);
|
||||
this.applyChanges(changes);
|
||||
this.verifyFileContent(this.activeFile.fileName, newContent);
|
||||
}
|
||||
|
||||
@ -630,8 +630,8 @@ export class Verify extends VerifyNegatable {
|
||||
this.state.noMoveToNewFile();
|
||||
}
|
||||
|
||||
public organizeImports(newContent: string, mode?: ts.OrganizeImportsMode): void {
|
||||
this.state.verifyOrganizeImports(newContent, mode);
|
||||
public organizeImports(newContent: string, mode?: ts.OrganizeImportsMode, preferences?: ts.UserPreferences): void {
|
||||
this.state.verifyOrganizeImports(newContent, mode, preferences);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3516,6 +3516,7 @@ export interface UserPreferences {
|
||||
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
|
||||
readonly includeInlayEnumMemberValueHints?: boolean;
|
||||
readonly autoImportFileExcludePatterns?: string[];
|
||||
readonly organizeImportsIgnoreCase?: "auto" | boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether {@link ReferencesResponseItem.lineText} is supported.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -38,20 +38,20 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
|
||||
function assertSortsBefore(importString1: string, importString2: string) {
|
||||
const [{moduleSpecifier: moduleSpecifier1}, {moduleSpecifier: moduleSpecifier2}] = parseImports(importString1, importString2);
|
||||
assert.equal(ts.OrganizeImports.compareModuleSpecifiers(moduleSpecifier1, moduleSpecifier2), ts.Comparison.LessThan);
|
||||
assert.equal(ts.OrganizeImports.compareModuleSpecifiers(moduleSpecifier2, moduleSpecifier1), ts.Comparison.GreaterThan);
|
||||
assert.equal(ts.OrganizeImports.compareModuleSpecifiers(moduleSpecifier1, moduleSpecifier2, /*ignoreCase*/ true), ts.Comparison.LessThan);
|
||||
assert.equal(ts.OrganizeImports.compareModuleSpecifiers(moduleSpecifier2, moduleSpecifier1, /*ignoreCase*/ true), ts.Comparison.GreaterThan);
|
||||
}
|
||||
});
|
||||
|
||||
describe("Coalesce imports", () => {
|
||||
it("No imports", () => {
|
||||
assert.isEmpty(ts.OrganizeImports.coalesceImports([]));
|
||||
assert.isEmpty(ts.OrganizeImports.coalesceImports([], /*ignoreCase*/ true));
|
||||
});
|
||||
|
||||
it("Sort specifiers - case-insensitive", () => {
|
||||
const sortedImports = parseImports(`import { default as M, a as n, B, y, Z as O } from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = parseImports(`import { a as n, B, default as M, y, Z as O } from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = parseImports(`import { B, default as M, a as n, Z as O, y } from "lib";`);
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
|
||||
@ -59,7 +59,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = parseImports(`import "lib";`);
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -68,7 +68,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import * as x from "lib";`,
|
||||
`import * as y from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -77,7 +77,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import x from "lib";`,
|
||||
`import y from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = parseImports(`import { default as x, default as y } from "lib";`);
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -86,7 +86,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import { x } from "lib";`,
|
||||
`import { y as z } from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = parseImports(`import { x, y as z } from "lib";`);
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -95,7 +95,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import * as x from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -104,7 +104,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import x from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -113,7 +113,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import { x } from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -122,7 +122,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import * as x from "lib";`,
|
||||
`import y from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = parseImports(
|
||||
`import y, * as x from "lib";`);
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
@ -132,7 +132,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import * as x from "lib";`,
|
||||
`import { y } from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -141,7 +141,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedImports = parseImports(
|
||||
`import x from "lib";`,
|
||||
`import { y } from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = parseImports(
|
||||
`import x, { y } from "lib";`);
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
@ -157,7 +157,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
`import * as x from "lib";`,
|
||||
`import z from "lib";`,
|
||||
`import { a } from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import * as x from "lib";`,
|
||||
@ -172,7 +172,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
`import * as x from "lib";`,
|
||||
`import * as y from "lib";`,
|
||||
`import z from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -182,7 +182,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
`import type { x } from "lib";`,
|
||||
`import type { y } from "lib";`,
|
||||
`import { z } from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = parseImports(
|
||||
`import { z } from "lib";`,
|
||||
`import type { x, y } from "lib";`);
|
||||
@ -196,7 +196,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
`import type z from "lib";`);
|
||||
// Default import could be rewritten as a named import to combine with `x`,
|
||||
// but seems of debatable merit.
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = actualCoalescedImports;
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -204,19 +204,19 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
|
||||
describe("Coalesce exports", () => {
|
||||
it("No exports", () => {
|
||||
assert.isEmpty(ts.OrganizeImports.coalesceExports([]));
|
||||
assert.isEmpty(ts.OrganizeImports.coalesceExports([], /*ignoreCase*/ true));
|
||||
});
|
||||
|
||||
it("Sort specifiers - case-insensitive", () => {
|
||||
const sortedExports = parseExports(`export { default as M, a as n, B, y, Z as O } from "lib";`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports);
|
||||
const expectedCoalescedExports = parseExports(`export { a as n, B, default as M, y, Z as O } from "lib";`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedExports = parseExports(`export { B, default as M, a as n, Z as O, y } from "lib";`);
|
||||
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
|
||||
});
|
||||
|
||||
it("Sort specifiers - type-only", () => {
|
||||
const sortedImports = parseImports(`import { type z, y, type x, c, type b, a } from "lib";`);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedImports = parseImports(`import { a, c, y, type b, type x, type z } from "lib";`);
|
||||
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
|
||||
});
|
||||
@ -225,7 +225,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedExports = parseExports(
|
||||
`export * from "lib";`,
|
||||
`export * from "lib";`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedExports = parseExports(`export * from "lib";`);
|
||||
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
|
||||
});
|
||||
@ -234,7 +234,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedExports = parseExports(
|
||||
`export { x };`,
|
||||
`export { y as z };`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedExports = parseExports(`export { x, y as z };`);
|
||||
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
|
||||
});
|
||||
@ -243,7 +243,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedExports = parseExports(
|
||||
`export { x } from "lib";`,
|
||||
`export { y as z } from "lib";`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`);
|
||||
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
|
||||
});
|
||||
@ -252,7 +252,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedExports = parseExports(
|
||||
`export * from "lib";`,
|
||||
`export { y } from "lib";`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedExports = sortedExports;
|
||||
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
|
||||
});
|
||||
@ -262,9 +262,9 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
`export { x };`,
|
||||
`export { y as w, z as default };`,
|
||||
`export { w as q };`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedExports = parseExports(
|
||||
`export { w as q, x, y as w, z as default };`);
|
||||
`export { z as default, w as q, y as w, x };`);
|
||||
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
|
||||
});
|
||||
|
||||
@ -273,10 +273,10 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
`export { x as a, y } from "lib";`,
|
||||
`export * from "lib";`,
|
||||
`export { z as b } from "lib";`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedExports = parseExports(
|
||||
`export * from "lib";`,
|
||||
`export { x as a, y, z as b } from "lib";`);
|
||||
`export { x as a, z as b, y } from "lib";`);
|
||||
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
|
||||
});
|
||||
|
||||
@ -284,7 +284,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedExports = parseExports(
|
||||
`export { x };`,
|
||||
`export type { y };`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedExports = sortedExports;
|
||||
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
|
||||
});
|
||||
@ -293,7 +293,7 @@ describe("unittests:: services:: organizeImports", () => {
|
||||
const sortedExports = parseExports(
|
||||
`export type { x };`,
|
||||
`export type { y };`);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports);
|
||||
const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports, /*ignoreCase*/ true);
|
||||
const expectedCoalescedExports = parseExports(
|
||||
`export type { x, y };`);
|
||||
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
|
||||
|
||||
@ -2802,6 +2802,7 @@ declare namespace ts {
|
||||
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
|
||||
readonly includeInlayEnumMemberValueHints?: boolean;
|
||||
readonly autoImportFileExcludePatterns?: string[];
|
||||
readonly organizeImportsIgnoreCase?: "auto" | boolean;
|
||||
/**
|
||||
* Indicates whether {@link ReferencesResponseItem.lineText} is supported.
|
||||
*/
|
||||
@ -8393,6 +8394,7 @@ declare namespace ts {
|
||||
readonly includeInlayEnumMemberValueHints?: boolean;
|
||||
readonly allowRenameOfImportPath?: boolean;
|
||||
readonly autoImportFileExcludePatterns?: string[];
|
||||
readonly organizeImportsIgnoreCase?: "auto" | boolean;
|
||||
}
|
||||
/** Represents a bigint literal value without requiring bigint support */
|
||||
interface PseudoBigInt {
|
||||
|
||||
@ -4459,6 +4459,7 @@ declare namespace ts {
|
||||
readonly includeInlayEnumMemberValueHints?: boolean;
|
||||
readonly allowRenameOfImportPath?: boolean;
|
||||
readonly autoImportFileExcludePatterns?: string[];
|
||||
readonly organizeImportsIgnoreCase?: "auto" | boolean;
|
||||
}
|
||||
/** Represents a bigint literal value without requiring bigint support */
|
||||
interface PseudoBigInt {
|
||||
|
||||
@ -10,7 +10,7 @@ D;
|
||||
// ==ORGANIZED==
|
||||
|
||||
import * as NS from "lib";
|
||||
import D, { class, class, class, F1, F2 } from "lib";
|
||||
import D, { F1, F2, class, class, class } from "lib";
|
||||
class class class;
|
||||
|
||||
D;
|
||||
|
||||
44
tests/cases/fourslash/autoImportSortCaseSensitivity1.ts
Normal file
44
tests/cases/fourslash/autoImportSortCaseSensitivity1.ts
Normal file
@ -0,0 +1,44 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /exports1.ts
|
||||
//// export const a = 0;
|
||||
//// export const A = 1;
|
||||
//// export const b = 2;
|
||||
//// export const B = 3;
|
||||
//// export const c = 4;
|
||||
//// export const C = 5;
|
||||
|
||||
// @Filename: /exports2.ts
|
||||
//// export const d = 0;
|
||||
//// export const D = 1;
|
||||
//// export const e = 2;
|
||||
//// export const E = 3;
|
||||
|
||||
// Ambiguous in whole file: use user preference, default to case-sensitive
|
||||
|
||||
// @Filename: /index0.ts
|
||||
//// import { A, B, C } from "./exports1";
|
||||
//// a/*0*/
|
||||
|
||||
// Ambiguous in target import: use user preference, look at other imports in file
|
||||
|
||||
// @Filename: /index1.ts
|
||||
//// import { A, a, B, b } from "./exports1";
|
||||
//// import { E } from "./exports2";
|
||||
//// d/*1*/
|
||||
|
||||
goTo.marker("0");
|
||||
verify.importFixAtPosition([`import { A, B, C, a } from "./exports1";\na`]);
|
||||
verify.importFixAtPosition([`import { a, A, B, C } from "./exports1";\na`],
|
||||
/*errorCode*/ undefined,
|
||||
{ organizeImportsIgnoreCase: true });
|
||||
|
||||
goTo.marker("1");
|
||||
verify.importFixAtPosition([
|
||||
`import { A, a, B, b } from "./exports1";
|
||||
import { d, E } from "./exports2";
|
||||
d`]);
|
||||
verify.importFixAtPosition([
|
||||
`import { A, a, B, b } from "./exports1";
|
||||
import { E, d } from "./exports2";
|
||||
d`], /*errorCode*/ undefined, { organizeImportsIgnoreCase: false });
|
||||
33
tests/cases/fourslash/autoImportSortCaseSensitivity2.ts
Normal file
33
tests/cases/fourslash/autoImportSortCaseSensitivity2.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export interface HasBar { bar: number }
|
||||
////export function hasBar(x: unknown): x is HasBar { return x && typeof x.bar === "number" }
|
||||
////export function foo() {}
|
||||
////export type __String = string;
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import { __String, HasBar, hasBar } from "./a";
|
||||
////f/**/;
|
||||
|
||||
verify.completions({
|
||||
marker: "",
|
||||
includes: {
|
||||
name: "foo",
|
||||
source: "/a",
|
||||
sourceDisplay: "./a",
|
||||
text: "function foo(): void",
|
||||
kind: "function",
|
||||
kindModifiers: "export",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
},
|
||||
preferences: { includeCompletionsForModuleExports: true },
|
||||
});
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
source: "/a",
|
||||
description: `Update import from "./a"`,
|
||||
newFileContent: `import { __String, foo, HasBar, hasBar } from "./a";
|
||||
f;`,
|
||||
});
|
||||
@ -20,7 +20,7 @@ goTo.file("/b.ts");
|
||||
verify.codeFix({
|
||||
description: "Infer parameter types from usage",
|
||||
newFileContent:
|
||||
`import { getEmail, User } from "./a";
|
||||
`import { User, getEmail } from "./a";
|
||||
|
||||
export function f(user: User) {
|
||||
getEmail(user);
|
||||
|
||||
@ -448,7 +448,7 @@ declare namespace FourSlashInterface {
|
||||
|
||||
generateTypes(...options: GenerateTypesOptions[]): void;
|
||||
|
||||
organizeImports(newContent: string, mode?: ts.OrganizeImportsMode): void;
|
||||
organizeImports(newContent: string, mode?: ts.OrganizeImportsMode, preferences?: UserPreferences): void;
|
||||
|
||||
toggleLineComment(newFileContent: string): void;
|
||||
toggleMultilineComment(newFileContent: string): void;
|
||||
@ -672,6 +672,7 @@ declare namespace FourSlashInterface {
|
||||
readonly providePrefixAndSuffixTextForRename?: boolean;
|
||||
readonly allowRenameOfImportPath?: boolean;
|
||||
readonly autoImportFileExcludePatterns?: readonly string[];
|
||||
readonly organizeImportsIgnoreCase?: "auto" | boolean;
|
||||
}
|
||||
interface InlayHintsOptions extends UserPreferences {
|
||||
readonly includeInlayParameterNameHints?: "none" | "literals" | "all";
|
||||
|
||||
@ -18,22 +18,44 @@
|
||||
//// console.log(a, B, b, c, C, d, D);
|
||||
//// console.log(e, f, F, g, G, H, h);
|
||||
|
||||
// verify.organizeImports(
|
||||
// `import {
|
||||
// a,
|
||||
// b,
|
||||
// b as B,
|
||||
// c,
|
||||
// c as C,
|
||||
// d, d as D,
|
||||
// e,
|
||||
// f,
|
||||
// f as F,
|
||||
// g,
|
||||
// g as G,
|
||||
// h, h as H
|
||||
// } from './foo';
|
||||
|
||||
// console.log(a, B, b, c, C, d, D);
|
||||
// console.log(e, f, F, g, G, H, h);`,
|
||||
// /*mode*/ undefined,
|
||||
// { organizeImportsIgnoreCase: true });
|
||||
|
||||
verify.organizeImports(
|
||||
`import {
|
||||
b as B,
|
||||
c as C,
|
||||
d as D,
|
||||
f as F,
|
||||
g as G,
|
||||
h as H,
|
||||
a,
|
||||
b,
|
||||
b as B,
|
||||
c,
|
||||
c as C,
|
||||
d, d as D,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
f as F,
|
||||
g,
|
||||
g as G,
|
||||
h, h as H
|
||||
h
|
||||
} from './foo';
|
||||
|
||||
console.log(a, B, b, c, C, d, D);
|
||||
console.log(e, f, F, g, G, H, h);`
|
||||
);
|
||||
console.log(e, f, F, g, G, H, h);`, /*mode*/ undefined, { organizeImportsIgnoreCase: false });
|
||||
|
||||
@ -23,6 +23,31 @@
|
||||
////interface Use extends Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9 {}
|
||||
////console.log(func1, func2, func3, func4, func5, func6, func7, func8, func9);
|
||||
|
||||
verify.organizeImports(
|
||||
`import {
|
||||
Type1,
|
||||
Type2,
|
||||
Type3,
|
||||
Type4,
|
||||
Type5,
|
||||
Type6,
|
||||
Type7,
|
||||
Type8,
|
||||
Type9,
|
||||
func1,
|
||||
func2,
|
||||
func3,
|
||||
func4,
|
||||
func5,
|
||||
func6,
|
||||
func7,
|
||||
func8,
|
||||
func9,
|
||||
} from "foo";
|
||||
interface Use extends Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9 {}
|
||||
console.log(func1, func2, func3, func4, func5, func6, func7, func8, func9);`
|
||||
);
|
||||
|
||||
verify.organizeImports(
|
||||
`import {
|
||||
func1,
|
||||
@ -45,5 +70,6 @@ verify.organizeImports(
|
||||
Type9,
|
||||
} from "foo";
|
||||
interface Use extends Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9 {}
|
||||
console.log(func1, func2, func3, func4, func5, func6, func7, func8, func9);`
|
||||
);
|
||||
console.log(func1, func2, func3, func4, func5, func6, func7, func8, func9);`,
|
||||
/*mode*/ undefined,
|
||||
{ organizeImportsIgnoreCase: true });
|
||||
|
||||
31
tests/cases/fourslash/organizeImports16.ts
Normal file
31
tests/cases/fourslash/organizeImports16.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import { a, A, b } from "foo";
|
||||
//// interface Use extends A {}
|
||||
//// console.log(a, b);
|
||||
|
||||
verify.organizeImports(
|
||||
`import { a, A, b } from "foo";
|
||||
interface Use extends A {}
|
||||
console.log(a, b);`);
|
||||
|
||||
verify.organizeImports(
|
||||
`import { a, A, b } from "foo";
|
||||
interface Use extends A {}
|
||||
console.log(a, b);`,
|
||||
/*mode*/ undefined,
|
||||
{ organizeImportsIgnoreCase: "auto" });
|
||||
|
||||
verify.organizeImports(
|
||||
`import { a, A, b } from "foo";
|
||||
interface Use extends A {}
|
||||
console.log(a, b);`,
|
||||
/*mode*/ undefined,
|
||||
{ organizeImportsIgnoreCase: true });
|
||||
|
||||
verify.organizeImports(
|
||||
`import { A, a, b } from "foo";
|
||||
interface Use extends A {}
|
||||
console.log(a, b);`,
|
||||
/*mode*/ undefined,
|
||||
{ organizeImportsIgnoreCase: false });
|
||||
Loading…
x
Reference in New Issue
Block a user