Add option for organize imports case sensitivity (#51733)

* Add ignore case option to organizeImports

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

* Fix build/lint

* Mark functions internal

* Update affected auto import test

* Update API baseline

* Update protocol

* Update API baseline

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

View File

@@ -944,16 +944,53 @@ export function sortAndDeduplicate<T>(array: readonly T[], comparer?: Comparer<T
/** @internal */
export function arrayIsSorted<T>(array: readonly T[], comparer: Comparer<T>) {
if (array.length < 2) return true;
let prevElement = array[0];
for (const element of array.slice(1)) {
if (comparer(prevElement, element) === Comparison.GreaterThan) {
for (let i = 1, len = array.length; i < len; i++) {
if (comparer(array[i - 1], array[i]) === Comparison.GreaterThan) {
return false;
}
prevElement = element;
}
return true;
}
/** @internal */
export const enum SortKind {
None = 0,
CaseSensitive = 1 << 0,
CaseInsensitive = 1 << 1,
Both = CaseSensitive | CaseInsensitive,
}
/** @internal */
export function detectSortCaseSensitivity(array: readonly string[], useEslintOrdering?: boolean): SortKind;
/** @internal */
export function detectSortCaseSensitivity<T>(array: readonly T[], useEslintOrdering: boolean, getString: (element: T) => string): SortKind;
/** @internal */
export function detectSortCaseSensitivity<T>(array: readonly T[], useEslintOrdering: boolean, getString?: (element: T) => string): SortKind {
let kind = SortKind.Both;
if (array.length < 2) return kind;
const caseSensitiveComparer = getString
? (a: T, b: T) => compareStringsCaseSensitive(getString(a), getString(b))
: compareStringsCaseSensitive as (a: T | undefined, b: T | undefined) => Comparison;
const compareCaseInsensitive = useEslintOrdering ? compareStringsCaseInsensitiveEslintCompatible : compareStringsCaseInsensitive;
const caseInsensitiveComparer = getString
? (a: T, b: T) => compareCaseInsensitive(getString(a), getString(b))
: compareCaseInsensitive as (a: T | undefined, b: T | undefined) => Comparison;
for (let i = 1, len = array.length; i < len; i++) {
const prevElement = array[i - 1];
const element = array[i];
if (kind & SortKind.CaseSensitive && caseSensitiveComparer(prevElement, element) === Comparison.GreaterThan) {
kind &= ~SortKind.CaseSensitive;
}
if (kind & SortKind.CaseInsensitive && caseInsensitiveComparer(prevElement, element) === Comparison.GreaterThan) {
kind &= ~SortKind.CaseInsensitive;
}
if (kind === SortKind.None) {
return kind;
}
}
return kind;
}
/** @internal */
export function arrayIsEqualTo<T>(array1: readonly T[] | undefined, array2: readonly T[] | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean {
if (!array1 || !array2) {
@@ -2144,6 +2181,23 @@ export function memoizeOne<A extends string | number | boolean | undefined, T>(c
};
}
/**
* A version of `memoize` that supports a single non-primitive argument, stored as keys of a WeakMap.
*
* @internal
*/
export function memoizeWeak<A extends object, T>(callback: (arg: A) => T): (arg: A) => T {
const map = new WeakMap<A, T>();
return (arg: A) => {
let value = map.get(arg);
if (value === undefined && !map.has(arg)) {
value = callback(arg);
map.set(arg, value);
}
return value!;
};
}
/**
* High-order function, composes functions. Note that functions are composed inside-out;
* for example, `compose(a, b)` is the equivalent of `x => b(a(x))`.
@@ -2293,6 +2347,27 @@ export function compareStringsCaseInsensitive(a: string, b: string) {
return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo;
}
/**
* `compareStringsCaseInsensitive` transforms letters to uppercase for unicode reasons,
* while eslint's `sort-imports` rule transforms letters to lowercase. Which one you choose
* affects the relative order of letters and ASCII characters 91-96, of which `_` is a
* valid character in an identifier. So if we used `compareStringsCaseInsensitive` for
* import sorting, TypeScript and eslint would disagree about the correct case-insensitive
* sort order for `__String` and `Foo`. Since eslint's whole job is to create consistency
* by enforcing nitpicky details like this, it makes way more sense for us to just adopt
* their convention so users can have auto-imports without making eslint angry.
*
* @internal
*/
export function compareStringsCaseInsensitiveEslintCompatible(a: string, b: string) {
if (a === b) return Comparison.EqualTo;
if (a === undefined) return Comparison.LessThan;
if (b === undefined) return Comparison.GreaterThan;
a = a.toLowerCase();
b = b.toLowerCase();
return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo;
}
/**
* Compare two strings using a case-sensitive ordinal comparison.
*

View File

@@ -9740,6 +9740,7 @@ export interface UserPreferences {
readonly includeInlayEnumMemberValueHints?: boolean;
readonly allowRenameOfImportPath?: boolean;
readonly autoImportFileExcludePatterns?: string[];
readonly organizeImportsIgnoreCase?: "auto" | boolean;
}
/** Represents a bigint literal value without requiring bigint support */