Organize imports collation (#52115)

This commit is contained in:
Ron Buckton 2023-01-19 17:53:51 -05:00 committed by GitHub
parent abd6cb446f
commit 20182cf848
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 721 additions and 109 deletions

View File

@ -865,32 +865,25 @@ export const enum SortKind {
}
/** @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 {
export function detectSortCaseSensitivity<T>(
array: readonly T[],
getString: (element: T) => string,
compareStringsCaseSensitive: Comparer<string>,
compareStringsCaseInsensitive: Comparer<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) {
let prevElement = getString(array[0]);
for (let i = 1, len = array.length; i < len && kind !== SortKind.None; i++) {
const element = getString(array[i]);
if (kind & SortKind.CaseSensitive && compareStringsCaseSensitive(prevElement, element) > 0) {
kind &= ~SortKind.CaseSensitive;
}
if (kind & SortKind.CaseInsensitive && caseInsensitiveComparer(prevElement, element) === Comparison.GreaterThan) {
if (kind & SortKind.CaseInsensitive && compareStringsCaseInsensitive(prevElement, element) > 0) {
kind &= ~SortKind.CaseInsensitive;
}
if (kind === SortKind.None) {
return kind;
}
prevElement = element;
}
return kind;
}
@ -2048,6 +2041,64 @@ export function memoizeWeak<A extends object, T>(callback: (arg: A) => T): (arg:
};
}
/** @internal */
export interface MemoizeCache<A extends any[], T> {
has(args: A): boolean;
get(args: A): T | undefined;
set(args: A, value: T): void;
}
/**
* A version of `memoize` that supports multiple arguments, backed by a provided cache.
*
* @internal
*/
export function memoizeCached<A extends any[], T>(callback: (...args: A) => T, cache: MemoizeCache<A, T>): (...args: A) => T {
return (...args: A) => {
let value = cache.get(args);
if (value === undefined && !cache.has(args)) {
value = callback(...args);
cache.set(args, 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))`.
*
* @param args The functions to compose.
*
* @internal
*/
export function compose<T>(...args: ((t: T) => T)[]): (t: T) => T;
/** @internal */
export function compose<T>(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T {
if (!!e) {
const args: ((t: T) => T)[] = [];
for (let i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
return t => reduceLeft(args, (u, f) => f(u), t);
}
else if (d) {
return t => d(c(b(a(t))));
}
else if (c) {
return t => c(b(a(t)));
}
else if (b) {
return t => b(a(t));
}
else if (a) {
return t => a(t);
}
else {
return t => t;
}
}
/** @internal */
export const enum AssertionLevel {
None = 0,

View File

@ -9832,6 +9832,11 @@ export interface UserPreferences {
readonly allowRenameOfImportPath?: boolean;
readonly autoImportFileExcludePatterns?: string[];
readonly organizeImportsIgnoreCase?: "auto" | boolean;
readonly organizeImportsCollation?: "ordinal" | "unicode";
readonly organizeImportsLocale?: string;
readonly organizeImportsNumericCollation?: boolean;
readonly organizeImportsAccentCollation?: boolean;
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
}
/** Represents a bigint literal value without requiring bigint support */

View File

@ -3516,7 +3516,60 @@ export interface UserPreferences {
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
readonly autoImportFileExcludePatterns?: string[];
/**
* Indicates whether imports should be organized in a case-insensitive manner.
*/
readonly organizeImportsIgnoreCase?: "auto" | boolean;
/**
* Indicates whether imports should be organized via an "ordinal" (binary) comparison using the numeric value
* of their code points, or via "unicode" collation (via the
* [Unicode Collation Algorithm](https://unicode.org/reports/tr10/#Scope)) using rules associated with the locale
* specified in {@link organizeImportsCollationLocale}.
*
* Default: `"ordinal"`.
*/
readonly organizeImportsCollation?: "ordinal" | "unicode";
/**
* Indicates the locale to use for "unicode" collation. If not specified, the locale `"en"` is used as an invariant
* for the sake of consistent sorting. Use `"auto"` to use the detected UI locale.
*
* This preference is ignored if {@link organizeImportsCollation} is not `"unicode"`.
*
* Default: `"en"`
*/
readonly organizeImportsCollationLocale?: string;
/**
* Indicates whether numeric collation should be used for digit sequences in strings. When `true`, will collate
* strings such that `a1z < a2z < a100z`. When `false`, will collate strings such that `a1z < a100z < a2z`.
*
* This preference is ignored if {@link organizeImportsCollation} is not `"unicode"`.
*
* Default: `false`
*/
readonly organizeImportsNumericCollation?: boolean;
/**
* Indicates whether accents and other diacritic marks are considered unequal for the purpose of collation. When
* `true`, characters with accents and other diacritics will be collated in the order defined by the locale specified
* in {@link organizeImportsCollationLocale}.
*
* This preference is ignored if {@link organizeImportsCollation} is not `"unicode"`.
*
* Default: `true`
*/
readonly organizeImportsAccentCollation?: boolean;
/**
* Indicates whether upper case or lower case should sort first. When `false`, the default order for the locale
* specified in {@link organizeImportsCollationLocale} is used.
*
* This preference is ignored if {@link organizeImportsCollation} is not `"unicode"`. This preference is also
* ignored if we are using case-insensitive sorting, which occurs when {@link organizeImportsIgnoreCase} is `true`,
* or if {@link organizeImportsIgnoreCase} is `"auto"` and the auto-detected case sensitivity is determined to be
* case-insensitive.
*
* Default: `false`
*/
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
/**
* Indicates whether {@link ReferencesResponseItem.lineText} is supported.

View File

@ -375,7 +375,7 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
newDeclarations = combine(newDeclarations, declarations);
});
if (newDeclarations) {
insertImports(changeTracker, sourceFile, newDeclarations, /*blankLineBetween*/ true);
insertImports(changeTracker, sourceFile, newDeclarations, /*blankLineBetween*/ true, preferences);
}
}
@ -1221,14 +1221,14 @@ function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile:
const defaultImport: Import | undefined = importKind === ImportKind.Default ? { name: symbolName, addAsTypeOnly } : undefined;
const namedImports: Import[] | undefined = importKind === ImportKind.Named ? [{ name: symbolName, addAsTypeOnly }] : undefined;
const namespaceLikeImport = importKind === ImportKind.Namespace || importKind === ImportKind.CommonJS ? { importKind, name: symbolName, addAsTypeOnly } : undefined;
insertImports(changes, sourceFile, getDeclarations(moduleSpecifier, quotePreference, defaultImport, namedImports, namespaceLikeImport), /*blankLineBetween*/ true);
insertImports(changes, sourceFile, getDeclarations(moduleSpecifier, quotePreference, defaultImport, namedImports, namespaceLikeImport), /*blankLineBetween*/ true, preferences);
return includeSymbolNameInDescription
? [Diagnostics.Import_0_from_1, symbolName, moduleSpecifier]
: [Diagnostics.Add_import_from_0, moduleSpecifier];
}
case ImportFixKind.PromoteTypeOnly: {
const { typeOnlyAliasDeclaration } = fix;
const promotedDeclaration = promoteFromTypeOnly(changes, typeOnlyAliasDeclaration, compilerOptions, sourceFile);
const promotedDeclaration = promoteFromTypeOnly(changes, typeOnlyAliasDeclaration, compilerOptions, sourceFile, preferences);
return promotedDeclaration.kind === SyntaxKind.ImportSpecifier
? [Diagnostics.Remove_type_from_import_of_0_from_1, symbolName, getModuleSpecifierText(promotedDeclaration.parent.parent)]
: [Diagnostics.Remove_type_from_import_declaration_from_0, getModuleSpecifierText(promotedDeclaration)];
@ -1244,17 +1244,18 @@ function getModuleSpecifierText(promotedDeclaration: ImportClause | ImportEquals
: cast(promotedDeclaration.parent.moduleSpecifier, isStringLiteral).text;
}
function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaration: TypeOnlyAliasDeclaration, compilerOptions: CompilerOptions, sourceFile: SourceFile) {
function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaration: TypeOnlyAliasDeclaration, compilerOptions: CompilerOptions, sourceFile: SourceFile, preferences: UserPreferences) {
// See comment in `doAddExistingFix` on constant with the same name.
const convertExistingToTypeOnly = compilerOptions.preserveValueImports && compilerOptions.isolatedModules;
switch (aliasDeclaration.kind) {
case SyntaxKind.ImportSpecifier:
if (aliasDeclaration.isTypeOnly) {
const sortKind = OrganizeImports.detectImportSpecifierSorting(aliasDeclaration.parent.elements);
const sortKind = OrganizeImports.detectImportSpecifierSorting(aliasDeclaration.parent.elements, preferences);
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, sortKind === SortKind.CaseInsensitive);
const comparer = OrganizeImports.getOrganizeImportsComparer(preferences, sortKind === SortKind.CaseInsensitive);
const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(aliasDeclaration.parent.elements, newSpecifier, comparer);
changes.insertImportSpecifierAtIndex(sourceFile, newSpecifier, aliasDeclaration.parent, insertionIndex);
}
else {
@ -1285,7 +1286,7 @@ function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaratio
if (convertExistingToTypeOnly) {
const namedImports = tryCast(importClause.namedBindings, isNamedImports);
if (namedImports && namedImports.elements.length > 1) {
if (OrganizeImports.detectImportSpecifierSorting(namedImports.elements) &&
if (OrganizeImports.detectImportSpecifierSorting(namedImports.elements, preferences) &&
aliasDeclaration.kind === SyntaxKind.ImportSpecifier &&
namedImports.elements.indexOf(aliasDeclaration) !== 0
) {
@ -1348,28 +1349,29 @@ function doAddExistingFix(
ignoreCaseForSorting = preferences.organizeImportsIgnoreCase;
}
else if (existingSpecifiers) {
const targetImportSorting = OrganizeImports.detectImportSpecifierSorting(existingSpecifiers);
const targetImportSorting = OrganizeImports.detectImportSpecifierSorting(existingSpecifiers, preferences);
if (targetImportSorting !== SortKind.Both) {
ignoreCaseForSorting = targetImportSorting === SortKind.CaseInsensitive;
}
}
if (ignoreCaseForSorting === undefined) {
ignoreCaseForSorting = OrganizeImports.detectSorting(sourceFile) === SortKind.CaseInsensitive;
ignoreCaseForSorting = OrganizeImports.detectSorting(sourceFile, preferences) === SortKind.CaseInsensitive;
}
const comparer = OrganizeImports.getOrganizeImportsComparer(preferences, ignoreCaseForSorting);
const newSpecifiers = stableSort(
namedImports.map(namedImport => factory.createImportSpecifier(
(!clause.isTypeOnly || promoteFromTypeOnly) && needsTypeOnly(namedImport),
/*propertyName*/ undefined,
factory.createIdentifier(namedImport.name))),
(s1, s2) => OrganizeImports.compareImportOrExportSpecifiers(s1, s2, ignoreCaseForSorting));
(s1, s2) => OrganizeImports.compareImportOrExportSpecifiers(s1, s2, comparer));
// 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);
const specifierSort = existingSpecifiers?.length && OrganizeImports.detectImportSpecifierSorting(existingSpecifiers, preferences);
if (specifierSort && !(ignoreCaseForSorting && specifierSort === SortKind.CaseSensitive)) {
for (const spec of newSpecifiers) {
// Organize imports puts type-only import specifiers last, so if we're
@ -1377,7 +1379,7 @@ function doAddExistingFix(
// 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, ignoreCaseForSorting);
: OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec, comparer);
changes.insertImportSpecifierAtIndex(sourceFile, spec, clause.namedBindings as NamedImports, insertionIndex);
}
}

View File

@ -3,6 +3,7 @@ import {
arrayIsSorted,
binarySearch,
compareBooleans,
Comparer,
compareStringsCaseInsensitiveEslintCompatible,
compareStringsCaseSensitive,
compareValues,
@ -18,12 +19,15 @@ import {
FileTextChanges,
find,
FindAllReferences,
firstOrUndefined,
flatMap,
formatting,
getNewLineOrDefaultFromHost,
getUILocale,
group,
Identifier,
identity,
ImportClause,
ImportDeclaration,
ImportOrExportSpecifier,
ImportSpecifier,
@ -42,7 +46,8 @@ import {
LanguageServiceHost,
length,
map,
memoizeWeak,
MemoizeCache,
memoizeCached,
NamedImportBindings,
NamedImports,
NamespaceImport,
@ -83,19 +88,16 @@ export function organizeImports(
const shouldSort = mode === OrganizeImportsMode.SortAndCombine || mode === OrganizeImportsMode.All;
const shouldCombine = shouldSort; // These are currently inseparable, but I draw a distinction for clarity and in case we add modes in the future.
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 comparer = getOrganizeImportsComparerWithDetection(preferences, shouldSort ? () => detectSortingWorker(topLevelImportGroupDecls, preferences) === SortKind.CaseInsensitive : undefined);
const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => {
const processedDeclarations = maybeCoalesce(maybeRemove(importGroup, sourceFile, program), ignoreCase, sourceFile);
return shouldSort
? stableSort(processedDeclarations, (s1, s2) => compareImportsOrRequireStatements(s1, s2))
: processedDeclarations;
if (shouldRemove) importGroup = removeUnusedImports(importGroup, sourceFile, program);
if (shouldCombine) importGroup = coalesceImportsWorker(importGroup, comparer, sourceFile);
if (shouldSort) importGroup = stableSort(importGroup, (s1, s2) => compareImportsOrRequireStatements(s1, s2, comparer));
return importGroup;
};
topLevelImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier));
@ -104,7 +106,7 @@ export function organizeImports(
if (mode !== OrganizeImportsMode.RemoveUnused) {
// All of the old ExportDeclarations in the file, in syntactic order.
const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration);
organizeImportsWorker(topLevelExportDecls, group => coalesceExports(group, ignoreCase));
organizeImportsWorker(topLevelExportDecls, group => coalesceExportsWorker(group, comparer));
}
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
@ -116,7 +118,7 @@ export function organizeImports(
// Exports are always used
if (mode !== OrganizeImportsMode.RemoveUnused) {
const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration);
organizeImportsWorker(ambientModuleExportDecls, group => coalesceExports(group, ignoreCase));
organizeImportsWorker(ambientModuleExportDecls, group => coalesceExportsWorker(group, comparer));
}
}
@ -141,7 +143,7 @@ export function organizeImports(
? group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier)!)
: [oldImportDecls];
const sortedImportGroups = shouldSort
? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier))
? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiersWorker(group1[0].moduleSpecifier, group2[0].moduleSpecifier, comparer))
: oldImportGroups;
const newImportDecls = flatMap(sortedImportGroups, importGroup =>
getExternalModuleName(importGroup[0].moduleSpecifier)
@ -306,15 +308,20 @@ function getExternalModuleName(specifier: Expression | undefined) {
/**
* @param importGroup a list of ImportDeclarations, all with the same module name.
*
* @deprecated Only used for testing
* @internal
*/
export function coalesceImports(importGroup: readonly ImportDeclaration[], ignoreCase?: boolean, sourceFile?: SourceFile): readonly ImportDeclaration[] {
export function coalesceImports(importGroup: readonly ImportDeclaration[], ignoreCase: boolean, sourceFile?: SourceFile): readonly ImportDeclaration[] {
const comparer = getOrganizeImportsOrdinalStringComparer(ignoreCase);
return coalesceImportsWorker(importGroup, comparer, sourceFile);
}
function coalesceImportsWorker(importGroup: readonly ImportDeclaration[], comparer: Comparer<string>, 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) {
@ -330,59 +337,58 @@ export function coalesceImports(importGroup: readonly ImportDeclaration[], ignor
// Add the namespace import to the existing default ImportDeclaration.
const defaultImport = defaultImports[0];
coalescedImports.push(
updateImportDeclarationAndClause(defaultImport, defaultImport.importClause!.name, namespaceImports[0].importClause!.namedBindings)); // TODO: GH#18217
updateImportDeclarationAndClause(defaultImport, defaultImport.importClause.name, namespaceImports[0].importClause.namedBindings));
continue;
}
const sortedNamespaceImports = stableSort(namespaceImports, (i1, i2) =>
compareIdentifiers((i1.importClause!.namedBindings as NamespaceImport).name, (i2.importClause!.namedBindings as NamespaceImport).name)); // TODO: GH#18217
comparer(i1.importClause.namedBindings.name.text, i2.importClause.namedBindings.name.text));
for (const namespaceImport of sortedNamespaceImports) {
// Drop the name, if any
coalescedImports.push(
updateImportDeclarationAndClause(namespaceImport, /*name*/ undefined, namespaceImport.importClause!.namedBindings)); // TODO: GH#18217
updateImportDeclarationAndClause(namespaceImport, /*name*/ undefined, namespaceImport.importClause.namedBindings));
}
if (defaultImports.length === 0 && namedImports.length === 0) {
const firstDefaultImport = firstOrUndefined(defaultImports);
const firstNamedImport = firstOrUndefined(namedImports);
const importDecl = firstDefaultImport ?? firstNamedImport;
if (!importDecl) {
continue;
}
let newDefaultImport: Identifier | undefined;
const newImportSpecifiers: ImportSpecifier[] = [];
if (defaultImports.length === 1) {
newDefaultImport = defaultImports[0].importClause!.name;
newDefaultImport = defaultImports[0].importClause.name;
}
else {
for (const defaultImport of defaultImports) {
newImportSpecifiers.push(
factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217
factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("default"), defaultImport.importClause.name));
}
}
newImportSpecifiers.push(...getNewImportSpecifiers(namedImports));
const sortedImportSpecifiers = factory.createNodeArray(
sortSpecifiers(newImportSpecifiers, ignoreCase),
(namedImports[0]?.importClause!.namedBindings as NamedImports)?.elements.hasTrailingComma
sortSpecifiers(newImportSpecifiers, comparer),
firstNamedImport?.importClause.namedBindings.elements.hasTrailingComma
);
const importDecl = defaultImports.length > 0
? defaultImports[0]
: namedImports[0];
const newNamedImports = sortedImportSpecifiers.length === 0
? newDefaultImport
? undefined
: factory.createNamedImports(emptyArray)
: namedImports.length === 0
? factory.createNamedImports(sortedImportSpecifiers)
: factory.updateNamedImports(namedImports[0].importClause!.namedBindings as NamedImports, sortedImportSpecifiers); // TODO: GH#18217
: firstNamedImport
? factory.updateNamedImports(firstNamedImport.importClause.namedBindings, sortedImportSpecifiers)
: factory.createNamedImports(sortedImportSpecifiers);
if (sourceFile &&
newNamedImports &&
namedImports[0]?.importClause!.namedBindings &&
!rangeIsOnSingleLine(namedImports[0].importClause.namedBindings, sourceFile)
firstNamedImport?.importClause.namedBindings &&
!rangeIsOnSingleLine(firstNamedImport.importClause.namedBindings, sourceFile)
) {
setEmitFlags(newNamedImports, EmitFlags.MultiLine);
}
@ -394,7 +400,7 @@ export function coalesceImports(importGroup: readonly ImportDeclaration[], ignor
coalescedImports.push(
updateImportDeclarationAndClause(importDecl, newDefaultImport, /*namedBindings*/ undefined));
coalescedImports.push(
updateImportDeclarationAndClause(namedImports[0] ?? importDecl, /*name*/ undefined, newNamedImports));
updateImportDeclarationAndClause(firstNamedImport ?? importDecl, /*name*/ undefined, newNamedImports));
}
else {
coalescedImports.push(
@ -403,13 +409,30 @@ export function coalesceImports(importGroup: readonly ImportDeclaration[], ignor
}
return coalescedImports;
}
type ImportDeclarationWithDefaultImport = ImportDeclaration & {
readonly importClause: ImportClause & {
readonly name: Identifier;
};
};
type ImportDeclarationWithNamespaceImport = ImportDeclaration & {
readonly importClause: ImportClause & {
readonly namedBindings: NamespaceImport;
};
};
type ImportDeclarationWithNamedImports = ImportDeclaration & {
readonly importClause: ImportClause & {
readonly namedBindings: NamedImports;
};
};
interface ImportGroup {
defaultImports: ImportDeclaration[];
namespaceImports: ImportDeclaration[];
namedImports: ImportDeclaration[];
defaultImports: ImportDeclarationWithDefaultImport[];
namespaceImports: ImportDeclarationWithNamespaceImport[];
namedImports: ImportDeclarationWithNamedImports[];
}
/*
@ -436,15 +459,15 @@ function getCategorizedImports(importGroup: readonly ImportDeclaration[]) {
const { name, namedBindings } = importDeclaration.importClause;
if (name) {
group.defaultImports.push(importDeclaration);
group.defaultImports.push(importDeclaration as ImportDeclarationWithDefaultImport);
}
if (namedBindings) {
if (isNamespaceImport(namedBindings)) {
group.namespaceImports.push(importDeclaration);
group.namespaceImports.push(importDeclaration as ImportDeclarationWithNamespaceImport);
}
else {
group.namedImports.push(importDeclaration);
group.namedImports.push(importDeclaration as ImportDeclarationWithNamedImports);
}
}
}
@ -460,9 +483,15 @@ function getCategorizedImports(importGroup: readonly ImportDeclaration[]) {
/**
* @param exportGroup a list of ExportDeclarations, all with the same module name.
*
* @deprecated Only used for testing
* @internal
*/
export function coalesceExports(exportGroup: readonly ExportDeclaration[], ignoreCase: boolean) {
const comparer = getOrganizeImportsOrdinalStringComparer(ignoreCase);
return coalesceExportsWorker(exportGroup, comparer);
}
function coalesceExportsWorker(exportGroup: readonly ExportDeclaration[], comparer: Comparer<string>) {
if (exportGroup.length === 0) {
return exportGroup;
}
@ -482,7 +511,7 @@ export function coalesceExports(exportGroup: readonly ExportDeclaration[], ignor
const newExportSpecifiers: ExportSpecifier[] = [];
newExportSpecifiers.push(...flatMap(exportGroup, i => i.exportClause && isNamedExports(i.exportClause) ? i.exportClause.elements : emptyArray));
const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers, ignoreCase);
const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers, comparer);
const exportDecl = exportGroup[0];
coalescedExports.push(
@ -546,37 +575,33 @@ function updateImportDeclarationAndClause(
importDeclaration.assertClause);
}
function sortSpecifiers<T extends ImportOrExportSpecifier>(specifiers: readonly T[], ignoreCase?: boolean) {
return stableSort(specifiers, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, ignoreCase));
function sortSpecifiers<T extends ImportOrExportSpecifier>(specifiers: readonly T[], comparer: Comparer<string>) {
return stableSort(specifiers, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, comparer));
}
/** @internal */
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);
export function compareImportOrExportSpecifiers<T extends ImportOrExportSpecifier>(s1: T, s2: T, comparer: Comparer<string>): Comparison {
return compareBooleans(s1.isTypeOnly, s2.isTypeOnly) || comparer(s1.name.text, s2.name.text);
}
/**
* Exported for testing
*
* @deprecated Only used for testing
* @internal
*/
export function compareModuleSpecifiers(m1: Expression | undefined, m2: Expression | undefined, ignoreCase?: boolean) {
const comparer = getOrganizeImportsOrdinalStringComparer(!!ignoreCase);
return compareModuleSpecifiersWorker(m1, m2, comparer);
}
function compareModuleSpecifiersWorker(m1: Expression | undefined, m2: Expression | undefined, comparer: Comparer<string>) {
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!)) ||
// 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
comparer(name1!, name2!); // I don't know why eslint is wrong but this one is necessary
}
function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement): Expression | undefined {
@ -591,17 +616,24 @@ function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement):
}
/** @internal */
export function detectSorting(sourceFile: SourceFile): SortKind {
export function detectSorting(sourceFile: SourceFile, preferences: UserPreferences): SortKind {
return detectSortingWorker(
groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)));
groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)),
preferences);
}
function detectSortingWorker(importGroups: ImportDeclaration[][]): SortKind {
function detectSortingWorker(importGroups: ImportDeclaration[][], preferences: UserPreferences): SortKind {
const collateCaseSensitive = getOrganizeImportsComparer(preferences, /*ignoreCase*/ false);
const collateCaseInsensitive = getOrganizeImportsComparer(preferences, /*ignoreCase*/ true);
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 ?? "");
sortState &= detectSortCaseSensitivity(
importGroup,
i => tryCast(i.moduleSpecifier, isStringLiteral)?.text ?? "",
collateCaseSensitive,
collateCaseInsensitive);
if (!sortState) {
return sortState;
}
@ -612,7 +644,7 @@ function detectSortingWorker(importGroups: ImportDeclaration[][]): SortKind {
importGroup,
i => tryCast(i.importClause?.namedBindings, isNamedImports)?.elements.length! > 1);
if (declarationWithNamedImports) {
sortState &= detectImportSpecifierSorting((declarationWithNamedImports.importClause!.namedBindings as NamedImports).elements);
sortState &= detectImportSpecifierSorting((declarationWithNamedImports.importClause!.namedBindings as NamedImports).elements, preferences);
if (!sortState) {
return sortState;
}
@ -629,33 +661,66 @@ function detectSortingWorker(importGroups: ImportDeclaration[][]): SortKind {
}
/** @internal */
export function detectImportDeclarationSorting(imports: readonly AnyImportOrRequireStatement[]): SortKind {
return detectSortCaseSensitivity(imports, /*useEslintOrdering*/ true, s => getExternalModuleName(getModuleSpecifierExpression(s)) || "");
export function detectImportDeclarationSorting(imports: readonly AnyImportOrRequireStatement[], preferences: UserPreferences): SortKind {
const collateCaseSensitive = getOrganizeImportsComparer(preferences, /*ignoreCase*/ false);
const collateCaseInsensitive = getOrganizeImportsComparer(preferences, /*ignoreCase*/ true);
return detectSortCaseSensitivity(
imports,
s => getExternalModuleName(getModuleSpecifierExpression(s)) || "",
collateCaseSensitive,
collateCaseInsensitive
);
}
class ImportSpecifierSortingCache implements MemoizeCache<[readonly ImportSpecifier[], UserPreferences], SortKind> {
private _lastPreferences: UserPreferences | undefined;
private _cache: WeakMap<readonly ImportSpecifier[], SortKind> | undefined;
has([specifiers, preferences]: [readonly ImportSpecifier[], UserPreferences]) {
if (this._lastPreferences !== preferences || !this._cache) return false;
return this._cache.has(specifiers);
}
get([specifiers, preferences]: [readonly ImportSpecifier[], UserPreferences]) {
if (this._lastPreferences !== preferences || !this._cache) return undefined;
return this._cache.get(specifiers);
}
set([specifiers, preferences]: [readonly ImportSpecifier[], UserPreferences], value: SortKind) {
if (this._lastPreferences !== preferences) {
this._lastPreferences = preferences;
this._cache = undefined;
}
this._cache ??= new WeakMap();
this._cache.set(specifiers, value);
}
}
/** @internal */
export const detectImportSpecifierSorting = memoizeWeak((specifiers: readonly ImportSpecifier[]): SortKind => {
export const detectImportSpecifierSorting = memoizeCached((specifiers: readonly ImportSpecifier[], preferences: UserPreferences): SortKind => {
if (!arrayIsSorted(specifiers, (s1, s2) => compareBooleans(s1.isTypeOnly, s2.isTypeOnly))) {
return SortKind.None;
}
return detectSortCaseSensitivity(specifiers, /*useEslintOrdering*/ true, specifier => specifier.name.text);
});
const collateCaseSensitive = getOrganizeImportsComparer(preferences, /*ignoreCase*/ false);
const collateCaseInsensitive = getOrganizeImportsComparer(preferences, /*ignoreCase*/ true);
return detectSortCaseSensitivity(specifiers, specifier => specifier.name.text, collateCaseSensitive, collateCaseInsensitive);
}, new ImportSpecifierSortingCache());
/** @internal */
export function getImportDeclarationInsertionIndex(sortedImports: readonly AnyImportOrRequireStatement[], newImport: AnyImportOrRequireStatement, ignoreCase?: boolean) {
const index = binarySearch(sortedImports, newImport, identity, (a, b) => compareImportsOrRequireStatements(a, b, ignoreCase));
export function getImportDeclarationInsertionIndex(sortedImports: readonly AnyImportOrRequireStatement[], newImport: AnyImportOrRequireStatement, comparer: Comparer<string>) {
const index = binarySearch(sortedImports, newImport, identity, (a, b) => compareImportsOrRequireStatements(a, b, comparer));
return index < 0 ? ~index : index;
}
/** @internal */
export function getImportSpecifierInsertionIndex(sortedImports: readonly ImportSpecifier[], newImport: ImportSpecifier, ignoreCase?: boolean) {
const index = binarySearch(sortedImports, newImport, identity, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, ignoreCase));
export function getImportSpecifierInsertionIndex(sortedImports: readonly ImportSpecifier[], newImport: ImportSpecifier, comparer: Comparer<string>) {
const index = binarySearch(sortedImports, newImport, identity, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, comparer));
return index < 0 ? ~index : index;
}
/** @internal */
export function compareImportsOrRequireStatements(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement, ignoreCase?: boolean) {
return compareModuleSpecifiers(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2), ignoreCase) || compareImportKind(s1, s2);
export function compareImportsOrRequireStatements(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement, comparer: Comparer<string>) {
return compareModuleSpecifiersWorker(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2), comparer) || compareImportKind(s1, s2);
}
function compareImportKind(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) {
@ -699,3 +764,51 @@ function tryGetNamedBindingElements(namedImport: ImportDeclaration) {
? namedImport.importClause.namedBindings.elements
: undefined;
}
function getOrganizeImportsOrdinalStringComparer(ignoreCase: boolean) {
return ignoreCase ? compareStringsCaseInsensitiveEslintCompatible : compareStringsCaseSensitive;
}
function getOrganizeImportsUnicodeStringComparer(ignoreCase: boolean, preferences: UserPreferences): Comparer<string> {
const resolvedLocale = getOrganizeImportsLocale(preferences);
const caseFirst = preferences.organizeImportsCaseFirst ?? false;
const numeric = preferences.organizeImportsNumericCollation ?? false;
const accents = preferences.organizeImportsAccentCollation ?? true;
const sensitivity =
ignoreCase ?
accents ? "accent" : "base" :
accents ? "variant" : "case";
const collator = new Intl.Collator(resolvedLocale, {
usage: "sort",
caseFirst: caseFirst || "false",
sensitivity,
numeric,
});
// `compare` is a bound method, so we do not need to close over `collator`.
return collator.compare;
}
function getOrganizeImportsLocale(preferences: UserPreferences): string {
let locale = preferences.organizeImportsLocale;
if (locale === "auto") locale = getUILocale();
if (locale === undefined) locale = "en";
const supportedLocales = Intl.Collator.supportedLocalesOf(locale);
const resolvedLocale = supportedLocales.length ? supportedLocales[0] : "en";
return resolvedLocale;
}
/** @internal */
export function getOrganizeImportsComparer(preferences: UserPreferences, ignoreCase: boolean): Comparer<string> {
const collation = preferences.organizeImportsCollation ?? "ordinal";
return collation === "unicode" ?
getOrganizeImportsUnicodeStringComparer(ignoreCase, preferences) :
getOrganizeImportsOrdinalStringComparer(ignoreCase);
}
function getOrganizeImportsComparerWithDetection(preferences: UserPreferences, detectIgnoreCase?: () => boolean): Comparer<string> {
const ignoreCase = typeof preferences.organizeImportsIgnoreCase === "boolean" ? preferences.organizeImportsIgnoreCase : detectIgnoreCase?.() ?? false;
return getOrganizeImportsComparer(preferences, ignoreCase);
}

View File

@ -282,7 +282,7 @@ function getNewStatementsAndRemoveFromOldFile(
const quotePreference = getQuotePreference(oldFile, preferences);
const importsFromNewFile = createOldFileImportsFromNewFile(oldFile, usage.oldFileImportsFromNewFile, newFilename, program, host, useEsModuleSyntax, quotePreference);
if (importsFromNewFile) {
insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true);
insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true, preferences);
}
deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker);

View File

@ -305,6 +305,7 @@ import {
skipAlias,
skipOuterExpressions,
some,
SortKind,
SourceFile,
SourceFileLike,
SourceMapper,
@ -2534,17 +2535,20 @@ export function findModifier(node: Node, kind: Modifier["kind"]): Modifier | und
}
/** @internal */
export function insertImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, imports: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[], blankLineBetween: boolean): void {
export function insertImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, imports: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[], blankLineBetween: boolean, preferences: UserPreferences): void {
const decl = isArray(imports) ? imports[0] : imports;
const importKindPredicate: (node: Node) => node is AnyImportOrRequireStatement = decl.kind === SyntaxKind.VariableStatement ? isRequireVariableStatement : isAnyImportSyntax;
const existingImportStatements = filter(sourceFile.statements, importKindPredicate);
const sortedNewImports = isArray(imports) ? stableSort(imports, OrganizeImports.compareImportsOrRequireStatements) : [imports];
let sortKind = isArray(imports) ? OrganizeImports.detectImportDeclarationSorting(imports, preferences) : SortKind.Both;
const comparer = OrganizeImports.getOrganizeImportsComparer(preferences, sortKind === SortKind.CaseInsensitive);
const sortedNewImports = isArray(imports) ? stableSort(imports, (a, b) => OrganizeImports.compareImportsOrRequireStatements(a, b, comparer)) : [imports];
if (!existingImportStatements.length) {
changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween);
}
else if (existingImportStatements && OrganizeImports.detectImportDeclarationSorting(existingImportStatements)) {
else if (existingImportStatements && (sortKind = OrganizeImports.detectImportDeclarationSorting(existingImportStatements, preferences))) {
const comparer = OrganizeImports.getOrganizeImportsComparer(preferences, sortKind === SortKind.CaseInsensitive);
for (const newImport of sortedNewImports) {
const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport);
const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport, comparer);
if (insertionIndex === 0) {
// If the first import is top-of-file, insert after the leading comment which is likely the header.
const options = existingImportStatements[0] === sourceFile.statements[0] ?

View File

@ -2802,7 +2802,59 @@ declare namespace ts {
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
readonly autoImportFileExcludePatterns?: string[];
/**
* Indicates whether imports should be organized in a case-insensitive manner.
*/
readonly organizeImportsIgnoreCase?: "auto" | boolean;
/**
* Indicates whether imports should be organized via an "ordinal" (binary) comparison using the numeric value
* of their code points, or via "unicode" collation (via the
* [Unicode Collation Algorithm](https://unicode.org/reports/tr10/#Scope)) using rules associated with the locale
* specified in {@link organizeImportsCollationLocale}.
*
* Default: `"ordinal"`.
*/
readonly organizeImportsCollation?: "ordinal" | "unicode";
/**
* Indicates the locale to use for "unicode" collation. If not specified, the locale `"en"` is used as an invariant
* for the sake of consistent sorting. Use `"auto"` to use the detected UI locale.
*
* This preference is ignored if {@link organizeImportsCollation} is not `"unicode"`.
*
* Default: `"en"`
*/
readonly organizeImportsCollationLocale?: string;
/**
* Indicates whether numeric collation should be used for digit sequences in strings. When `true`, will collate
* strings such that `a1z < a2z < a100z`. When `false`, will collate strings such that `a1z < a100z < a2z`.
*
* This preference is ignored if {@link organizeImportsCollation} is not `"unicode"`.
*
* Default: `false`
*/
readonly organizeImportsNumericCollation?: boolean;
/**
* Indicates whether accents and other diacritic marks are considered unequal for the purpose of collation. When
* `true`, characters with accents and other diacritics will be collated in the order defined by the locale specified
* in {@link organizeImportsCollationLocale}.
*
* This preference is ignored if {@link organizeImportsCollation} is not `"unicode"`.
*
* Default: `true`
*/
readonly organizeImportsAccentCollation?: boolean;
/**
* Indicates whether upper case or lower case should sort first. When `false`, the default order for the locale
* specified in {@link organizeImportsCollationLocale} is used.
*
* This preference is ignored if {@link organizeImportsCollation} is not `"unicode"`. This preference is also
* ignored if we are using case-insensitive sorting, which occurs when {@link organizeImportsIgnoreCase} is `true`,
* or if {@link organizeImportsIgnoreCase} is `"auto"` and the auto-detected case sensitivity is determined to be
* case-insensitive.
*
* Default: `false`
*/
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
/**
* Indicates whether {@link ReferencesResponseItem.lineText} is supported.
*/
@ -8430,6 +8482,11 @@ declare namespace ts {
readonly allowRenameOfImportPath?: boolean;
readonly autoImportFileExcludePatterns?: string[];
readonly organizeImportsIgnoreCase?: "auto" | boolean;
readonly organizeImportsCollation?: "ordinal" | "unicode";
readonly organizeImportsLocale?: string;
readonly organizeImportsNumericCollation?: boolean;
readonly organizeImportsAccentCollation?: boolean;
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
}
/** Represents a bigint literal value without requiring bigint support */
interface PseudoBigInt {

View File

@ -4489,6 +4489,11 @@ declare namespace ts {
readonly allowRenameOfImportPath?: boolean;
readonly autoImportFileExcludePatterns?: string[];
readonly organizeImportsIgnoreCase?: "auto" | boolean;
readonly organizeImportsCollation?: "ordinal" | "unicode";
readonly organizeImportsLocale?: string;
readonly organizeImportsNumericCollation?: boolean;
readonly organizeImportsAccentCollation?: boolean;
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
}
/** Represents a bigint literal value without requiring bigint support */
interface PseudoBigInt {

View File

@ -674,6 +674,11 @@ declare namespace FourSlashInterface {
readonly allowRenameOfImportPath?: boolean;
readonly autoImportFileExcludePatterns?: readonly string[];
readonly organizeImportsIgnoreCase?: "auto" | boolean;
readonly organizeImportsCollation?: "unicode" | "ordinal";
readonly organizeImportsLocale?: string;
readonly organizeImportsNumericCollation?: boolean;
readonly organizeImportsAccentCollation?: boolean;
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
}
interface InlayHintsOptions extends UserPreferences {
readonly includeInlayParameterNameHints?: "none" | "literals" | "all";

View File

@ -0,0 +1,53 @@
/// <reference path="fourslash.ts" />
// @Filename: /a.ts
////export const _aB: number;
////export const _Ab: number;
////export const aB: number;
////export const Ab: number;
// @Filename: /b.ts
////[|import {
//// _aB,
//// _Ab,
//// Ab,
////} from "./a";
////aB;|]
// @Filename: /c.ts
////[|import {
//// _aB,
//// _Ab,
//// Ab,
////} from "./a";
////aB;|]
// the import in 'b.ts' isn't sorted per ordinal comparison, so the import is added to the end of the list
goTo.file("/b.ts");
verify.importFixAtPosition([
`import {
_aB,
_Ab,
Ab,
aB,
} from "./a";
aB;`,
], undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "ordinal"
});
// the import in 'c.ts' *is* sorted per natural collation, so the import is added before `Ab`
goTo.file("/c.ts");
verify.importFixAtPosition([
`import {
_aB,
_Ab,
aB,
Ab,
} from "./a";
aB;`,
], undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode"
});

View File

@ -0,0 +1,30 @@
/// <reference path="fourslash.ts" />
//// import * as Ab from "./Ab";
//// import * as _aB from "./_aB";
//// import * as aB from "./aB";
//// import * as _Ab from "./_Ab";
////
//// console.log(_aB, _Ab, aB, Ab);
verify.organizeImports(
`import * as Ab from "./Ab";
import * as _Ab from "./_Ab";
import * as _aB from "./_aB";
import * as aB from "./aB";
console.log(_aB, _Ab, aB, Ab);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "ordinal",
});
verify.organizeImports(
`import * as _aB from "./_aB";
import * as _Ab from "./_Ab";
import * as aB from "./aB";
import * as Ab from "./Ab";
console.log(_aB, _Ab, aB, Ab);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
});

View File

@ -0,0 +1,29 @@
/// <reference path="fourslash.ts" />
//// import * as a2 from "./a2";
//// import * as a100 from "./a100";
//// import * as a1 from "./a1";
////
//// console.log(a1, a2, a100);
verify.organizeImports(
`import * as a1 from "./a1";
import * as a100 from "./a100";
import * as a2 from "./a2";
console.log(a1, a2, a100);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsNumericCollation: false,
});
verify.organizeImports(
`import * as a1 from "./a1";
import * as a2 from "./a2";
import * as a100 from "./a100";
console.log(a1, a2, a100);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsNumericCollation: true,
});

View File

@ -0,0 +1,29 @@
/// <reference path="fourslash.ts" />
//// import * as B from "./B";
//// import * as À from "./À";
//// import * as A from "./A";
////
//// console.log(A, À, B);
verify.organizeImports(
`import * as À from "./À";
import * as A from "./A";
import * as B from "./B";
console.log(A, À, B);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsAccentCollation: false,
});
verify.organizeImports(
`import * as A from "./A";
import * as À from "./À";
import * as B from "./B";
console.log(A, À, B);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsAccentCollation: true,
});

View File

@ -0,0 +1,32 @@
/// <reference path="fourslash.ts" />
//// import * as Ab from "./Ab";
//// import * as _aB from "./_aB";
//// import * as aB from "./aB";
//// import * as _Ab from "./_Ab";
////
//// console.log(_aB, _Ab, aB, Ab);
verify.organizeImports(
`import * as _Ab from "./_Ab";
import * as _aB from "./_aB";
import * as Ab from "./Ab";
import * as aB from "./aB";
console.log(_aB, _Ab, aB, Ab);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsCaseFirst: "upper",
});
verify.organizeImports(
`import * as _aB from "./_aB";
import * as _Ab from "./_Ab";
import * as aB from "./aB";
import * as Ab from "./Ab";
console.log(_aB, _Ab, aB, Ab);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsCaseFirst: "lower",
});

View File

@ -0,0 +1,36 @@
/// <reference path="fourslash.ts" />
//// import {
//// Ab,
//// _aB,
//// aB,
//// _Ab,
//// } from './foo';
////
//// console.log(_aB, _Ab, aB, Ab);
verify.organizeImports(
`import {
Ab,
_Ab,
_aB,
aB,
} from './foo';
console.log(_aB, _Ab, aB, Ab);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "ordinal",
});
verify.organizeImports(
`import {
_aB,
_Ab,
aB,
Ab,
} from './foo';
console.log(_aB, _Ab, aB, Ab);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
});

View File

@ -0,0 +1,35 @@
/// <reference path="fourslash.ts" />
//// import {
//// a2,
//// a100,
//// a1,
//// } from './foo';
////
//// console.log(a1, a2, a100);
verify.organizeImports(
`import {
a1,
a100,
a2,
} from './foo';
console.log(a1, a2, a100);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsNumericCollation: false,
});
verify.organizeImports(
`import {
a1,
a2,
a100,
} from './foo';
console.log(a1, a2, a100);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsNumericCollation: true,
});

View File

@ -0,0 +1,35 @@
/// <reference path="fourslash.ts" />
//// import {
//// B,
//// À,
//// A,
//// } from './foo';
////
//// console.log(A, À, B);
verify.organizeImports(
`import {
À,
A,
B,
} from './foo';
console.log(A, À, B);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsAccentCollation: false,
});
verify.organizeImports(
`import {
A,
À,
B,
} from './foo';
console.log(A, À, B);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsAccentCollation: true,
});

View File

@ -0,0 +1,38 @@
/// <reference path="fourslash.ts" />
//// import {
//// Ab,
//// _aB,
//// aB,
//// _Ab,
//// } from './foo';
////
//// console.log(_aB, _Ab, aB, Ab);
verify.organizeImports(
`import {
_Ab,
_aB,
Ab,
aB,
} from './foo';
console.log(_aB, _Ab, aB, Ab);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsCaseFirst: "upper",
});
verify.organizeImports(
`import {
_aB,
_Ab,
aB,
Ab,
} from './foo';
console.log(_aB, _Ab, aB, Ab);`, /*mode*/ undefined, {
organizeImportsIgnoreCase: false,
organizeImportsCollation: "unicode",
organizeImportsCaseFirst: "lower",
});