mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
Organize imports collation (#52115)
This commit is contained in:
parent
abd6cb446f
commit
20182cf848
@ -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,
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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] ?
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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";
|
||||
|
||||
53
tests/cases/fourslash/importNameCodeFix_order2.ts
Normal file
53
tests/cases/fourslash/importNameCodeFix_order2.ts
Normal 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"
|
||||
});
|
||||
30
tests/cases/fourslash/organizeImportsPathsUnicode1.ts
Normal file
30
tests/cases/fourslash/organizeImportsPathsUnicode1.ts
Normal 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",
|
||||
});
|
||||
29
tests/cases/fourslash/organizeImportsPathsUnicode2.ts
Normal file
29
tests/cases/fourslash/organizeImportsPathsUnicode2.ts
Normal 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,
|
||||
});
|
||||
29
tests/cases/fourslash/organizeImportsPathsUnicode3.ts
Normal file
29
tests/cases/fourslash/organizeImportsPathsUnicode3.ts
Normal 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,
|
||||
});
|
||||
32
tests/cases/fourslash/organizeImportsPathsUnicode4.ts
Normal file
32
tests/cases/fourslash/organizeImportsPathsUnicode4.ts
Normal 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",
|
||||
});
|
||||
36
tests/cases/fourslash/organizeImportsUnicode1.ts
Normal file
36
tests/cases/fourslash/organizeImportsUnicode1.ts
Normal 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",
|
||||
});
|
||||
35
tests/cases/fourslash/organizeImportsUnicode2.ts
Normal file
35
tests/cases/fourslash/organizeImportsUnicode2.ts
Normal 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,
|
||||
});
|
||||
35
tests/cases/fourslash/organizeImportsUnicode3.ts
Normal file
35
tests/cases/fourslash/organizeImportsUnicode3.ts
Normal 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,
|
||||
});
|
||||
38
tests/cases/fourslash/organizeImportsUnicode4.ts
Normal file
38
tests/cases/fourslash/organizeImportsUnicode4.ts
Normal 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",
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user