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) -> &'static str {
10 "MD027"
11 }
12
13 fn description(&self) -> &'static 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_owned(),
47 message: format!(
48 "Multiple spaces after blockquote symbol ({space_count} spaces)"
49 ),
50 fix: Some(Fix {
51 line_start: line_number,
52 line_end: line_number,
53 column_start: None,
54 column_end: None,
55 replacement,
56 description: "Replace multiple spaces with single space".to_owned(),
57 }),
58 });
59 }
60 }
61 }
62
63 violations
64 }
65
66 fn fixable(&self) -> bool {
67 true
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::fix::Fixer;
75
76 fn apply_fixes(content: &str, violations: &[Violation]) -> String {
77 let fixes: Vec<_> = violations.iter().filter_map(|v| v.fix.clone()).collect();
78 Fixer::new()
79 .apply_fixes_to_content(content, &fixes)
80 .unwrap()
81 }
82
83 #[test]
84 fn test_correct_blockquote() {
85 let content = "> Quote line 1\n> Quote line 2";
86 let parser = MarkdownParser::new(content);
87 let rule = MD027;
88 let violations = rule.check(&parser, None);
89
90 assert_eq!(violations.len(), 0);
91 }
92
93 #[test]
94 fn test_multiple_spaces() {
95 let content = "> Quote with 2 spaces\n> Correct quote";
96 let parser = MarkdownParser::new(content);
97 let rule = MD027;
98 let violations = rule.check(&parser, None);
99
100 assert_eq!(violations.len(), 1);
101 assert_eq!(violations[0].line, 1);
102 }
103
104 #[test]
105 fn test_many_spaces() {
106 let content = "> Quote with 5 spaces";
107 let parser = MarkdownParser::new(content);
108 let rule = MD027;
109 let violations = rule.check(&parser, None);
110
111 assert_eq!(violations.len(), 1);
112 assert!(violations[0].message.contains("5 spaces"));
113 }
114
115 #[test]
116 fn test_no_space() {
117 let content = ">Quote without space";
118 let parser = MarkdownParser::new(content);
119 let rule = MD027;
120 let violations = rule.check(&parser, None);
121
122 assert_eq!(violations.len(), 0); }
124
125 #[test]
126 fn test_fix_collapses_blockquote_spaces() {
127 let content = "> Too many spaces\n> Correct line\n";
128 let parser = MarkdownParser::new(content);
129 let rule = MD027;
130 let violations = rule.check(&parser, None);
131 assert_eq!(violations.len(), 1);
132 let fixed = apply_fixes(content, &violations);
133 assert_eq!(fixed, "> Too many spaces\n> Correct line\n");
134 }
135}