mdlint/lint/rules/
md027.rs1use crate::lint::rule::Rule;
2use crate::markdown::MarkdownParser;
3use crate::types::{Fix, Violation};
4use serde_json::Value;
5
6pub struct MD027;
7
8impl Rule for MD027 {
9 fn name(&self) -> &str {
10 "MD027"
11 }
12
13 fn description(&self) -> &str {
14 "Multiple spaces after blockquote symbol"
15 }
16
17 fn tags(&self) -> &[&str] {
18 &["blockquote", "whitespace", "indentation"]
19 }
20
21 fn check(&self, parser: &MarkdownParser, _config: Option<&Value>) -> Vec<Violation> {
22 let mut violations = Vec::new();
23
24 for (line_num, line) in parser.lines().iter().enumerate() {
25 let line_number = line_num + 1;
26 let trimmed = line.trim_start();
27
28 if let Some(after_gt) = trimmed.strip_prefix('>') {
30 let space_count = after_gt.chars().take_while(|&c| c == ' ').count();
32
33 if space_count > 1 {
34 let leading_spaces = &line[..line.len() - trimmed.len()];
36 let content = after_gt[space_count..].trim_start();
37 let replacement = if content.is_empty() {
38 format!("{}>", leading_spaces)
39 } else {
40 format!("{}> {}", leading_spaces, content)
41 };
42
43 violations.push(Violation {
44 line: line_number,
45 column: Some(line.len() - trimmed.len() + 2),
46 rule: self.name().to_string(),
47 message: format!(
48 "Multiple spaces after blockquote symbol ({} spaces)",
49 space_count
50 ),
51 fix: Some(Fix {
52 line_start: line_number,
53 line_end: line_number,
54 column_start: None,
55 column_end: None,
56 replacement,
57 description: "Replace multiple spaces with single space".to_string(),
58 }),
59 });
60 }
61 }
62 }
63
64 violations
65 }
66
67 fn fixable(&self) -> bool {
68 true
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::fix::Fixer;
76
77 fn apply_fixes(content: &str, violations: &[Violation]) -> String {
78 let fixes: Vec<_> = violations.iter().filter_map(|v| v.fix.clone()).collect();
79 Fixer::new()
80 .apply_fixes_to_content(content, &fixes)
81 .unwrap()
82 }
83
84 #[test]
85 fn test_correct_blockquote() {
86 let content = "> Quote line 1\n> Quote line 2";
87 let parser = MarkdownParser::new(content);
88 let rule = MD027;
89 let violations = rule.check(&parser, None);
90
91 assert_eq!(violations.len(), 0);
92 }
93
94 #[test]
95 fn test_multiple_spaces() {
96 let content = "> Quote with 2 spaces\n> Correct quote";
97 let parser = MarkdownParser::new(content);
98 let rule = MD027;
99 let violations = rule.check(&parser, None);
100
101 assert_eq!(violations.len(), 1);
102 assert_eq!(violations[0].line, 1);
103 }
104
105 #[test]
106 fn test_many_spaces() {
107 let content = "> Quote with 5 spaces";
108 let parser = MarkdownParser::new(content);
109 let rule = MD027;
110 let violations = rule.check(&parser, None);
111
112 assert_eq!(violations.len(), 1);
113 assert!(violations[0].message.contains("5 spaces"));
114 }
115
116 #[test]
117 fn test_no_space() {
118 let content = ">Quote without space";
119 let parser = MarkdownParser::new(content);
120 let rule = MD027;
121 let violations = rule.check(&parser, None);
122
123 assert_eq!(violations.len(), 0); }
125
126 #[test]
127 fn test_fix_collapses_blockquote_spaces() {
128 let content = "> Too many spaces\n> Correct line\n";
129 let parser = MarkdownParser::new(content);
130 let rule = MD027;
131 let violations = rule.check(&parser, None);
132 assert_eq!(violations.len(), 1);
133 let fixed = apply_fixes(content, &violations);
134 assert_eq!(fixed, "> Too many spaces\n> Correct line\n");
135 }
136}