diff --git a/src/glob/lib.rs b/src/glob/lib.rs index 1c6aae4e1723..571c85ff13e5 100644 --- a/src/glob/lib.rs +++ b/src/glob/lib.rs @@ -33,7 +33,7 @@ pub fn detect_glob_syntax(potential_pattern: &[u8]) -> bool { if let Some(idx) = slice.iter().position(|&b| b == token) { // Check for even number of backslashes preceding the // token to know that it's not escaped - let mut i = idx; + let mut i = potential_pattern.len() - slice.len() + idx; let mut backslash_count: u16 = 0; while i > 0 && potential_pattern[i - 1] == b'\\' { @@ -53,3 +53,36 @@ pub fn detect_glob_syntax(potential_pattern: &[u8]) -> bool { false } + +#[cfg(test)] +mod tests { + use super::detect_glob_syntax; + + #[test] + fn detects_unescaped_tokens() { + assert!(detect_glob_syntax(b"*.ts")); + assert!(detect_glob_syntax(b"a/{b,c}/d")); + assert!(detect_glob_syntax(b"a[bc]d")); + assert!(detect_glob_syntax(b"a?c")); + assert!(detect_glob_syntax(b"!foo")); + } + + #[test] + fn ignores_escaped_tokens() { + assert!(!detect_glob_syntax(b"a\\*b")); + assert!(!detect_glob_syntax(b"a\\{b\\}c")); + assert!(!detect_glob_syntax(b"plain/path.txt")); + // even backslash count = escaped backslash, unescaped token + assert!(detect_glob_syntax(b"a\\\\*b")); + } + + #[test] + fn detects_unescaped_token_after_escaped_one() { + // https://github.com/oven-sh/bun/pull/34275 + assert!(detect_glob_syntax(b"\\*x*")); + assert!(detect_glob_syntax(b"\\{a\\}{b,c}")); + assert!(detect_glob_syntax(b"\\?a?")); + assert!(detect_glob_syntax(b"\\[a\\]b[cd]")); + assert!(!detect_glob_syntax(b"\\*x\\*")); + } +} diff --git a/test/cli/install/bad-workspace.test.ts b/test/cli/install/bad-workspace.test.ts index e2101f0a3fe3..120dd15bb274 100644 --- a/test/cli/install/bad-workspace.test.ts +++ b/test/cli/install/bad-workspace.test.ts @@ -1,7 +1,8 @@ import { spawnSync } from "bun"; import { beforeEach, expect, setDefaultTimeout, test } from "bun:test"; import { mkdirSync, writeFileSync } from "fs"; -import { bunEnv, bunExe, tempDir, tmpdirSync } from "harness"; +import { bunEnv, bunExe, isWindows, tempDir, tmpdirSync } from "harness"; +import { join } from "path"; let cwd: string; @@ -58,6 +59,28 @@ test("non-string workspaces entry prints the error without literal markup", asyn expect(exitCode).toBe(1); }); +// https://github.com/oven-sh/bun/pull/34275 (`*` is not a valid filename character on Windows) +test.skipIf(isWindows)("workspace glob with an escaped token followed by an unescaped one is expanded", async () => { + using dir = tempDir("workspace-escaped-glob", { + "package.json": JSON.stringify({ name: "root", workspaces: ["\\*x*"] }), + "*xpkg/package.json": JSON.stringify({ name: "xpkg", version: "1.0.0" }), + }); + await using proc = Bun.spawn({ + cmd: [bunExe(), "install"], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).not.toContain("Workspace not found"); + const lockfile = await Bun.file(join(String(dir), "bun.lock")).text(); + expect(lockfile).toContain('"*xpkg"'); + expect(lockfile).toContain('"xpkg@workspace:'); + expect({ stdout, stderr, exitCode }).toMatchObject({ exitCode: 0 }); +}); + test("workspace with ./ should not crash", () => { writeFileSync( `${cwd}/package.json`,