use crate::lint::rule::Rule;
use crate::markdown::MarkdownParser;
use crate::types::Violation;
use pulldown_cmark::{Event, Tag};
use serde_json::Value;
pub struct MD042;
impl Rule for MD042 {
fn name(&self) -> &str {
"MD042"
}
fn description(&self) -> &str {
"No empty links"
}
fn tags(&self) -> &[&str] {
&["links"]
}
fn check(&self, parser: &MarkdownParser, _config: Option<&Value>) -> Vec<Violation> {
let mut violations = Vec::new();
for (event, range) in parser.parse_with_offsets() {
if let Event::Start(Tag::Link(_, url, _)) = event {
let url_str = url.to_string();
if url_str.is_empty() || url_str == "#" {
let line = parser.offset_to_line(range.start);
violations.push(Violation {
line,
column: Some(1),
rule: self.name().to_string(),
message: "No empty links".to_string(),
fix: None,
});
}
}
}
violations
}
fn fixable(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_link_with_text() {
let content = "[Link text](https://example.com)";
let parser = MarkdownParser::new(content);
let rule = MD042;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 0);
}
#[test]
fn test_empty_destination() {
let content = "[Link text]()";
let parser = MarkdownParser::new(content);
let rule = MD042;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 1);
}
#[test]
fn test_empty_text_non_empty_url() {
let content = "[](https://example.com)";
let parser = MarkdownParser::new(content);
let rule = MD042;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 0);
}
#[test]
fn test_empty_fragment() {
let content = "[Link](#)";
let parser = MarkdownParser::new(content);
let rule = MD042;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 1);
}
#[test]
fn test_multiple_links() {
let content = "[Good](url1) and [Bad]() and [Also good](url3)";
let parser = MarkdownParser::new(content);
let rule = MD042;
let violations = rule.check(&parser, None);
assert_eq!(violations.len(), 1); }
}