use std::path::{Path, PathBuf};
use antigen_macros::{antigen_tolerance, presents};
use syn::visit::Visit;
use walkdir::WalkDir;
use super::{
dedupe_lineage_edges, detect_lineage_failures, finalize_report, ParseFailure, ScanReport,
ScanVisitor, MAX_LINEAGE_DEPTH,
};
#[presents(ScannerBoundaryFalseNegative)]
#[antigen_tolerance(
ScannerBoundaryFalseNegative,
rationale = "Accepted v0.2 limitation: the scan is a static-heuristic walk that surfaces only \
explicitly-declared #[mucosal]/#[presents] sites — it cannot infer implicit trust \
boundaries from parameter types or call sites, by design (ADR-006 recognition-not-design: \
the scan recognizes declared structure, it does not guess). Adopters mark boundaries \
explicitly; the false-negative on unmarked sites is the honest cost of not guessing.",
until = "v0.3"
)]
pub fn scan_workspace(root: &Path, excluded_dirs: Option<&[&str]>) -> std::io::Result<ScanReport> {
let default_exclusions = ["target", ".git", "node_modules"];
let exclusions = excluded_dirs.unwrap_or(&default_exclusions);
let mut report = ScanReport::default();
let mut parsed_files: Vec<(PathBuf, syn::File)> = Vec::new();
for entry in WalkDir::new(root)
.follow_links(false)
.into_iter()
.filter_entry(|e| {
if e.file_type().is_dir() {
let name = e.file_name().to_string_lossy();
!exclusions.iter().any(|x| *x == name)
} else {
true
}
})
{
let Ok(entry) = entry else { continue };
if !entry.file_type().is_file() {
continue;
}
if entry.path().extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let Ok(content) = std::fs::read_to_string(entry.path()) else {
continue;
};
match syn::parse_file(&content) {
Ok(file) => {
let file_path = entry.path().to_path_buf();
let mut visitor = ScanVisitor::new(file_path.clone(), &mut report);
visitor.visit_file(&file);
report.files_scanned += 1;
parsed_files.push((file_path, file));
}
Err(e) => {
report.parse_failures.push(ParseFailure {
file: entry.path().to_path_buf(),
error: e.to_string(),
});
}
}
}
let (deduped_edges, dedup_failures) = dedupe_lineage_edges(&report.lineage_edges);
report.lineage_edges = deduped_edges;
report.parse_failures.extend(dedup_failures);
let lineage_failures = detect_lineage_failures(&report.lineage_edges, MAX_LINEAGE_DEPTH);
report.parse_failures.extend(lineage_failures);
finalize_report(&mut report, &parsed_files);
Ok(report)
}