use crate::diagnostic::{Diagnostic, Fix};
use crate::rule::LintRule;
use mdwright_document::Document;
pub struct TrailingWhitespace;
impl LintRule for TrailingWhitespace {
fn name(&self) -> &str {
"trailing-whitespace"
}
fn description(&self) -> &str {
"Trailing whitespace at end of line."
}
fn explain(&self) -> &str {
include_str!("explain/trailing_whitespace.md")
}
fn produces_fix(&self) -> bool {
true
}
fn check(&self, doc: &Document, out: &mut Vec<Diagnostic>) {
let source = doc.source();
let code_blocks = doc.code_blocks();
let mut line_start: usize = 0;
for line in source.split_inclusive('\n') {
let line_end_incl = line_start.saturating_add(line.len());
let (content, newline_len) = line.strip_suffix('\n').map_or((line, 0), |c| (c, 1));
let content_end = line_start.saturating_add(content.len());
let trail = content.bytes().rev().take_while(|b| matches!(b, b' ' | b'\t')).count();
if trail == 0 {
line_start = line_end_incl;
continue;
}
if trail == 2 && content.bytes().rev().take(2).all(|b| b == b' ') && content.len() > 2 {
line_start = line_end_incl;
continue;
}
let span_start = content_end.saturating_sub(trail);
let span_end = content_end;
let inside_code = code_blocks
.iter()
.any(|c| c.raw_range.start <= span_start && span_start < c.raw_range.end);
if inside_code {
line_start = line_end_incl;
continue;
}
let local = 0..(span_end.saturating_sub(span_start));
if let Some(d) = Diagnostic::at(
doc,
span_start,
local,
"trailing whitespace".to_owned(),
Some(Fix {
replacement: String::new(),
safe: true,
}),
) {
out.push(d);
}
let _ = newline_len;
line_start = line_end_incl;
}
}
}