use std::path::{Path, PathBuf};
use rustc_lint::{LateContext, LintContext};
use rustc_span::{FileName, SourceFile};
pub(super) fn is_separate_test_target(cx: &LateContext<'_>) -> bool {
let Some(root) = cx.sess().local_crate_source_file() else {
return false;
};
let Some(path) = root.local_path() else {
return false;
};
is_separate_target_path(path)
}
fn is_separate_target_path(path: &Path) -> bool {
let parent = path.parent();
is_target_directory(parent)
|| (path.file_name().and_then(|name| name.to_str()) == Some("main.rs")
&& is_target_directory(parent.and_then(Path::parent)))
}
fn is_target_directory(dir: Option<&Path>) -> bool {
matches!(
dir.and_then(Path::file_name).and_then(|name| name.to_str()),
Some("tests" | "benches" | "examples"),
)
}
pub(super) fn real_path(file: &SourceFile) -> Option<PathBuf> {
match &file.name {
FileName::Real(real) => real.local_path().map(Path::to_path_buf),
_ => None,
}
}
pub(super) fn canonical_target(parent: &Path, name: &str) -> Option<PathBuf> {
let dir = parent.parent()?;
if is_mod_root(parent) {
return Some(dir.join(format!("{name}.rs")));
}
let stem = parent.file_stem()?.to_str()?;
Some(dir.join(stem).join(format!("{name}.rs")))
}
fn is_mod_root(parent: &Path) -> bool {
matches!(
parent.file_name().and_then(|name| name.to_str()),
Some("lib.rs" | "main.rs" | "mod.rs"),
)
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::is_separate_target_path;
#[test]
fn separate_targets_are_recognised() {
for path in [
"tests/integration.rs",
"tests/main.rs",
"tests/suite/main.rs",
"benches/bench.rs",
"benches/suite/main.rs",
"examples/demo.rs",
"examples/demo/main.rs",
"/abs/tests/integration.rs",
] {
assert!(
is_separate_target_path(Path::new(path)),
"`{path}` should be treated as a separate test target",
);
}
}
#[test]
fn library_and_binary_roots_are_checked() {
for path in [
"src/lib.rs",
"src/main.rs",
"src/bin/cli.rs",
"src/bin/cli/main.rs",
"src/rules/inline_test_footprint/paths.rs",
"lib.rs",
] {
assert!(
!is_separate_target_path(Path::new(path)),
"`{path}` should be checked, not skipped",
);
}
}
}