Skip to main content

mdwright_lint/stdlib/
duplicate_link_label.rs

1//! Two `[label]:` definitions with the same case-insensitive label.
2//!
3//! `CommonMark` resolves duplicate labels to the first definition,
4//! silently ignoring subsequent ones — easy to introduce by accident,
5//! easy to miss in review. Each duplicate after the first is flagged.
6
7use std::collections::HashMap;
8
9use crate::diagnostic::Diagnostic;
10use crate::rule::LintRule;
11use mdwright_document::Document;
12
13pub struct DuplicateLinkLabel;
14
15impl LintRule for DuplicateLinkLabel {
16    fn name(&self) -> &str {
17        "duplicate-link-label"
18    }
19
20    fn description(&self) -> &str {
21        "Two `[label]:` definitions with the same label."
22    }
23
24    fn explain(&self) -> &str {
25        include_str!("explain/duplicate_link_label.md")
26    }
27
28    fn check(&self, doc: &Document, out: &mut Vec<Diagnostic>) {
29        let mut seen: HashMap<String, usize> = HashMap::new();
30        for def in doc.link_defs() {
31            let key = def.label.to_ascii_lowercase();
32            match seen.get(&key) {
33                Some(&first_line_byte) => {
34                    let message = format!(
35                        "duplicate link reference `{}` — first defined at byte {first_line_byte}",
36                        def.label
37                    );
38                    let local = 0..(def.raw_range.end.saturating_sub(def.raw_range.start));
39                    if let Some(d) = Diagnostic::at(doc, def.raw_range.start, local, message, None) {
40                        out.push(d);
41                    }
42                }
43                None => {
44                    seen.insert(key, def.raw_range.start);
45                }
46            }
47        }
48    }
49}