stern4rust/rules/source/
test_free_source_rule.rs1use crate::finding::parsing::unit_test_finder::UnitTestFinder;
6use crate::reporting::offence::Offence;
7use crate::reporting::rule_explanation::RuleExplanation;
8use crate::rule::Rule;
9use crate::source_file::SourceFile;
10
11pub struct TestFreeSourceRule;
26
27impl TestFreeSourceRule {
28 pub const ROOT: &'static str = "tests/";
29
30 pub fn new() -> Self {
31 Self
32 }
33
34 fn applies_to(file: &SourceFile) -> bool {
38 !file.relative_path().starts_with(Self::ROOT)
39 }
40}
41
42impl Default for TestFreeSourceRule {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl Rule for TestFreeSourceRule {
49 fn name(&self) -> &'static str {
50 "test-free-source"
51 }
52
53 fn check(&self, file: &SourceFile) -> Vec<Offence> {
54 if !Self::applies_to(file) {
55 return Vec::new();
56 }
57 UnitTestFinder::sites(file)
58 .unwrap_or_default()
59 .into_iter()
60 .map(|site| {
61 Offence::new(
62 file.relative_path(),
63 site.line,
64 self.name(),
65 format!("{} does not belong in the source tree", site.label),
66 site.correction.clone(),
67 )
68 .with_subject(&site.label)
69 })
70 .collect()
71 }
72
73 fn check_workspace(&self, _files: &[SourceFile]) -> Vec<Offence> {
74 Vec::new()
75 }
76
77 fn requirement(&self) -> Option<&'static str> {
78 None
79 }
80
81 fn is_configured(&self) -> bool {
82 true
83 }
84
85 fn explanation(&self) -> RuleExplanation {
86 RuleExplanation::new(
87 self.name(),
88 "Tests live in tests/, and the production source tree carries none of them.",
89 "// src/widget.rs\n#[test]\nfn widget_works() {}",
90 "// tests/widget_tests.rs\n#[test]\nfn widget_works() {}",
91 )
92 }
93}