Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions src/glob/GlobWalker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1863,7 +1863,7 @@
!filepath.is_empty() && filepath[0] == b'.'
}

const SYNTAX_TOKENS: &'static [u8] = b"*[{?!";
const SYNTAX_TOKENS: &'static [u8] = b"*[{?!\\";
Comment thread
robobun marked this conversation as resolved.
Outdated

fn check_special_syntax(pattern: &[u8]) -> bool {
strings::index_of_any(pattern, Self::SYNTAX_TOKENS).is_some()
Expand Down Expand Up @@ -1939,11 +1939,13 @@
// false negatives, but that's okay, it just
// means we don't apply the optimization.
//
// We also don't need to look for the `!` token,
// because that only applies negation if at the
// beginning of the string.
b'[' | b'{' | b'?' | b'*' => break 'out_of_check_wildcard_filepath,
b'[' | b'{' | b'?' | b'*' | b'\\' => {
break 'out_of_check_wildcard_filepath
}
_ => {}

Check notice on line 1948 in src/glob/GlobWalker.rs

View check run for this annotation

Claude / Claude Code Review

WildcardFilepath bailout loop scans past component end

Pre-existing nit (perf-only): this loop iterates `pattern[(component.start + 2)..]` to the end of the **full** pattern string rather than to `(component.start + component.len)`, so a special token in a *later* component (e.g. the second `*` in `*.ts/*.js`, or now a `\` in `*.ts/foo\ bar`) defeats the `WildcardFilepath` fast path for the earlier `*.ts` component. Since this PR touches the match arm anyway, you could tighten the slice to `&pattern[(component.start + 2) as usize..(component.start +
Comment thread
robobun marked this conversation as resolved.
}
}
component.syntax_hint = SyntaxHint::WildcardFilepath;
Expand Down
37 changes: 36 additions & 1 deletion test/js/bun/glob/scan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import { Glob, GlobScanOptions } from "bun";
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import fg from "fast-glob";
import { bunEnv, bunExe, tempDir, tempDirWithFiles, tmpdirSync } from "harness";
import { bunEnv, bunExe, isWindows, tempDir, tempDirWithFiles, tmpdirSync } from "harness";
import * as fs from "node:fs";
import * as path from "path";
import { createTempDirectoryWithBrokenSymlinks, prepareEntries, tempFixturesDir } from "./util";
Expand Down Expand Up @@ -618,6 +618,41 @@ describe("literal fast path", async () => {
});
});

// On Windows `\` is a path separator, not an escape character.
describe.skipIf(isWindows)("backslash escapes non-special characters", () => {
let cwd = "";
beforeAll(() => {
cwd = tempDirWithFiles("glob-scan-backslash-escape", {
"sp ace.txt": "x",
"d r": { "f.txt": "x" },
"foo.t s": "x",
});
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// `\x` quotes `x` whether or not `x` is special, so scan must agree with match.
test.each([
["literal component", "sp\\ ace.txt", ["sp ace.txt"]],
["*.ext component", "*.t\\ s", ["foo.t s"]],
["directory component", "d\\ r/*.txt", ["d r/f.txt"]],
["under **", "**/sp\\ ace.txt", ["sp ace.txt"]],
["escaped backslash is literal", "sp\\\\ ace.txt", []],
])("%s", async (_, pattern, expected) => {
const glob = new Glob(pattern);
const sync = Array.from(glob.scanSync({ cwd })).sort();
const async = (await Array.fromAsync(glob.scan({ cwd }))).sort();
expect({ sync, async }).toEqual({ sync: expected, async: expected });
for (const name of expected) {
expect(glob.match(name)).toBe(true);
}
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test("absolute path", async () => {
const glob = new Glob(path.posix.join(cwd, "sp\\ ace.txt"));
const entries = await Array.fromAsync(glob.scan());
expect(entries).toEqual([path.join(cwd, "sp ace.txt")]);
});
});

describe("trailing directory separator", async () => {
test("matches directories absolute", async () => {
const tmpdir = tmpdirSync();
Expand Down
Loading