use super::*;
pub(crate) struct WorkspaceScope {
roots: Vec<PathBuf>,
walked: HashMap<PathBuf, HashSet<PathBuf>>,
}
impl WorkspaceScope {
pub(crate) fn new(roots: &[PathBuf]) -> Self {
Self {
roots: roots.iter().map(|r| normalize_path(r)).collect(),
walked: HashMap::new(),
}
}
pub(crate) fn contains(&mut self, path: &Path) -> bool {
let path = normalize_path(path);
let Some(root) = owning_root(&self.roots, &path) else {
return false;
};
self.walked
.entry(root.to_path_buf())
.or_insert_with_key(|root| {
crate::linter::check::scope_members_at(root)
.iter()
.map(|p| normalize_path(p))
.collect()
})
.contains(&path)
}
}
fn owning_root<'a>(roots: &'a [PathBuf], path: &Path) -> Option<&'a Path> {
roots
.iter()
.filter(|r| path.starts_with(r))
.max_by_key(|r| r.components().count())
.map(PathBuf::as_path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scope_prefers_the_deepest_root() {
let dir = tempfile::tempdir().expect("tempdir");
let outer = dir.path();
std::fs::write(outer.join("arity.toml"), "exclude = [\"pkg/\"]\n").expect("outer config");
let inner = outer.join("pkg");
std::fs::create_dir_all(inner.join("R")).expect("pkg/R");
std::fs::write(inner.join("arity.toml"), "").expect("inner config");
let a = inner.join("R").join("a.R");
std::fs::write(&a, "foo <- function() 1\n").expect("a.R");
let roots = vec![outer.to_path_buf(), inner.clone()];
assert!(
WorkspaceScope::new(&roots).contains(&a),
"the deepest root's config governs"
);
assert!(
!WorkspaceScope::new(&[outer.to_path_buf()]).contains(&a),
"the outer root excludes pkg/"
);
}
#[test]
fn scope_rejects_a_path_under_no_root() {
let dir = tempfile::tempdir().expect("tempdir");
let stray = if cfg!(windows) {
PathBuf::from(r"C:\elsewhere\stray.R")
} else {
PathBuf::from("/elsewhere/stray.R")
};
assert!(!WorkspaceScope::new(&[dir.path().to_path_buf()]).contains(&stray));
}
#[test]
fn scope_answers_repeated_queries_from_one_walk() {
let (dir, _db, a) = seeded_package();
let root = dir.path().to_path_buf();
let b = dir.path().join("R").join("b.R");
let mut scope = WorkspaceScope::new(std::slice::from_ref(&root));
assert!(scope.contains(&a));
std::fs::write(&b, "bar <- function() 2\n").expect("b.R");
assert!(!scope.contains(&b), "one walk per root, taken up front");
assert!(WorkspaceScope::new(&[root]).contains(&b));
}
}