From 8c4fd6d961b71d8903e3ed1a96d84648fc11fe2f Mon Sep 17 00:00:00 2001 From: Osamaali313 <86572800+Osamaali313@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:28:47 +0300 Subject: [PATCH] fix: do not root a relative path beginning with ./ followed by a slash simpleNormalizePath stripped a leading './' before checking whether the following character was itself a separator. For input like './/a', the '/./' cleanup is a no-op, then startsWith('./') + slice(2) removed './' and left '/a' (the second slash was a redundant separator, not a root). '/a' then re-tested clean and was returned, so normalizePath('.//a') and getNormalizedAbsolutePath('.//a', '') produced the rooted '/a' instead of the relative 'a'. The whole './/...' and '././/...' family was affected. Only strip the leading './' when it is an actual '.' segment, i.e. when the character after it is not another separator; otherwise fall through to the slow path, which normalizes correctly. Adds regression tests. --- src/compiler/path.ts | 5 ++++- src/testRunner/unittests/paths.ts | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/compiler/path.ts b/src/compiler/path.ts index a06359d51e549..4a4c0f91434c4 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -736,7 +736,10 @@ function simpleNormalizePath(path: string): string | undefined { } // Some paths only require cleanup of `/./` or leading `./` let simplified = path.replace(/\/\.\//g, "/"); - if (simplified.startsWith("./")) { + // Only strip a leading `./` when it is an actual `.` segment, not the + // start of a `.//` sequence where the second slash is a redundant + // separator (stripping there would turn a relative path into a rooted one). + if (simplified.startsWith("./") && simplified.charCodeAt(2) !== CharacterCodes.slash) { simplified = simplified.slice(2); } if (simplified !== path) { diff --git a/src/testRunner/unittests/paths.ts b/src/testRunner/unittests/paths.ts index 743e791baa181..8b65053281a7c 100644 --- a/src/testRunner/unittests/paths.ts +++ b/src/testRunner/unittests/paths.ts @@ -318,6 +318,10 @@ describe("unittests:: core paths", () => { assert.strictEqual(ts.getNormalizedAbsolutePath(".", ""), ""); assert.strictEqual(ts.getNormalizedAbsolutePath("./", ""), ""); assert.strictEqual(ts.getNormalizedAbsolutePath("./a", ""), "a"); + // A `./` followed by a redundant separator must stay relative, not become rooted. + assert.strictEqual(ts.getNormalizedAbsolutePath(".//a", ""), "a"); + assert.strictEqual(ts.getNormalizedAbsolutePath(".//a/b", ""), "a/b"); + assert.strictEqual(ts.getNormalizedAbsolutePath("././/a", ""), "a"); // Strangely, these do not normalize to the empty string. assert.strictEqual(ts.getNormalizedAbsolutePath("..", ""), ".."); assert.strictEqual(ts.getNormalizedAbsolutePath("../", ""), "..");