-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Re-add
importOrderCaseSensitive
option (#184)
Implements a custom natural sort algorithm which allows for numeric natural sorting while also sorting all uppercase letters before all lowercase letters, which is the desired behavior here. For example, when `importOrderCaseSensitive` is false (the default): ```js import ExampleComponent from './ExampleComponent'; import ExamplesList from './ExamplesList'; import ExampleWidget from './ExampleWidget'; ``` Compared with `"importOrderCaseSensitive": true`: ```js import ExampleComponent from './ExampleComponent'; import ExampleWidget from './ExampleWidget'; import ExamplesList from './ExamplesList'; ``` Closes #151 --------- Co-authored-by: Ian VanSchooten <[email protected]>
- Loading branch information
Showing
17 changed files
with
334 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,115 @@ | ||
import { expect, test } from 'vitest'; | ||
import { describe, expect, test } from 'vitest'; | ||
|
||
import { naturalSort } from '..'; | ||
import { naturalSort, naturalSortCaseSensitive } from '..'; | ||
|
||
test('should sort normal things alphabetically', () => { | ||
expect( | ||
['a', 'h', 'b', 'i', 'c', 'd', 'j', 'e', 'k', 'f', 'g'].sort((a, b) => | ||
naturalSort(a, b), | ||
), | ||
).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']); | ||
}); | ||
describe('naturalSort', () => { | ||
test('should sort normal things alphabetically', () => { | ||
expect( | ||
['a', 'h', 'b', 'i', 'c', 'd', 'j', 'e', 'k', 'f', 'g'].sort( | ||
(a, b) => naturalSort(a, b), | ||
), | ||
).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']); | ||
}); | ||
|
||
test('should ignore capitalization differences', () => { | ||
expect( | ||
['./ExampleComponent', './ExamplesList', './ExampleWidget'].sort( | ||
(a, b) => naturalSort(a, b), | ||
), | ||
).toEqual(['./ExampleComponent', './ExamplesList', './ExampleWidget']); | ||
}); | ||
|
||
test('should ignore capitalization differences', () => { | ||
// We have no option to cause case-sensitive sorting, so this is the "default" case! | ||
expect( | ||
['./ExampleView', './ExamplesList'].sort((a, b) => naturalSort(a, b)), | ||
).toEqual(['./ExamplesList', './ExampleView']); | ||
test('should sort things numerically', () => { | ||
expect( | ||
[ | ||
'a2', | ||
'a3', | ||
'a10', | ||
'a1', | ||
'a11', | ||
'a9', | ||
'a1b', | ||
'file000b', | ||
'file000a', | ||
'file00a', | ||
'file00z', | ||
].sort(naturalSort), | ||
).toEqual([ | ||
'a1', | ||
'a1b', | ||
'a2', | ||
'a3', | ||
'a9', | ||
'a10', | ||
'a11', | ||
'file000a', | ||
'file00a', | ||
'file000b', | ||
'file00z', | ||
]); | ||
}); | ||
}); | ||
|
||
test('should sort things numerically', () => { | ||
expect( | ||
['a2', 'a3', 'a10', 'a1', 'a11', 'a9'].sort((a, b) => | ||
naturalSort(a, b), | ||
), | ||
).toEqual(['a1', 'a2', 'a3', 'a9', 'a10', 'a11']); | ||
describe('naturalSortCaseSensitive', () => { | ||
test('should not ignore capitalization differences', () => { | ||
expect( | ||
['./ExampleComponent', './ExamplesList', './ExampleWidget'].sort( | ||
(a, b) => naturalSortCaseSensitive(a, b), | ||
), | ||
).toEqual(['./ExampleComponent', './ExampleWidget', './ExamplesList']); | ||
}); | ||
|
||
test('should sort numerically and case-sensitively', () => { | ||
expect( | ||
[ | ||
'file1', | ||
'File10', | ||
'AbA', | ||
'file10', | ||
'files10', | ||
'file1z', | ||
'file10ab', | ||
'file2s', | ||
'a', | ||
'Ab', | ||
'file20', | ||
'file22', | ||
'file11', | ||
'file2', | ||
'File20', | ||
'file000b', | ||
'file000a', | ||
'file00a', | ||
'file00z', | ||
'aaa', | ||
'AAA', | ||
'bBb', | ||
'BBB', | ||
].sort(naturalSortCaseSensitive), | ||
).toEqual([ | ||
'AAA', | ||
'Ab', | ||
'AbA', | ||
'BBB', | ||
'File10', | ||
'File20', | ||
'a', | ||
'aaa', | ||
'bBb', | ||
'file000a', | ||
'file00a', | ||
'file000b', | ||
'file00z', | ||
'file1', | ||
'file1z', | ||
'file2', | ||
'file2s', | ||
'file10', | ||
'file10ab', | ||
'file11', | ||
'file20', | ||
'file22', | ||
'files10', | ||
]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,47 @@ | ||
export function naturalSort(a: string, b: string): number { | ||
const left = typeof a === 'string' ? a : String(a); | ||
|
||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#syntax | ||
const sortOptions: Intl.CollatorOptions = { | ||
sensitivity: 'base', | ||
numeric: true, | ||
caseFirst: 'lower', | ||
}; | ||
|
||
return left.localeCompare(b, 'en', sortOptions); | ||
} | ||
|
||
/** | ||
* Using a custom comparison function here, as `String.localeCompare` does not | ||
* support sorting characters with all uppercase letters before lowercase | ||
* letters, which is the desired behavior for a case-sensitive import sort. When | ||
* `sensitivity` is set to `base`, `String.localeCompare` sorts alphabetically | ||
* and then by case, but we want to sort by case first (then alphabetical). | ||
*/ | ||
const numericRegex = /^\d+/; | ||
export function naturalSortCaseSensitive(a: string, b: string) { | ||
let aIndex = 0; | ||
let bIndex = 0; | ||
while (aIndex < Math.max(a.length, b.length)) { | ||
// check if we've encountered a number and compare appropriately if so | ||
const aNumericMatch = a.slice(aIndex).match(numericRegex); | ||
const bNumericMatch = b.slice(bIndex).match(numericRegex); | ||
if (aNumericMatch && !bNumericMatch) return -1; | ||
if (!aNumericMatch && bNumericMatch) return 1; | ||
if (aNumericMatch && bNumericMatch) { | ||
const aNumber = parseInt(aNumericMatch[0]); | ||
const bNumber = parseInt(bNumericMatch[0]); | ||
if (aNumber > bNumber) return 1; | ||
if (aNumber < bNumber) return -1; | ||
aIndex += aNumericMatch[0].length; | ||
bIndex += bNumericMatch[0].length; | ||
} | ||
// otherwise just compare characters directly | ||
const aChar = a[aIndex]; | ||
const bChar = b[bIndex]; | ||
if (aChar && !bChar) return 1; | ||
if (!aChar && bChar) return -1; | ||
if (aChar !== bChar) return aChar.charCodeAt(0) - bChar.charCodeAt(0); | ||
aIndex++; | ||
bIndex++; | ||
} | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.