use std::collections::BTreeSet;
use crate::finding::model::public_entry_point::PublicEntryPoint;
use crate::finding::parsing::call_site_finder::CallSiteFinder;
use crate::finding::parsing::public_entry_point_finder::PublicEntryPointFinder;
use crate::reporting::offence::Offence;
use crate::reporting::rule_explanation::RuleExplanation;
use crate::rule::Rule;
use crate::source_file::SourceFile;
pub struct TestedPublicApiRule;
impl TestedPublicApiRule {
pub const SOURCE_ROOT: &'static str = "src/";
pub const TESTS_ROOT: &'static str = "tests/";
pub fn new() -> Self {
Self
}
fn declared_in(files: &[SourceFile]) -> Vec<(String, PublicEntryPoint)> {
files
.iter()
.filter(|file| file.relative_path().starts_with(Self::SOURCE_ROOT))
.flat_map(|file| {
PublicEntryPointFinder::find(file)
.unwrap_or_default()
.into_iter()
.map(|entry| (file.relative_path().to_string(), entry))
})
.collect()
}
fn called_in(files: &[SourceFile]) -> BTreeSet<PublicEntryPoint> {
files
.iter()
.filter(|file| file.relative_path().starts_with(Self::TESTS_ROOT))
.filter_map(CallSiteFinder::find)
.flatten()
.collect()
}
fn offence(&self, file: &str, entry: &PublicEntryPoint) -> Offence {
Offence::new(
file,
1,
self.name(),
format!(
"`{}` is public but no test calls it with {} argument(s)",
entry.name, entry.arity
),
format!(
"call `{}` from a test, or stop exposing it if nothing outside needs it",
entry.name
),
)
.with_subject(&entry.signature())
}
}
impl Default for TestedPublicApiRule {
fn default() -> Self {
Self::new()
}
}
impl Rule for TestedPublicApiRule {
fn name(&self) -> &'static str {
"tested-public-api"
}
fn check(&self, _file: &SourceFile) -> Vec<Offence> {
Vec::new()
}
fn check_workspace(&self, files: &[SourceFile]) -> Vec<Offence> {
let called = Self::called_in(files);
Self::declared_in(files)
.iter()
.filter(|(_, entry)| !called.contains(entry))
.map(|(file, entry)| self.offence(file, entry))
.collect()
}
fn requirement(&self) -> Option<&'static str> {
None
}
fn is_configured(&self) -> bool {
true
}
fn explanation(&self) -> RuleExplanation {
RuleExplanation::new(
self.name(),
"Every public entry point is called by at least one test.",
"pub fn commit(&self) -> bool -- no test calls it",
"#[test]\nfn commit_without_a_quorum_returns_false() {\n assert!(!store.commit());\n}",
)
}
}