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:
+23
-5
@@ -112,21 +112,39 @@ export function validateEntryPath(path: string): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Folds full-width and half-width forms (U+FF01…U+FFEF) to their ordinary
|
||||
* counterparts — the part of NFKC that Foundation's `.widthInsensitive` does.
|
||||
* Foundation's `.widthInsensitive`: the Halfwidth and Fullwidth Forms block
|
||||
* (U+FF01…U+FFEE) folds to its NFKC form — full-width ASCII, half-width
|
||||
* katakana, half-width hangul (to conjoining jamo) and the symbols. Measured
|
||||
* against the Swift core (packages/fls/test/path-folding.*.json).
|
||||
*/
|
||||
function foldWidth(text: string): string {
|
||||
return text.replace(/[!-]/g, (character) => character.normalize('NFKC'));
|
||||
return text.replace(/[\uFF01-\uFFEE]/g, (character) => character.normalize('NFKC'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unicode full case folding, which JavaScript lacks: lower, upper, lower per
|
||||
* character gives it (`ß` → `ss`, `fi` → `fi`, `K` (Kelvin) → `k`, `ς` → `σ`,
|
||||
* `İ` → `i̇`). The one exception is `ı`, which default case folding leaves
|
||||
* alone and the round trip would turn into `i`.
|
||||
*/
|
||||
function foldCase(text: string): string {
|
||||
let folded = '';
|
||||
for (const character of text) {
|
||||
folded += character === '\u0131' ? character : character.toLowerCase().toUpperCase().toLowerCase();
|
||||
}
|
||||
return folded;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key two paths share when they would name the same file on a case- and
|
||||
* width-insensitive file system: NFC, width-folded, case-folded, per component.
|
||||
* width-insensitive file system: NFC, then width and case folding, per
|
||||
* component — `precomposedStringWithCanonicalMapping.folding([.caseInsensitive,
|
||||
* .widthInsensitive])` in Swift.
|
||||
*/
|
||||
export function canonicalEntryKey(path: string): string {
|
||||
return path
|
||||
.split('/')
|
||||
.map((component) => foldWidth(component.normalize('NFC')).toLowerCase())
|
||||
.map((component) => foldCase(foldWidth(component.normalize('NFC'))))
|
||||
.join('/');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user