fix(fls): archive path folding measured against Swift
41 path pairs replayed in the Swift core decide which spellings name the same file. Two corrections: full Unicode case folding (ß, ligatures, final sigma, micro, long s) instead of toLowerCase, and width folding of the whole Halfwidth and Fullwidth Forms block. Swift treats composed and decomposed spellings as one dictionary key; here the writer refuses both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// Which archive paths name the same file: the TypeScript folding against what
|
||||
// the Swift core decides for the same pairs (path-folding.swift.json, written
|
||||
// by tools/swift-oracle/run.sh; the Swift folding is Linux Foundation's).
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { FlsArchiveError, writeZipEntries } from '../src/index.js';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const cases = JSON.parse(readFileSync(join(here, 'path-folding.cases.json'), 'utf8')) as {
|
||||
name: string;
|
||||
first: string;
|
||||
second: string;
|
||||
}[];
|
||||
const swift = new Map(
|
||||
(JSON.parse(readFileSync(join(here, 'path-folding.swift.json'), 'utf8')) as { name: string; outcome: string }[]).map(
|
||||
(item) => [item.name, item.outcome],
|
||||
),
|
||||
);
|
||||
|
||||
function outcome(first: string, second: string): string {
|
||||
try {
|
||||
writeZipEntries(
|
||||
new Map([
|
||||
[first, Uint8Array.of(1)],
|
||||
[second, Uint8Array.of(2)],
|
||||
]),
|
||||
);
|
||||
return 'distinct';
|
||||
} catch (error) {
|
||||
if (error instanceof FlsArchiveError) return error.code === 'duplicateEntry' ? 'collide' : error.code;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
describe('archive path folding matches Swift', () => {
|
||||
it('has a Swift result for every case', () => {
|
||||
expect(cases.every((item) => swift.has(item.name))).toBe(true);
|
||||
});
|
||||
|
||||
for (const item of cases) {
|
||||
it(item.name, () => {
|
||||
// Swift strings compare by canonical equivalence, so a composed and a
|
||||
// decomposed spelling are one dictionary key there ("sameKey"). A Map
|
||||
// here keeps both — and the writer then refuses them as one file.
|
||||
const expected = swift.get(item.name) === 'sameKey' ? 'collide' : swift.get(item.name);
|
||||
expect(outcome(item.first, item.second)).toBe(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user