use std::collections::BTreeSet;
use crate::reporting::offence::Offence;
use crate::rule::Rule;
use crate::source_file::SourceFile;
pub struct PairedTestFileRule;
impl PairedTestFileRule {
pub const PROPTEST_POSTFIX: &'static str = "_proptest_tests.rs";
pub const REGISTRY: &'static str = "all_tests.rs";
pub const SOURCE_ROOT: &'static str = "src/";
pub const TESTS_POSTFIX: &'static str = "_tests.rs";
pub const TESTS_ROOT: &'static str = "tests/";
pub fn new() -> Self {
Self
}
fn is_test_file(path: &str) -> bool {
path.starts_with(Self::TESTS_ROOT)
&& path.ends_with(Self::TESTS_POSTFIX)
&& !path.ends_with(Self::PROPTEST_POSTFIX)
&& Self::file_name(path) != Self::REGISTRY
}
fn file_name(path: &str) -> &str {
path.rsplit('/').next().unwrap_or(path)
}
fn source_of(path: &str) -> String {
let without_root = path.strip_prefix(Self::TESTS_ROOT).unwrap_or(path);
let stem = without_root
.strip_suffix(Self::TESTS_POSTFIX)
.unwrap_or(without_root);
format!("{}{stem}.rs", Self::SOURCE_ROOT)
}
fn present(files: &[SourceFile]) -> BTreeSet<&str> {
files
.iter()
.map(SourceFile::relative_path)
.filter(|path| path.starts_with(Self::SOURCE_ROOT))
.collect()
}
fn offence(&self, path: &str, expected: &str) -> Offence {
Offence::new(
path,
1,
self.name(),
format!("{path} is named for {expected}, which does not exist"),
"rename it after the source file it exercises, or delete it if that file is gone"
.to_string(),
)
.with_subject(path)
.with_expected(expected)
}
}
impl Default for PairedTestFileRule {
fn default() -> Self {
Self::new()
}
}
impl Rule for PairedTestFileRule {
fn name(&self) -> &'static str {
"paired-test-file"
}
fn check(&self, _file: &SourceFile) -> Vec<Offence> {
Vec::new()
}
fn check_workspace(&self, files: &[SourceFile]) -> Vec<Offence> {
let present = Self::present(files);
files
.iter()
.map(SourceFile::relative_path)
.filter(|path| Self::is_test_file(path))
.filter_map(|path| {
let expected = Self::source_of(path);
(!present.contains(expected.as_str())).then(|| self.offence(path, &expected))
})
.collect()
}
fn is_configured(&self) -> bool {
true
}
}