Skip to content

Commit 1d09214

Browse files
isaacswraithgar
authored andcommitted
fix(packages): locale-agnostic string sorting
This adds the 'en' locale to all instances of String.localeCompare within the CLI codebase. Tests added for the cases where we're sorting arbitrary user-generated data. The tests rely on the fact that 'ch' sorts after 'd' in the `'sk'` locale, but ahead of `'d'` in the `'en'` locale. To ensure that this is the default behavior if no locale is specified, `LC_ALL=sk` is set in the test environment. Other instances of `localeCompare` sort data that the cli controls, so no tests were added. Re: #2829 PR-URL: #3203 Credit: @isaacs Close: #3203 Reviewed-by: @ruyadorno
1 parent 1eb7e5c commit 1d09214

24 files changed

+363
-327
lines changed

lib/config.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ class Config extends BaseCommand {
199199
; Configs like \`//<hostname>/:_authToken\` are auth that is restricted
200200
; to the registry host specified.
201201
202-
${data.split('\n').sort((a, b) => a.localeCompare(b)).join('\n').trim()}
202+
${data.split('\n').sort((a, b) => a.localeCompare(b, 'en')).join('\n').trim()}
203203
204204
;;;;
205205
; all available options shown below with default values
@@ -227,7 +227,7 @@ ${defData}
227227
if (where === 'default' && !long)
228228
continue
229229

230-
const keys = Object.keys(data).sort((a, b) => a.localeCompare(b))
230+
const keys = Object.keys(data).sort((a, b) => a.localeCompare(b, 'en'))
231231
if (!keys.length)
232232
continue
233233

lib/help.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class Help extends BaseCommand {
7777
if (aManNumber !== bManNumber)
7878
return aManNumber - bManNumber
7979

80-
return a.localeCompare(b)
80+
return a.localeCompare(b, 'en')
8181
})
8282
const man = mans[0]
8383

lib/ls.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ const augmentNodesWithMetadata = ({
443443
}
444444

445445
const sortAlphabetically = (a, b) =>
446-
a.pkgid.localeCompare(b.pkgid)
446+
a.pkgid.localeCompare(b.pkgid, 'en')
447447

448448
const humanOutput = ({ color, result, seenItems, unicode }) => {
449449
// we need to traverse the entire tree in order to determine which items

lib/outdated.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class Outdated extends BaseCommand {
6868
}))
6969

7070
// sorts list alphabetically
71-
const outdated = this.list.sort((a, b) => a.name.localeCompare(b.name))
71+
const outdated = this.list.sort((a, b) => a.name.localeCompare(b.name, 'en'))
7272

7373
// return if no outdated packages
7474
if (outdated.length === 0 && !this.npm.config.get('json'))

lib/utils/completion/installed-deep.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const installedDeep = async (npm) => {
1616
})
1717
.filter(i => (i.depth - 1) <= depth)
1818
.sort((a, b) => a.depth - b.depth)
19-
.sort((a, b) => a.depth === b.depth ? a.name.localeCompare(b.name) : 0)
19+
.sort((a, b) => a.depth === b.depth ? a.name.localeCompare(b.name, 'en') : 0)
2020

2121
const res = new Set()
2222
const gArb = new Arborist({ global: true, path: resolve(npm.globalDir, '..') })

lib/utils/config/describe-all.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const describeAll = () => {
77
const sort = ([keya, {deprecated: depa}], [keyb, {deprecated: depb}]) => {
88
return depa && !depb ? 1
99
: !depa && depb ? -1
10-
: keya.localeCompare(keyb)
10+
: keya.localeCompare(keyb, 'en')
1111
}
1212
return Object.entries(definitions).sort(sort)
1313
.map(([key, def]) => def.describe())

lib/utils/npm-usage.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const usages = (npm) => {
6262
maxLen = Math.max(maxLen, c.length)
6363
return set
6464
}, [])
65-
.sort((a, b) => a[0].localeCompare(b[0]))
65+
.sort((a, b) => a[0].localeCompare(b[0], 'en'))
6666
.map(([c, usage]) => `\n ${c}${' '.repeat(maxLen - c.length + 1)}${
6767
(usage.split('\n').join('\n' + ' '.repeat(maxLen + 5)))}`)
6868
.join('\n')

lib/utils/tar.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,15 @@ const getContents = async (manifest, tarball) => {
7676
})
7777

7878
const comparator = (a, b) => {
79-
return a.path.localeCompare(b.path, undefined, {
79+
return a.path.localeCompare(b.path, 'en', {
8080
sensitivity: 'case',
8181
numeric: true,
8282
})
8383
}
8484

8585
const isUpper = (str) => {
8686
const ch = str.charAt(0)
87-
return ch >= 'A' && ch <= 'Z'
87+
return ch === ch.toUpperCase()
8888
}
8989

9090
const uppers = files.filter(file => isUpper(file.path))

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@
216216
"Remove the 'files' below once we're done porting old tests over"
217217
],
218218
"tap": {
219+
"test-env": [
220+
"LC_ALL=sk"
221+
],
219222
"color": 1,
220223
"files": "test/{lib,bin}",
221224
"coverage-map": "test/coverage-map.js",

scripts/bundle-and-gitignore-deps.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ arb.loadVirtual().then(tree => {
1818
bundle.push(node.name)
1919
}
2020
}
21-
pkg.bundleDependencies = bundle.sort((a, b) => a.localeCompare(b))
21+
pkg.bundleDependencies = bundle.sort((a, b) => a.localeCompare(b, 'en'))
2222

23-
const ignores = shouldIgnore.sort((a, b) => a.localeCompare(b))
23+
const ignores = shouldIgnore.sort((a, b) => a.localeCompare(b, 'en'))
2424
.map(i => `/${i}`)
2525
.join('\n')
2626
const ignoreData = `# Automatically generated to ignore dev deps

scripts/config-doc.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ const addShorthands = doc => {
3535
const body = Object.entries(shorthands)
3636
.sort(([shorta, expansiona], [shortb, expansionb]) => {
3737
// sort by what they're short FOR
38-
return expansiona.join(' ').localeCompare(expansionb.join(' ')) ||
39-
shorta.localeCompare(shortb)
38+
return expansiona.join(' ').localeCompare(expansionb.join(' '), 'en') ||
39+
shorta.localeCompare(shortb, 'en')
4040
})
4141
.map(([short, expansion]) => {
4242
const dash = short.length === 1 ? '-' : '--'

tap-snapshots/test/lib/config.js.test.cjs

+12
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ exports[`test/lib/config.js TAP config edit > should write config file 2`] = `
8888
exports[`test/lib/config.js TAP config get no args > should list configs on config get no args 1`] = `
8989
; "cli" config from command line options
9090
91+
cat = true
92+
chai = true
93+
dog = true
9194
editor = "vi"
9295
global = false
9396
json = false
@@ -109,6 +112,9 @@ init.version = "1.0.0"
109112
110113
; "cli" config from command line options
111114
115+
cat = true
116+
chai = true
117+
dog = true
112118
editor = "vi"
113119
global = false
114120
json = false
@@ -118,6 +124,9 @@ long = true
118124
exports[`test/lib/config.js TAP config list > should list configs 1`] = `
119125
; "cli" config from command line options
120126
127+
cat = true
128+
chai = true
129+
dog = true
121130
editor = "vi"
122131
global = false
123132
json = false
@@ -132,6 +141,9 @@ long = false
132141
exports[`test/lib/config.js TAP config list overrides > should list overridden configs 1`] = `
133142
; "cli" config from command line options
134143
144+
cat = true
145+
chai = true
146+
dog = true
135147
editor = "vi"
136148
global = false
137149
init.author.name = "Bar"

0 commit comments

Comments
 (0)