Skip to content

Commit 2107f03

Browse files
authored
fix(grep): keep FilePath scope in regex/literal fallback (#756) (#764)
* fix(grep): keep FilePath scope in regex/literal fallback (#756) The literal/regex fallback rebuilt the query with empty constraints, dropping an explicit inline FilePath scope. In regex mode a top-level alternation then leaked matches into files outside the pinned path. Preserve FilePath constraints in the fallback query. Closes #756 * chore: cargo fmt (#756) --------- Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
1 parent 9441cbc commit 2107f03

2 files changed

Lines changed: 66 additions & 1 deletion

File tree

crates/fff-core/src/grep/grep.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,19 @@ pub(crate) fn grep_search<'a>(
229229
return result;
230230
}
231231

232+
// Keep any explicit FilePath scope (AI mode `path/to/file.ext` prefix) so the
233+
// fallback can't leak matches outside the file the user pinned. Only the
234+
// swallowed operator/glob tokens are dropped. See issue #756.
235+
let scoped_constraints: fff_query_parser::ConstraintVec<'_> = query
236+
.constraints
237+
.iter()
238+
.filter(|c| matches!(c, fff_query_parser::Constraint::FilePath(_)))
239+
.cloned()
240+
.collect();
241+
232242
let literal_query = FFFQuery {
233243
raw_query: query.raw_query,
234-
constraints: Vec::new(),
244+
constraints: scoped_constraints,
235245
fuzzy_query: fff_query_parser::FuzzyQuery::Text(raw),
236246
location: None,
237247
};

crates/fff-core/src/grep/grep_tests.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,3 +461,58 @@ fn test_grep_no_duplicates_with_overflow_trailing_bits() {
461461
result.matches.len()
462462
);
463463
}
464+
465+
/// Issue #756: an AI-mode regex query with an inline FilePath scope and
466+
/// top-level alternation. The regex fragments are swallowed as bogus Glob
467+
/// constraints, the constrained search finds nothing, and the literal/regex
468+
/// fallback must NOT drop the FilePath scope — otherwise the `|` branch leaks
469+
/// matches into files outside the pinned path.
470+
#[test]
471+
fn regex_fallback_keeps_file_path_scope_issue_756() {
472+
use fff_query_parser::{AiGrepConfig, QueryParser};
473+
let dir = tempfile::tempdir().unwrap();
474+
let base = crate::path_utils::canonicalize(dir.path()).unwrap();
475+
std::fs::create_dir(base.join("scope")).unwrap();
476+
std::fs::write(
477+
base.join("scope").join("target.css"),
478+
"/* ---------- target ---------- */\n",
479+
)
480+
.unwrap();
481+
std::fs::write(
482+
base.join("outside.css"),
483+
"/* ---------- outside ---------- */\n",
484+
)
485+
.unwrap();
486+
487+
let mut picker = FilePicker::new(FilePickerOptions {
488+
base_path: base.to_str().unwrap().into(),
489+
watch: false,
490+
..Default::default()
491+
})
492+
.unwrap();
493+
picker.collect_files().unwrap();
494+
495+
let options = crate::GrepSearchOptions {
496+
mode: super::GrepMode::Regex,
497+
smart_case: true,
498+
max_matches_per_file: 80,
499+
page_limit: 100,
500+
..Default::default()
501+
};
502+
503+
let raw = r"scope/target.css ^/\* |^\s*/\* ----------";
504+
let query = QueryParser::new(AiGrepConfig).parse(raw);
505+
let result = picker.grep(&query, &options);
506+
let mut paths: Vec<String> = result
507+
.files
508+
.iter()
509+
.map(|f| f.relative_path(&picker))
510+
.collect();
511+
paths.sort();
512+
513+
assert_eq!(
514+
paths,
515+
vec!["scope/target.css"],
516+
"regex fallback must not leak outside the FilePath scope"
517+
);
518+
}

0 commit comments

Comments
 (0)