Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 10 additions & 1 deletion packages/core/src/filesystem/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,16 @@ export const fffLayer = Layer.effect(
}),
)

const layer = Layer.unwrap(Effect.sync(() => (Flag.OPENCODE_DISABLE_FFF || !Fff.available() ? ripgrepLayer : fffLayer)))
// fff has no ignore-file support, so repos governed by a .boltignore must use
// the ripgrep backend to keep hidden files out of glob/find/grep results.
const layer = Layer.unwrap(
Effect.gen(function* () {
if (Flag.OPENCODE_DISABLE_FFF || !Fff.available()) return ripgrepLayer
const location = yield* Location.Service
if (Ripgrep.ignored(location.directory)) return ripgrepLayer
return fffLayer
}),
)

export const locationLayer = layer

Expand Down
34 changes: 21 additions & 13 deletions packages/core/src/ripgrep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,34 @@ const MAX_SUBMATCHES = 100
// directory) to keep root-anchored rules like `/src/generated/` correctly
// scoped. User globs and output paths are translated between the two bases.
function boltignore(cwd: string) {
const root = (() => {
let dir = cwd
while (true) {
if (fs.existsSync(path.join(dir, ".git"))) return dir
const parent = path.dirname(dir)
if (parent === dir) return cwd
dir = parent
}
})()
const file = path.join(root, ".boltignore")
if (!fs.existsSync(file)) return { cwd, args: [], search: ".", prefix: "" }
const search = path.relative(root, cwd)
const base = root(cwd)
const file = path.join(base, ".boltignore")
if (!fs.statSync(file, { throwIfNoEntry: false })?.isFile()) return { cwd, args: [], search: ".", prefix: "" }
const search = path.relative(base, cwd)
return {
cwd: root,
cwd: base,
args: [`--ignore-file=${file}`],
search: search || ".",
prefix: search ? `${search.replaceAll("\\", "/")}/` : "",
}
}
Comment on lines 31 to 42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected function declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable


It is considered a best practice to avoid 'polluting' the global scope with variables that are intended to be local to the script. Global variables created from a script can produce name collisions with global variables created from another script, which will usually lead to runtime errors or unexpected behavior. It is mostly useful for browser scripts.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive: the file is an ES module, so this function declaration is module-scoped, not global. The rule targets browser scripts where top-level declarations pollute the global object.


function root(cwd: string) {
let dir = cwd
while (true) {
if (fs.existsSync(path.join(dir, ".git"))) return dir
const parent = path.dirname(dir)
if (parent === dir) return cwd
dir = parent
}
}
Comment on lines +44 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected function declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable


It is considered a best practice to avoid 'polluting' the global scope with variables that are intended to be local to the script. Global variables created from a script can produce name collisions with global variables created from another script, which will usually lead to runtime errors or unexpected behavior. It is mostly useful for browser scripts.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive: the file is an ES module, so this function declaration is module-scoped, not global. The rule targets browser scripts where top-level declarations pollute the global object.


// Whether a .boltignore governs this directory. Search backends without
// ignore-file support (fff) must fall back to ripgrep when this is true.
export function ignored(cwd: string) {
return fs.statSync(path.join(root(cwd), ".boltignore"), { throwIfNoEntry: false })?.isFile() === true
}
Comment on lines +56 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected function declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable


It is considered a best practice to avoid 'polluting' the global scope with variables that are intended to be local to the script. Global variables created from a script can produce name collisions with global variables created from another script, which will usually lead to runtime errors or unexpected behavior. It is mostly useful for browser scripts.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive: this is an ES module (top-level imports/exports), so the function is module-scoped, not global. The rule targets browser scripts, and the declaration matches the file's existing pattern for sibling helpers.


// -g globs follow .gitignore rules relative to the ripgrep working directory:
// slash-less globs match basenames at any depth and survive re-basing, while
// globs with a non-trailing slash are anchored and must be re-anchored onto
Expand Down
18 changes: 18 additions & 0 deletions packages/core/test/ripgrep.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,24 @@ describe("Ripgrep", () => {
),
)

it.live("ignored reports whether a .boltignore governs a directory", () =>
Effect.acquireUseRelease(
Effect.promise(() => tmpdir()),
(tmp) =>
Effect.gen(function* () {
yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, "src"), { recursive: true }))
yield* Effect.promise(() => Bun.$`git init -q ${tmp.path}`)
expect(Ripgrep.ignored(tmp.path)).toBe(false)
expect(Ripgrep.ignored(path.join(tmp.path, "src"))).toBe(false)
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, ".boltignore"), "hidden.txt\n"))
expect(Ripgrep.ignored(tmp.path)).toBe(true)
// Subdirectories resolve the .boltignore at the repository root.
expect(Ripgrep.ignored(path.join(tmp.path, "src"))).toBe(true)
}),
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
),
)

it.live("never includes git metadata", () =>
Effect.acquireUseRelease(
Effect.promise(() => tmpdir()),
Expand Down
Loading