use crate::reporting::offence::Offence;
use crate::rule::Rule;
use crate::source_file::SourceFile;
pub struct HeaderRule {
expected: Vec<String>,
}
impl HeaderRule {
pub const NAME: &'static str = "header";
pub fn new(expected: Vec<String>) -> Self {
Self { expected }
}
pub fn expected(&self) -> &[String] {
&self.expected
}
fn correction(&self) -> String {
format!(
"make the first {} lines of the file match the expected header",
self.expected.len()
)
}
fn expected_text(&self) -> String {
self.expected.join("\n")
}
fn missing_entirely(&self, file: &SourceFile) -> Offence {
Offence::new(
file.relative_path(),
1,
self.name(),
"file is empty, so it carries no header".to_string(),
self.correction(),
)
.with_expected(&self.expected_text())
}
fn ends_early(&self, file: &SourceFile) -> Offence {
Offence::new(
file.relative_path(),
file.lines().len(),
self.name(),
format!(
"file has {} lines but the header is {}",
file.lines().len(),
self.expected.len()
),
self.correction(),
)
.with_expected(&self.expected_text())
}
fn line_differs(&self, file: &SourceFile, index: usize) -> Offence {
Offence::new(
file.relative_path(),
index + 1,
self.name(),
format!(
"expected {:?} but found {:?}",
self.expected[index],
file.lines()[index]
),
self.correction(),
)
.with_expected(&self.expected_text())
}
}
impl Rule for HeaderRule {
fn name(&self) -> &'static str {
Self::NAME
}
fn is_configured(&self) -> bool {
!self.expected.is_empty()
}
fn check(&self, file: &SourceFile) -> Vec<Offence> {
if self.expected.is_empty() {
return Vec::new();
}
if file.is_empty() {
return vec![self.missing_entirely(file)];
}
let overlap = self.expected.len().min(file.lines().len());
if let Some(index) =
(0..overlap).find(|index| file.lines()[*index] != self.expected[*index])
{
return vec![self.line_differs(file, index)];
}
if file.lines().len() < self.expected.len() {
return vec![self.ends_early(file)];
}
Vec::new()
}
}