use std::collections::{BTreeSet, HashSet};
use std::env;
use std::path::{Path, PathBuf};
use ast_grep_language::SupportLang;
use globset::GlobSet;
use crate::cross_file::{FileSymbols, SymbolIndex};
use crate::finding::Finding;
use super::engine::{BoxedEngine, ProjectContext, RuleContext};
use super::{walk_files, CompiledRules};
pub(super) fn index_only_files(
compiled: &CompiledRules,
targets: &[PathBuf],
scanned: &[(PathBuf, SupportLang)],
ignores: &GlobSet,
) -> Vec<(PathBuf, SupportLang)> {
if compiled.cross_file.is_empty() {
return Vec::new();
}
let roots: BTreeSet<PathBuf> = targets
.iter()
.filter(|target| target.is_file())
.filter_map(|file| project_of(file))
.collect();
if roots.is_empty() {
return Vec::new();
}
let seen: HashSet<PathBuf> = scanned
.iter()
.filter_map(|(path, _)| path.canonicalize().ok())
.collect();
let roots: Vec<PathBuf> = roots.into_iter().collect();
walk_files(&roots, ignores)
.into_iter()
.filter(|(path, _)| path.canonicalize().is_ok_and(|c| !seen.contains(&c)))
.collect()
}
fn project_of(file: &Path) -> Option<PathBuf> {
let file = file.canonicalize().ok()?;
let root = file
.ancestors()
.skip(1)
.find(|dir| dir.join("slopguard.toml").is_file() || dir.join(".git").exists())?;
let is_cwd = env::current_dir()
.and_then(|cwd| cwd.canonicalize())
.is_ok_and(|cwd| cwd == root);
Some(if is_cwd {
PathBuf::from(".")
} else {
root.to_path_buf()
})
}
pub(super) fn append_cross_file(
findings: &mut Vec<Finding>,
project: &[BoxedEngine],
contributions: &[(PathBuf, FileSymbols)],
reported: &HashSet<&Path>,
) {
if project.is_empty() {
return;
}
let index = SymbolIndex::build(contributions.iter().map(|(p, s)| (p.as_path(), s)));
let ctx = RuleContext::Project(ProjectContext { index: &index });
for engine in project {
findings.extend(
engine
.evaluate(&ctx)
.into_iter()
.filter(|finding| reported.contains(finding.file.as_path())),
);
}
}