-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add option for organize imports case sensitivity #51733
Changes from 8 commits
6f21600
7ae6020
d4d0844
a9eb9cf
289276b
005c08d
a3a6cd4
3c34aab
54d350f
4257fe4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -944,16 +944,54 @@ 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 (caseSensitiveComparer(prevElement, element) === Comparison.GreaterThan) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be more obvious to me what was going on if this was something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. “less than or equal to” can’t be represented in a single value the way our export const enum Comparison {
LessThan = -1,
EqualTo = 0,
GreaterThan = 1
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah - just an idle sort of "it would be nice". |
||||
kind &= ~SortKind.CaseSensitive; | ||||
} | ||||
if (caseInsensitiveComparer(prevElement, element) === Comparison.GreaterThan) { | ||||
kind &= ~SortKind.CaseInsensitive; | ||||
} | ||||
if (kind === SortKind.None) { | ||||
return kind; | ||||
} | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that you can optimize this.
That means the only times you ever have to do two checks for a pair of elements is the first pair that forces you to transition to only checking for case-insensitive comparison. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point on (1), but (2) is not true. It sure sounds like the “case-insensitively sorted lists” ought to be a superset of “case-sensitively sorted lists,” doesn’t it? They’re actually a classic Venn diagram shape where there is a non-empty intersection but neither is a subset of the other. When Jake was first trying to explain the problem to me verbally, I made the same mistake. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh, why was I so convinced this was true? But yes, I see that. At least you can avoid each kind of comparison after the first failure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Quiz ChatGPT about this for a good laugh) |
||||
} | ||||
return kind; | ||||
} | ||||
|
||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
|
||||
/** @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 +2182,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>(); | ||||
andrewbranch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
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 +2348,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. | ||||
* | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:yikes: nice catch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DanielRosenwasser noticed this while I was screensharing working on this. Turns out I wrote it years ago, before I was terrified of allocations 🙈