use std::path::{Path, PathBuf};
use fallow_engine::changed_files::RenamedFile;
use fallow_types::results::AnalysisResults;
use rustc_hash::FxHashSet;
#[expect(
clippy::implicit_hasher,
reason = "fallow standardizes on FxHashSet across the workspace"
)]
pub fn scope_dependency_findings(
results: &mut AnalysisResults,
root: &Path,
changed_files: &FxHashSet<PathBuf>,
) {
let changed: FxHashSet<PathBuf> = changed_files
.iter()
.map(|path| dunce::simplified(path).to_path_buf())
.collect();
let declared_in_change = |path: &Path| {
let anchored = if path.is_absolute() {
path.to_path_buf()
} else {
root.join(path)
};
changed.contains(dunce::simplified(&anchored))
|| dunce::canonicalize(&anchored).is_ok_and(|canonical| changed.contains(&canonical))
};
results
.unused_dependencies
.retain(|finding| declared_in_change(&finding.dep.path));
results
.unused_dev_dependencies
.retain(|finding| declared_in_change(&finding.dep.path));
results
.unused_optional_dependencies
.retain(|finding| declared_in_change(&finding.dep.path));
results
.type_only_dependencies
.retain(|finding| declared_in_change(&finding.dep.path));
results
.test_only_dependencies
.retain(|finding| declared_in_change(&finding.dep.path));
results
.dev_dependencies_in_production
.retain(|finding| declared_in_change(&finding.dep.path));
results
.unused_catalog_entries
.retain(|finding| declared_in_change(&finding.entry.path));
}
#[must_use]
pub fn renamed_files(root: &Path, base_ref: &str) -> Vec<RenamedFile> {
fallow_engine::changed_files::try_get_renamed_files(root, base_ref).unwrap_or_default()
}
#[must_use]
#[expect(
clippy::implicit_hasher,
reason = "fallow standardizes on FxHashSet across the workspace"
)]
pub fn base_focus_files(
changed_files: &FxHashSet<PathBuf>,
renames: &[RenamedFile],
) -> FxHashSet<PathBuf> {
changed_files
.iter()
.cloned()
.chain(renames.iter().map(|rename| rename.from.clone()))
.collect()
}
#[must_use]
#[expect(
clippy::implicit_hasher,
reason = "fallow standardizes on FxHashSet across the workspace"
)]
pub fn remap_focus_files(
files: &FxHashSet<PathBuf>,
from_root: &Path,
to_root: &Path,
) -> Option<FxHashSet<PathBuf>> {
let simple_from = dunce::simplified(from_root).to_path_buf();
let canonical_from = dunce::canonicalize(from_root).unwrap_or_else(|_| simple_from.clone());
let mut remapped = FxHashSet::default();
for file in files {
let simple_file = dunce::simplified(file);
let relative = simple_file
.strip_prefix(&simple_from)
.or_else(|_| simple_file.strip_prefix(&canonical_from))
.map(Path::to_path_buf)
.ok()
.or_else(|| {
let canonical_file = dunce::canonicalize(file).ok()?;
canonical_file
.strip_prefix(&canonical_from)
.map(Path::to_path_buf)
.ok()
});
if let Some(relative) = relative {
remapped.insert(to_root.join(relative));
}
}
if remapped.is_empty() {
return None;
}
Some(remapped)
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct BaseCoverageInputs {
pub coverage: Option<PathBuf>,
pub coverage_root: Option<PathBuf>,
}
#[must_use]
pub fn base_coverage_inputs(
root: &Path,
coverage: Option<&Path>,
coverage_root: Option<&Path>,
) -> BaseCoverageInputs {
let canonical_root = dunce::canonicalize(root).unwrap_or_else(|_| root.to_path_buf());
let coverage = coverage.map_or_else(
|| fallow_engine::health::scoring::auto_detect_coverage(&canonical_root),
|coverage| {
Some(fallow_engine::health::scoring::resolve_relative_to_root(
coverage,
Some(&canonical_root),
))
},
);
let coverage_root = match (&coverage, coverage_root) {
(_, Some(explicit)) => Some(explicit.to_path_buf()),
(Some(_), None) => Some(canonical_root),
(None, None) => None,
};
BaseCoverageInputs {
coverage,
coverage_root,
}
}