use crate::lint::rule::Rule;
use crate::markdown::MarkdownParser;
use crate::types::Violation;
use serde_json::Value;
pub struct MD007;
impl Rule for MD007 {
fn name(&self) -> &str {
"MD007"
}
fn description(&self) -> &str {
"Unordered list indentation"
}
fn tags(&self) -> &[&str] {
&["bullet", "ul", "indentation"]
}
fn check(&self, parser: &MarkdownParser, config: Option<&Value>) -> Vec<Violation> {
let indent_size = config
.and_then(|c| c.get("indent"))
.and_then(|v| v.as_u64())
.unwrap_or(2) as usize;
let mut violations = Vec::new();
let mut list_depth = 0;
let mut prev_indent = 0;
for (line_num, line) in parser.lines().iter().enumerate() {
let line_number = line_num + 1;
let trimmed = line.trim_start();
let is_ul_item =
trimmed.starts_with("* ") || trimmed.starts_with("+ ") || trimmed.starts_with("- ");
if !is_ul_item {
if !line.trim().is_empty() && !trimmed.starts_with(" ") {
list_depth = 0;
prev_indent = 0;
}
continue;
}
let indent = line.len() - trimmed.len();
if indent > prev_indent {
list_depth += 1;
} else if indent < prev_indent {
list_depth = indent / indent_size;
}
let expected_indent = list_depth * indent_size;
if indent != expected_indent {
violations.push(Violation {
line: line_number,
column: Some(1),
rule: self.name().to_string(),
message: format!(
"Unordered list indentation should be {} spaces (found {})",
expected_indent, indent
),
fix: None,
});
}
prev_indent = indent;
}
violations
}
fn fixable(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_correct_indentation() {
let content = "* Item 1\n * Nested 1\n * Double nested\n * Nested 2\n* Item 2";
let parser = MarkdownParser::new(content);
let rule = MD007;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 0);
}
#[test]
fn test_incorrect_indentation() {
let content = "* Item 1\n * Nested wrong - 3 spaces instead of 2";
let parser = MarkdownParser::new(content);
let rule = MD007;
let violations = rule.check(&parser, None);
assert!(!violations.is_empty());
}
#[test]
fn test_custom_indent_size() {
let content = "* Item 1\n * Nested with 4 spaces";
let parser = MarkdownParser::new(content);
let rule = MD007;
let config = serde_json::json!({ "indent": 4 });
let violations = rule.check(&parser, Some(&config));
assert_eq!(violations.len(), 0);
}
#[test]
fn test_multiple_levels() {
let content = "* Level 1\n * Level 2\n * Level 3\n * Level 4";
let parser = MarkdownParser::new(content);
let rule = MD007;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 0);
}
}