mdlint/lint/rules/
md044.rs1use crate::lint::rule::Rule;
2use crate::markdown::MarkdownParser;
3use crate::types::Violation;
4use regex::Regex;
5use serde_json::Value;
6
7pub struct MD044;
8
9impl Rule for MD044 {
10 fn name(&self) -> &'static str {
11 "MD044"
12 }
13
14 fn description(&self) -> &'static str {
15 "Proper names should have the correct capitalization"
16 }
17
18 fn tags(&self) -> &[&str] {
19 &["spelling"]
20 }
21
22 fn check(&self, parser: &MarkdownParser, config: Option<&Value>) -> Vec<Violation> {
23 let names = config
24 .and_then(|c| c.get("names"))
25 .and_then(|v| v.as_array())
26 .map(|arr| {
27 arr.iter()
28 .filter_map(|v| v.as_str().map(str::to_owned))
29 .collect::<Vec<_>>()
30 });
31
32 let code_blocks = config
33 .and_then(|c| c.get("code_blocks"))
34 .and_then(serde_json::Value::as_bool)
35 .unwrap_or(true);
36
37 let proper_names = match names {
39 Some(n) if !n.is_empty() => n,
40 _ => return Vec::new(),
41 };
42
43 let mut violations = Vec::new();
44
45 for (line_num, line) in parser.lines().iter().enumerate() {
46 let line_number = line_num + 1;
47
48 if !code_blocks
50 && (line.starts_with(" ") || line.starts_with('\t') || line.contains("```"))
51 {
52 continue;
53 }
54
55 for name in &proper_names {
57 let pattern = format!(r"(?i)\b{}\b", regex::escape(name));
59 if let Ok(re) = Regex::new(&pattern) {
60 for mat in re.find_iter(line) {
61 let found = mat.as_str();
62 if found != name {
64 violations.push(Violation {
65 line: line_number,
66 column: Some(mat.start() + 1),
67 rule: self.name().to_owned(),
68 message: format!(
69 "Proper name '{found}' should be capitalized as '{name}'"
70 ),
71 fix: None,
72 });
73 }
74 }
75 }
76 }
77 }
78
79 violations
80 }
81
82 fn fixable(&self) -> bool {
83 false
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn test_no_config() {
93 let content = "This mentions javascript and rust";
94 let parser = MarkdownParser::new(content);
95 let rule = MD044;
96 let violations = rule.check(&parser, None);
97
98 assert_eq!(violations.len(), 0); }
100
101 #[test]
102 fn test_correct_capitalization() {
103 let content = "We use JavaScript and TypeScript.";
104 let parser = MarkdownParser::new(content);
105 let rule = MD044;
106 let config = serde_json::json!({
107 "names": ["JavaScript", "TypeScript"]
108 });
109 let violations = rule.check(&parser, Some(&config));
110
111 assert_eq!(violations.len(), 0);
112 }
113
114 #[test]
115 fn test_incorrect_capitalization() {
116 let content = "We use javascript and typescript.";
117 let parser = MarkdownParser::new(content);
118 let rule = MD044;
119 let config = serde_json::json!({
120 "names": ["JavaScript", "TypeScript"]
121 });
122 let violations = rule.check(&parser, Some(&config));
123
124 assert_eq!(violations.len(), 2);
125 }
126
127 #[test]
128 fn test_partial_match() {
129 let content = "JavaScriptCore is different from JavaScript";
130 let parser = MarkdownParser::new(content);
131 let rule = MD044;
132 let config = serde_json::json!({
133 "names": ["JavaScript"]
134 });
135 let violations = rule.check(&parser, Some(&config));
136
137 assert_eq!(violations.len(), 0);
139 }
140}