use crate::lint::rule::Rule;
use crate::markdown::MarkdownParser;
use crate::types::{Fix, Violation};
use serde_json::Value;
pub struct MD009;
impl Rule for MD009 {
fn name(&self) -> &str {
"MD009"
}
fn description(&self) -> &str {
"Trailing spaces"
}
fn tags(&self) -> &[&str] {
&["whitespace"]
}
fn check(&self, parser: &MarkdownParser, config: Option<&Value>) -> Vec<Violation> {
let br_spaces = config
.and_then(|c| c.get("br_spaces"))
.and_then(|v| v.as_u64())
.unwrap_or(2) as usize;
let strict = config
.and_then(|c| c.get("strict"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
let mut violations = Vec::new();
for (line_num, line) in parser.lines().iter().enumerate() {
let trimmed = line.trim_end();
let trailing_spaces = line.len() - trimmed.len();
if trailing_spaces > 0 {
if !strict && trailing_spaces == br_spaces {
continue;
}
violations.push(Violation {
line: line_num + 1,
column: Some(trimmed.len() + 1),
rule: self.name().to_string(),
message: format!("Trailing spaces ({} spaces)", trailing_spaces),
fix: Some(Fix {
line_start: line_num + 1,
line_end: line_num + 1,
column_start: Some(trimmed.len() + 1),
column_end: Some(line.len() + 1),
replacement: String::new(),
description: "Remove trailing spaces".to_string(),
}),
});
}
}
violations
}
fn fixable(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_trailing_spaces() {
let content = "Line 1\nLine 2\nLine 3";
let parser = MarkdownParser::new(content);
let rule = MD009;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 0);
}
#[test]
fn test_trailing_spaces() {
let content = "Line 1 \nLine 2\nLine 3 ";
let parser = MarkdownParser::new(content);
let rule = MD009;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 1); assert_eq!(violations[0].line, 3);
assert_eq!(violations[0].column, Some(7));
}
#[test]
fn test_strict_mode() {
let content = "Line 1 \nLine 2";
let parser = MarkdownParser::new(content);
let rule = MD009;
let config = serde_json::json!({ "strict": true });
let violations = rule.check(&parser, Some(&config));
assert_eq!(violations.len(), 1);
assert_eq!(violations[0].line, 1);
}
#[test]
fn test_custom_br_spaces() {
let content = "Line 1 \nLine 2";
let parser = MarkdownParser::new(content);
let rule = MD009;
let config = serde_json::json!({ "br_spaces": 3 });
let violations = rule.check(&parser, Some(&config));
assert_eq!(violations.len(), 0); }
}