Skip to content
Open
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
16 changes: 16 additions & 0 deletions path/relative_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ const relativeTests = {
["\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"],
["C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"],
["\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"],
// Lowercasing can change a path's length ("İ" becomes "i̇"), see
// https://github.com/denoland/std/issues/7336
[
"C:\\İPTV\\player",
"C:\\İPTV\\player\\node_modules\\zod\\index.ts",
"node_modules\\zod\\index.ts",
],
["C:\\İPTV\\player", "C:\\İPTV\\player\\src\\main.ts", "src\\main.ts"],
["C:\\İ\\İ\\a", "C:\\İ\\İ\\a\\bcd.ts", "bcd.ts"],
["c:\\a\\İ", "c:\\a\\İ\\test.txt", "test.txt"],
["c:\\İ\\a\\İ", "c:\\İ\\b\\İ\\test.txt", "..\\..\\b\\İ\\test.txt"],
["c:\\i̇\\a\\İ", "c:\\İ\\b\\İ\\test.txt", "..\\..\\b\\İ\\test.txt"],
["c:\\ß\\a\\ß", "c:\\ß\\b\\ß\\test.txt", "..\\..\\b\\ß\\test.txt"],
["C:\\", "C:\\İPTV", "İPTV"],
["C:\\İPTV\\player", "C:\\", "..\\.."],
["C:\\İPTV\\player", "D:\\other\\target", "D:\\other\\target"],
] as const,
// arguments result
posix: [
Expand Down
37 changes: 37 additions & 0 deletions path/windows/relative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,43 @@ export function relative(from: string, to: string): string {

if (from === to) return "";

// Lowercasing can change the length of a path ("İ" becomes "i̇", which is
// two code units), so indexes found in the lowercased strings don't line up
// with the original paths. Fall back to comparing path segments, as Node.js
// does in https://github.com/nodejs/node/pull/53991
if (fromOrig.length !== from.length || toOrig.length !== to.length) {
const fromSegments = fromOrig.split("\\");
const toSegments = toOrig.split("\\");
if (fromSegments[fromSegments.length - 1] === "") fromSegments.pop();
if (toSegments[toSegments.length - 1] === "") toSegments.pop();

const fromSegmentCount = fromSegments.length;
const toSegmentCount = toSegments.length;
const maxSharedSegments = fromSegmentCount < toSegmentCount
? fromSegmentCount
: toSegmentCount;

let sharedSegments = 0;
for (; sharedSegments < maxSharedSegments; sharedSegments++) {
const fromSegment = fromSegments[sharedSegments]!;
const toSegment = toSegments[sharedSegments]!;
if (fromSegment.toLowerCase() !== toSegment.toLowerCase()) break;
}

if (sharedSegments === 0) {
return toOrig;
} else if (sharedSegments === maxSharedSegments) {
if (toSegmentCount > maxSharedSegments) {
return toSegments.slice(sharedSegments).join("\\");
}
// Equal-length paths with all segments shared would have returned above.
return "..\\".repeat(fromSegmentCount - 1 - sharedSegments) + "..";
}

return "..\\".repeat(fromSegmentCount - sharedSegments) +
toSegments.slice(sharedSegments).join("\\");
}

// Trim any leading backslashes
let fromStart = 0;
let fromEnd = from.length;
Expand Down
Loading