fix(app): preserve native path separators in file path helpers (#14912)

This commit is contained in:
Luke Parker
2026-02-25 00:03:15 +10:00
committed by GitHub
parent 2cee947671
commit 082f0cc127
2 changed files with 13 additions and 15 deletions

View File

@@ -15,10 +15,10 @@ describe("file path helpers", () => {
test("normalizes Windows absolute paths with mixed separators", () => { test("normalizes Windows absolute paths with mixed separators", () => {
const path = createPathHelpers(() => "C:\\repo") const path = createPathHelpers(() => "C:\\repo")
expect(path.normalize("C:\\repo\\src\\app.ts")).toBe("src/app.ts") expect(path.normalize("C:\\repo\\src\\app.ts")).toBe("src\\app.ts")
expect(path.normalize("C:/repo/src/app.ts")).toBe("src/app.ts") expect(path.normalize("C:/repo/src/app.ts")).toBe("src/app.ts")
expect(path.normalize("file://C:/repo/src/app.ts")).toBe("src/app.ts") expect(path.normalize("file://C:/repo/src/app.ts")).toBe("src/app.ts")
expect(path.normalize("c:\\repo\\src\\app.ts")).toBe("src/app.ts") expect(path.normalize("c:\\repo\\src\\app.ts")).toBe("src\\app.ts")
}) })
test("keeps query/hash stripping behavior stable", () => { test("keeps query/hash stripping behavior stable", () => {

View File

@@ -103,32 +103,30 @@ export function encodeFilePath(filepath: string): string {
export function createPathHelpers(scope: () => string) { export function createPathHelpers(scope: () => string) {
const normalize = (input: string) => { const normalize = (input: string) => {
const root = scope().replace(/\\/g, "/") const root = scope()
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input)))).replace(/\\/g, "/") let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input))))
// Remove initial root prefix, if it's a complete match or followed by / // Separator-agnostic prefix stripping for Cygwin/native Windows compatibility
// (don't want /foo/bar to root of /f). // Only case-insensitive on Windows (drive letter or UNC paths)
// For Windows paths, also check for case-insensitive match. const windows = /^[A-Za-z]:/.test(root) || root.startsWith("\\\\")
const windows = /^[A-Za-z]:/.test(root) const canonRoot = windows ? root.replace(/\\/g, "/").toLowerCase() : root.replace(/\\/g, "/")
const canonRoot = windows ? root.toLowerCase() : root const canonPath = windows ? path.replace(/\\/g, "/").toLowerCase() : path.replace(/\\/g, "/")
const canonPath = windows ? path.toLowerCase() : path
if ( if (
canonPath.startsWith(canonRoot) && canonPath.startsWith(canonRoot) &&
(canonRoot.endsWith("/") || canonPath === canonRoot || canonPath.startsWith(canonRoot + "/")) (canonRoot.endsWith("/") || canonPath === canonRoot || canonPath[canonRoot.length] === "/")
) { ) {
// If we match canonRoot + "/", the slash will be removed below. // Slice from original path to preserve native separators
path = path.slice(root.length) path = path.slice(root.length)
} }
if (path.startsWith("./")) { if (path.startsWith("./") || path.startsWith(".\\")) {
path = path.slice(2) path = path.slice(2)
} }
if (path.startsWith("/")) { if (path.startsWith("/") || path.startsWith("\\")) {
path = path.slice(1) path = path.slice(1)
} }
return path return path
} }