use std::path::Path;
use std::path::PathBuf;
use super::CARGO_TARGET_KIND_BENCH;
use super::CARGO_TARGET_KIND_BIN;
use super::CARGO_TARGET_KIND_EXAMPLE;
use super::CARGO_TARGET_KIND_LIB;
use super::CARGO_TARGET_KIND_TEST;
use super::PackageMetadata;
use super::TargetMetadata;
use crate::compiler::SOURCE_DIR_SRC;
use crate::config::CargoCheckCli;
use crate::config::TargetSelection;
use crate::reporting::Report;
pub(crate) enum DisplayFilter {
AllowAll,
AllowDirs {
allowed: Vec<PathBuf>,
excluded_dirs: Vec<PathBuf>,
package_root: PathBuf,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LibraryTargetSelection {
Included,
Omitted,
}
impl DisplayFilter {
pub(crate) fn from_cli(cli: &CargoCheckCli, packages: &[PackageMetadata]) -> Self {
let asks_for_all = cli.target_selections.contains(&TargetSelection::All);
let any_target_flag = !cli.target_selections.is_empty()
|| !cli.bin.is_empty()
|| !cli.example.is_empty()
|| !cli.test.is_empty()
|| !cli.bench.is_empty();
if asks_for_all || !any_target_flag {
return Self::AllowAll;
}
let mut allowed = Vec::new();
let mut excluded_dirs = Vec::new();
let mut package_root = PathBuf::new();
let mut library_target_selection = LibraryTargetSelection::Omitted;
for package in packages {
package_root.clone_from(&package.root);
for target in &package.targets {
let dir = target_directory(target);
if cli_includes_target(cli, target) {
allowed.push(dir.clone());
if target.kind.iter().any(|k| k == CARGO_TARGET_KIND_LIB) {
library_target_selection = LibraryTargetSelection::Included;
}
}
}
if matches!(library_target_selection, LibraryTargetSelection::Included) {
for target in &package.targets {
if !target.kind.iter().any(|k| k == CARGO_TARGET_KIND_LIB) {
let dir = target_directory(target);
if dir.starts_with(&package.root) {
excluded_dirs.push(dir);
}
}
}
}
}
if allowed.is_empty() {
Self::AllowDirs {
allowed: Vec::new(),
excluded_dirs: Vec::new(),
package_root: PathBuf::new(),
}
} else {
Self::AllowDirs {
allowed,
excluded_dirs,
package_root,
}
}
}
pub(crate) fn allows(&self, finding_path: &Path) -> bool {
match self {
Self::AllowAll => true,
Self::AllowDirs {
allowed,
excluded_dirs,
package_root,
} => {
let absolute = if finding_path.is_absolute() {
finding_path.to_path_buf()
} else {
package_root.join(finding_path)
};
let in_allowed = allowed.iter().any(|dir| absolute.starts_with(dir));
if !in_allowed {
return false;
}
let in_excluded_dir = excluded_dirs.iter().any(|dir| absolute.starts_with(dir));
if in_excluded_dir {
let lib_dir = package_root.join(SOURCE_DIR_SRC);
return allowed
.iter()
.any(|dir| absolute.starts_with(dir) && *dir != lib_dir);
}
true
},
}
}
pub(crate) fn apply(&self, report: &mut Report) {
if matches!(self, Self::AllowAll) {
return;
}
report
.findings
.retain(|finding| self.allows(Path::new(&finding.path)));
report.refresh_summary();
}
}
fn target_directory(target: &TargetMetadata) -> PathBuf {
target
.src_path
.parent()
.map_or_else(|| target.src_path.clone(), Path::to_path_buf)
}
fn cli_includes_target(cli: &CargoCheckCli, target: &TargetMetadata) -> bool {
let kind = target.kind.first().map_or("", String::as_str);
match kind {
CARGO_TARGET_KIND_LIB => cli.target_selections.contains(&TargetSelection::Library),
CARGO_TARGET_KIND_BIN => {
cli.target_selections.contains(&TargetSelection::Binaries)
|| cli.bin.iter().any(|name| name == &target.name)
},
CARGO_TARGET_KIND_EXAMPLE => {
cli.target_selections.contains(&TargetSelection::Examples)
|| cli.example.iter().any(|name| name == &target.name)
},
CARGO_TARGET_KIND_TEST => {
cli.target_selections.contains(&TargetSelection::Tests)
|| cli.test.iter().any(|name| name == &target.name)
},
CARGO_TARGET_KIND_BENCH => {
cli.target_selections.contains(&TargetSelection::Benches)
|| cli.bench.iter().any(|name| name == &target.name)
},
_ => false,
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::*;
use crate::compiler::CARGO_MANIFEST_FILE;
use crate::config::TargetSelection;
use crate::selection::CARGO_TARGET_KIND_BIN;
use crate::selection::CARGO_TARGET_KIND_EXAMPLE;
use crate::selection::CARGO_TARGET_KIND_LIB;
use crate::selection::PackageMetadata;
use crate::selection::TargetMetadata;
use crate::selection::TargetSupport;
fn target(kind: &str, name: &str, src_path: &str) -> TargetMetadata {
TargetMetadata {
kind: vec![kind.to_string()],
crate_types: Vec::new(),
name: name.to_string(),
src_path: PathBuf::from(src_path),
edition: "2024".to_string(),
required_features: Vec::new(),
doc: TargetSupport::Disabled,
doctest: TargetSupport::Disabled,
test: TargetSupport::Disabled,
}
}
fn package_with_lib_and_example() -> PackageMetadata {
PackageMetadata {
id: "pkg".to_string(),
manifest_path: PathBuf::from("/proj").join(CARGO_MANIFEST_FILE),
root: PathBuf::from("/proj"),
targets: vec![
target(CARGO_TARGET_KIND_LIB, "pkg", "/proj/src/lib.rs"),
target(
CARGO_TARGET_KIND_EXAMPLE,
"demo",
"/proj/examples/demo/main.rs",
),
],
}
}
#[test]
fn no_target_flags_allows_everything() {
let filter =
DisplayFilter::from_cli(&CargoCheckCli::default(), &[package_with_lib_and_example()]);
assert!(matches!(filter, DisplayFilter::AllowAll));
assert!(filter.allows(Path::new("src/anywhere.rs")));
assert!(filter.allows(Path::new("examples/demo/helper.rs")));
}
#[test]
fn all_targets_flag_allows_everything() {
let cli = CargoCheckCli {
target_selections: BTreeSet::from([TargetSelection::All]),
..CargoCheckCli::default()
};
let filter = DisplayFilter::from_cli(&cli, &[package_with_lib_and_example()]);
assert!(matches!(filter, DisplayFilter::AllowAll));
}
#[test]
fn lib_flag_allows_lib_and_excludes_examples() {
let cli = CargoCheckCli {
target_selections: BTreeSet::from([TargetSelection::Library]),
..CargoCheckCli::default()
};
let filter = DisplayFilter::from_cli(&cli, &[package_with_lib_and_example()]);
assert!(filter.allows(Path::new("/proj/src/helpers.rs")));
assert!(!filter.allows(Path::new("/proj/examples/demo/helper.rs")));
}
#[test]
fn lib_flag_excludes_src_bin_subdirectory() {
let pkg = PackageMetadata {
id: "pkg".to_string(),
manifest_path: PathBuf::from("/proj").join(CARGO_MANIFEST_FILE),
root: PathBuf::from("/proj"),
targets: vec![
target(CARGO_TARGET_KIND_LIB, "pkg", "/proj/src/lib.rs"),
target(CARGO_TARGET_KIND_BIN, "tool", "/proj/src/bin/tool.rs"),
],
};
let cli = CargoCheckCli {
target_selections: BTreeSet::from([TargetSelection::Library]),
..CargoCheckCli::default()
};
let filter = DisplayFilter::from_cli(&cli, &[pkg]);
assert!(filter.allows(Path::new("/proj/src/lib_helpers.rs")));
assert!(
!filter.allows(Path::new("/proj/src/bin/tool.rs")),
"src/bin/tool.rs belongs to the bin, not the lib",
);
}
#[test]
fn named_example_allows_only_that_example_directory() {
let cli = CargoCheckCli {
example: vec!["demo".to_string()],
..CargoCheckCli::default()
};
let filter = DisplayFilter::from_cli(&cli, &[package_with_lib_and_example()]);
assert!(filter.allows(Path::new("/proj/examples/demo/helper.rs")));
assert!(!filter.allows(Path::new("/proj/src/anywhere.rs")));
}
}