Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1356,7 +1356,10 @@ function sortName(value) {
return name;
}

const natural = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}).compare;
// The same order everywhere: a fixed locale, as Node follows the locale of the user and Bun does not,
// and ascii for the names natural sees as the same, like file1 and file01, so the order of the glob does not decide
const collator = new Intl.Collator('en', {numeric: true, sensitivity: 'base'}).compare;
const natural = (a, b) => collator(a, b) || ascii(a, b);
const ascii = (a, b) => (a < b ? -1 : a > b ? 1 : 0);

function sortFiles(files, sort, conf) {
Expand Down
6 changes: 6 additions & 0 deletions test/cli/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ assert_raises "rexreplace 'oo' 'x' nope.file --expect-files" 1
reset
assert "rexreplace 'b' 'x' my.file your.file --sort-reverse --name-delimiter ','" 'your.file,my.file,'

# The natural order is the same on Node and Bun, and in every locale. In Danish aa sorts after z.
reset
echo 'foo' > aab.file
echo 'foo' > b.file
assert "LC_ALL=da_DK.UTF-8 rexreplace 'foo' 'x' b.file aab.file --sort natural --simulate --name-delimiter ','" 'aab.file,b.file,'


# --full-path prints the full path of each name
reset
Expand Down
42 changes: 42 additions & 0 deletions test/fix/820.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// https://github.com/mathiasrw/rexreplace/issues/820
// Names that natural sees as the same kept the order of the glob, which differs between Node and Bun,
// and natural followed the locale of the user on Node, but not on Bun. The locale is checked in test/cli/run.sh.
import {describe, test, expect} from 'bun:test';
import fs from 'fs';
import {rr} from '../helper.js';

async function sorted(names, config = {}) {
names.forEach(name => fs.writeFileSync(name, 'foo'));
const result = await rr('foo', 'bar', {
includeGlob: ['*.md'],
simulate: true,
sort: 'natural',
...config,
});
return result.out.split('\n').filter(Boolean);
}

describe('#820 the natural order', () => {
test.each([
['numbers as numbers', ['file10.md', 'file9.md'], ['file9.md', 'file10.md']],
['case ignored', ['B.md', 'a.md'], ['a.md', 'B.md']],
['accents ignored', ['cafe2.md', 'café1.md'], ['café1.md', 'cafe2.md']],
['aa as two a, like in English', ['b.md', 'aab.md'], ['aab.md', 'b.md']],
[
'ascii for the names it sees as the same',
['file1.md', 'file01.md', 'file001.md', 'cafe.md', 'café.md'],
['cafe.md', 'café.md', 'file001.md', 'file01.md', 'file1.md'],
],
])('has %s', async (name, names, order) => {
expect(await sorted(names)).toEqual(order);
});

test('is turned around with --sort-reverse, also for the names it sees as the same', async () => {
expect(await sorted(['file1.md', 'file01.md', 'B.md', 'a.md'], {sortReverse: true})).toEqual([
'file1.md',
'file01.md',
'B.md',
'a.md',
]);
});
});
Loading