stern4rust/rules/source/
header_rule.rs1use crate::reporting::offence::Offence;
6use crate::rule::Rule;
7use crate::source_file::SourceFile;
8
9pub struct HeaderRule {
20 expected: Vec<String>,
21}
22
23impl HeaderRule {
24 pub const NAME: &'static str = "header";
25
26 pub fn new(expected: Vec<String>) -> Self {
27 Self { expected }
28 }
29
30 pub fn expected(&self) -> &[String] {
31 &self.expected
32 }
33
34 fn correction(&self) -> String {
40 format!(
41 "make the first {} lines of the file match the expected header",
42 self.expected.len()
43 )
44 }
45
46 fn expected_text(&self) -> String {
47 self.expected.join("\n")
48 }
49
50 fn missing_entirely(&self, file: &SourceFile) -> Offence {
51 Offence::new(
52 file.relative_path(),
53 1,
54 self.name(),
55 "file is empty, so it carries no header".to_string(),
56 self.correction(),
57 )
58 .with_expected(&self.expected_text())
59 }
60
61 fn ends_early(&self, file: &SourceFile) -> Offence {
62 Offence::new(
63 file.relative_path(),
64 file.lines().len(),
65 self.name(),
66 format!(
67 "file has {} lines but the header is {}",
68 file.lines().len(),
69 self.expected.len()
70 ),
71 self.correction(),
72 )
73 .with_expected(&self.expected_text())
74 }
75
76 fn line_differs(&self, file: &SourceFile, index: usize) -> Offence {
77 Offence::new(
78 file.relative_path(),
79 index + 1,
80 self.name(),
81 format!(
82 "expected {:?} but found {:?}",
83 self.expected[index],
84 file.lines()[index]
85 ),
86 self.correction(),
87 )
88 .with_expected(&self.expected_text())
89 }
90}
91
92impl Rule for HeaderRule {
93 fn name(&self) -> &'static str {
94 Self::NAME
95 }
96
97 fn requirement(&self) -> Option<&'static str> {
99 Some("needs --header-file")
100 }
101
102 fn is_configured(&self) -> bool {
103 !self.expected.is_empty()
104 }
105
106 fn check(&self, file: &SourceFile) -> Vec<Offence> {
112 if self.expected.is_empty() {
113 return Vec::new();
114 }
115 if file.is_empty() {
116 return vec![self.missing_entirely(file)];
117 }
118 let overlap = self.expected.len().min(file.lines().len());
119 if let Some(index) =
120 (0..overlap).find(|index| file.lines()[*index] != self.expected[*index])
121 {
122 return vec![self.line_differs(file, index)];
123 }
124 if file.lines().len() < self.expected.len() {
125 return vec![self.ends_early(file)];
126 }
127 Vec::new()
128 }
129
130 fn check_workspace(&self, _files: &[SourceFile]) -> Vec<Offence> {
131 Vec::new()
132 }
133}