From 7060648e35334f07a19bfbb332410ad18e7c3e4f Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 9 Aug 2025 16:47:30 +0200 Subject: [PATCH 1/6] fix(fs/expand-glob): match non-glob path segments containing escaped glob chars --- fs/expand_glob.ts | 9 +++++++-- fs/expand_glob_test.ts | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/fs/expand_glob.ts b/fs/expand_glob.ts index ab0c93cf7b09..7968e5adf92b 100644 --- a/fs/expand_glob.ts +++ b/fs/expand_glob.ts @@ -303,7 +303,7 @@ export async function* expandGlob( let fixedRoot = isGlobAbsolute ? winRoot ?? "/" : absRoot; while (segments.length > 0 && !isGlob(segments[0]!)) { const seg = segments.shift()!; - fixedRoot = joinGlobs([fixedRoot, seg], globOptions); + fixedRoot = joinGlobs([fixedRoot, unescapeGlobSegment(seg)], globOptions); } let fixedRootInfo: WalkEntry; @@ -460,7 +460,7 @@ export function* expandGlobSync( let fixedRoot = isGlobAbsolute ? winRoot ?? "/" : absRoot; while (segments.length > 0 && !isGlob(segments[0]!)) { const seg = segments.shift()!; - fixedRoot = joinGlobs([fixedRoot, seg], globOptions); + fixedRoot = joinGlobs([fixedRoot, unescapeGlobSegment(seg)], globOptions); } let fixedRootInfo: WalkEntry; @@ -532,3 +532,8 @@ export function* expandGlobSync( } yield* currentMatches; } + +const globEscapeChar = Deno.build.os === "windows" ? "`" : `\\`; +function unescapeGlobSegment(segment: string): string { + return segment.replaceAll(globEscapeChar, ""); +} diff --git a/fs/expand_glob_test.ts b/fs/expand_glob_test.ts index 6a65e16043b9..b9dfdd5d57f1 100644 --- a/fs/expand_glob_test.ts +++ b/fs/expand_glob_test.ts @@ -444,3 +444,19 @@ Deno.test( ); }, ); + +Deno.test("expandGlob() finds directory with escaped brackets", async function () { + const escapeChar = Deno.build.os === "windows" ? "`" : "\\"; + const pattern = `a${escapeChar}[b${escapeChar}]c`; + assertEquals(await expandGlobArray(pattern, EG_OPTIONS), [ + "a[b]c", + ]); +}); + +Deno.test("expandGlobSync() finds directory with escaped brackets", function () { + const escapeChar = Deno.build.os === "windows" ? "`" : "\\"; + const pattern = `a${escapeChar}[b${escapeChar}]c`; + assertEquals(expandGlobSyncArray(pattern, EG_OPTIONS), [ + "a[b]c", + ]); +}); From 4fdc2895d2e5bcf33323d23b78731060e2a1de7b Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 9 Aug 2025 16:52:08 +0200 Subject: [PATCH 2/6] update --- fs/expand_glob_test.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/fs/expand_glob_test.ts b/fs/expand_glob_test.ts index b9dfdd5d57f1..8f9282fcda3d 100644 --- a/fs/expand_glob_test.ts +++ b/fs/expand_glob_test.ts @@ -445,18 +445,26 @@ Deno.test( }, ); +const escapeChar = Deno.build.os === "windows" ? "`" : "\\"; + Deno.test("expandGlob() finds directory with escaped brackets", async function () { - const escapeChar = Deno.build.os === "windows" ? "`" : "\\"; - const pattern = `a${escapeChar}[b${escapeChar}]c`; - assertEquals(await expandGlobArray(pattern, EG_OPTIONS), [ - "a[b]c", - ]); + assertEquals( + await expandGlobArray(`a${escapeChar}[b${escapeChar}]c`, EG_OPTIONS), + ["a[b]c"], + ); + assertEquals( + await expandGlobArray(`a${escapeChar}[b${escapeChar}]c/fo[o]`, EG_OPTIONS), + ["a[b]c/foo"], + ); }); Deno.test("expandGlobSync() finds directory with escaped brackets", function () { - const escapeChar = Deno.build.os === "windows" ? "`" : "\\"; - const pattern = `a${escapeChar}[b${escapeChar}]c`; - assertEquals(expandGlobSyncArray(pattern, EG_OPTIONS), [ - "a[b]c", - ]); + assertEquals( + expandGlobSyncArray(`a${escapeChar}[b${escapeChar}]c`, EG_OPTIONS), + ["a[b]c"], + ); + assertEquals( + expandGlobSyncArray(`a${escapeChar}[b${escapeChar}]c/fo[o]`, EG_OPTIONS), + ["a[b]c/foo"], + ); }); From 71af5c0bc5523f4358c0f947ee0865927662ef53 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 9 Aug 2025 17:11:09 +0200 Subject: [PATCH 3/6] maybe fix windows ci --- fs/expand_glob_test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/expand_glob_test.ts b/fs/expand_glob_test.ts index 8f9282fcda3d..643b4741162f 100644 --- a/fs/expand_glob_test.ts +++ b/fs/expand_glob_test.ts @@ -454,7 +454,7 @@ Deno.test("expandGlob() finds directory with escaped brackets", async function ( ); assertEquals( await expandGlobArray(`a${escapeChar}[b${escapeChar}]c/fo[o]`, EG_OPTIONS), - ["a[b]c/foo"], + [join("a[b]c", "foo")], ); }); @@ -465,6 +465,6 @@ Deno.test("expandGlobSync() finds directory with escaped brackets", function () ); assertEquals( expandGlobSyncArray(`a${escapeChar}[b${escapeChar}]c/fo[o]`, EG_OPTIONS), - ["a[b]c/foo"], + [join("a[b]c", "foo")], ); }); From a85350d0829e46ad9117542bc181ea4c511e1e22 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 17 Oct 2025 10:59:52 -0400 Subject: [PATCH 4/6] update based on feedback --- fs/expand_glob.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/fs/expand_glob.ts b/fs/expand_glob.ts index 7968e5adf92b..75f6adbbfe6d 100644 --- a/fs/expand_glob.ts +++ b/fs/expand_glob.ts @@ -534,6 +534,27 @@ export function* expandGlobSync( } const globEscapeChar = Deno.build.os === "windows" ? "`" : `\\`; +const globMetachars = "*?{}[]"; function unescapeGlobSegment(segment: string): string { - return segment.replaceAll(globEscapeChar, ""); + let result = ""; + let lastIndex = 0; + for (let i = 0; i < segment.length; i++) { + const char = segment[i]!; + if (char === globEscapeChar && i + 1 < segment.length) { + const nextChar = segment[i + 1]!; + if (globMetachars.includes(nextChar)) { + // append the slice before the escape char, then the metachar + result += segment.slice(lastIndex, i) + nextChar; + i++; // skip next char since we already processed it + lastIndex = i + 1; + } + } + } + // no escaped, return the original segment + if (lastIndex === 0) { + return segment; + } + // append any remaining characters + result += segment.slice(lastIndex); + return result; } From d056f4f849c21a63dfb57caec1d5068c0b954258 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 17 Oct 2025 11:04:40 -0400 Subject: [PATCH 5/6] remove non-null assertions --- fs/expand_glob.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/expand_glob.ts b/fs/expand_glob.ts index 75f6adbbfe6d..a5cbef3c055f 100644 --- a/fs/expand_glob.ts +++ b/fs/expand_glob.ts @@ -539,10 +539,10 @@ function unescapeGlobSegment(segment: string): string { let result = ""; let lastIndex = 0; for (let i = 0; i < segment.length; i++) { - const char = segment[i]!; - if (char === globEscapeChar && i + 1 < segment.length) { - const nextChar = segment[i + 1]!; - if (globMetachars.includes(nextChar)) { + const char = segment[i]; + if (char === globEscapeChar) { + const nextChar = segment[i + 1]; + if (nextChar && globMetachars.includes(nextChar)) { // append the slice before the escape char, then the metachar result += segment.slice(lastIndex, i) + nextChar; i++; // skip next char since we already processed it From 3e37ea2ee3833fe6dc89bd95dec3eacc9af96ee2 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 19 Oct 2025 21:33:13 -0400 Subject: [PATCH 6/6] Add more chars --- fs/expand_glob.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/expand_glob.ts b/fs/expand_glob.ts index 1881e8be71d8..d13757f0f498 100644 --- a/fs/expand_glob.ts +++ b/fs/expand_glob.ts @@ -534,7 +534,7 @@ export function* expandGlobSync( } const globEscapeChar = Deno.build.os === "windows" ? "`" : `\\`; -const globMetachars = "*?{}[]"; +const globMetachars = "*?{}[]()|+@!"; function unescapeGlobSegment(segment: string): string { let result = ""; let lastIndex = 0;