use crate::lint::rule::Rule;
use crate::markdown::MarkdownParser;
use crate::types::Violation;
use regex::Regex;
use serde_json::Value;
pub struct MD044;
impl Rule for MD044 {
fn name(&self) -> &str {
"MD044"
}
fn description(&self) -> &str {
"Proper names should have the correct capitalization"
}
fn tags(&self) -> &[&str] {
&["spelling"]
}
fn check(&self, parser: &MarkdownParser, config: Option<&Value>) -> Vec<Violation> {
let names = config
.and_then(|c| c.get("names"))
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect::<Vec<_>>()
});
let code_blocks = config
.and_then(|c| c.get("code_blocks"))
.and_then(|v| v.as_bool())
.unwrap_or(true);
let proper_names = match names {
Some(n) if !n.is_empty() => n,
_ => return Vec::new(),
};
let mut violations = Vec::new();
for (line_num, line) in parser.lines().iter().enumerate() {
let line_number = line_num + 1;
if !code_blocks
&& (line.starts_with(" ") || line.starts_with('\t') || line.contains("```"))
{
continue;
}
for name in &proper_names {
let pattern = format!(r"(?i)\b{}\b", regex::escape(name));
if let Ok(re) = Regex::new(&pattern) {
for mat in re.find_iter(line) {
let found = mat.as_str();
if found != name {
violations.push(Violation {
line: line_number,
column: Some(mat.start() + 1),
rule: self.name().to_string(),
message: format!(
"Proper name '{}' should be capitalized as '{}'",
found, name
),
fix: None,
});
}
}
}
}
}
violations
}
fn fixable(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_config() {
let content = "This mentions javascript and rust";
let parser = MarkdownParser::new(content);
let rule = MD044;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 0); }
#[test]
fn test_correct_capitalization() {
let content = "We use JavaScript and TypeScript.";
let parser = MarkdownParser::new(content);
let rule = MD044;
let config = serde_json::json!({
"names": ["JavaScript", "TypeScript"]
});
let violations = rule.check(&parser, Some(&config));
assert_eq!(violations.len(), 0);
}
#[test]
fn test_incorrect_capitalization() {
let content = "We use javascript and typescript.";
let parser = MarkdownParser::new(content);
let rule = MD044;
let config = serde_json::json!({
"names": ["JavaScript", "TypeScript"]
});
let violations = rule.check(&parser, Some(&config));
assert_eq!(violations.len(), 2);
}
#[test]
fn test_partial_match() {
let content = "JavaScriptCore is different from JavaScript";
let parser = MarkdownParser::new(content);
let rule = MD044;
let config = serde_json::json!({
"names": ["JavaScript"]
});
let violations = rule.check(&parser, Some(&config));
assert_eq!(violations.len(), 0);
}
}