use crate::lint::rule::Rule;
use crate::markdown::MarkdownParser;
use crate::types::Violation;
use serde_json::Value;
pub struct MD036;
impl Rule for MD036 {
fn name(&self) -> &str {
"MD036"
}
fn description(&self) -> &str {
"Emphasis used instead of a heading"
}
fn tags(&self) -> &[&str] {
&["headings", "emphasis"]
}
fn check(&self, parser: &MarkdownParser, config: Option<&Value>) -> Vec<Violation> {
let punctuation = config
.and_then(|c| c.get("punctuation"))
.and_then(|v| v.as_str())
.unwrap_or(".,;:!?。,;:!?");
let mut violations = Vec::new();
let lines = parser.lines();
for (line_num, line) in lines.iter().enumerate() {
let line_number = line_num + 1;
let trimmed = line.trim();
if is_emphasis_only_line(trimmed) {
if let Some(last_char) = trimmed
.trim_end_matches('*')
.trim_end_matches('_')
.chars()
.last()
&& !punctuation.contains(last_char)
{
violations.push(Violation {
line: line_number,
column: Some(1),
rule: self.name().to_string(),
message: "Emphasis used instead of a heading".to_string(),
fix: None,
});
}
}
}
violations
}
fn fixable(&self) -> bool {
false
}
}
fn is_emphasis_only_line(line: &str) -> bool {
let trimmed = line.trim();
if (trimmed.starts_with("**") && trimmed.ends_with("**") && trimmed.len() > 4)
|| (trimmed.starts_with("__") && trimmed.ends_with("__") && trimmed.len() > 4)
{
let inner = trimmed
.trim_start_matches('*')
.trim_start_matches('_')
.trim_end_matches('*')
.trim_end_matches('_');
return !inner.is_empty() && !inner.chars().all(|c| c == '*' || c == '_');
}
if (trimmed.starts_with('*')
&& trimmed.ends_with('*')
&& !trimmed.starts_with("**")
&& trimmed.len() > 2)
|| (trimmed.starts_with('_')
&& trimmed.ends_with('_')
&& !trimmed.starts_with("__")
&& trimmed.len() > 2)
{
let inner = trimmed
.trim_start_matches('*')
.trim_start_matches('_')
.trim_end_matches('*')
.trim_end_matches('_');
return !inner.is_empty() && !inner.chars().all(|c| c == '*' || c == '_');
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_real_heading() {
let content = "# Heading\n## Another Heading";
let parser = MarkdownParser::new(content);
let rule = MD036;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 0);
}
#[test]
fn test_emphasis_as_heading() {
let content = "**Summary**\n\nSome content";
let parser = MarkdownParser::new(content);
let rule = MD036;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 1);
}
#[test]
fn test_emphasis_with_punctuation() {
let content = "**Note:** This is fine.";
let parser = MarkdownParser::new(content);
let rule = MD036;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 0); }
#[test]
fn test_inline_emphasis() {
let content = "This has **bold text** in the middle.";
let parser = MarkdownParser::new(content);
let rule = MD036;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 0); }
}