Skip to main content

mdwright_lint/stdlib/
inconsistent_list_marker.rs

1//! Mixed `-` / `*` / `+` markers within one bullet list.
2//!
3//! Pick one and stick to it. Mixed markers look like a stale merge
4//! and confuse renderers that re-emit the list with a normalised
5//! marker. Ordered lists are not flagged — their markers are digits
6//! and have semantic meaning.
7
8use crate::diagnostic::{Diagnostic, Fix};
9use crate::rule::LintRule;
10use mdwright_document::Document;
11
12pub struct InconsistentListMarker;
13
14impl LintRule for InconsistentListMarker {
15    fn name(&self) -> &str {
16        "inconsistent-list-marker"
17    }
18
19    fn description(&self) -> &str {
20        "Mixed `-` / `*` / `+` markers in one bullet list."
21    }
22
23    fn explain(&self) -> &str {
24        include_str!("explain/inconsistent_list_marker.md")
25    }
26
27    fn produces_fix(&self) -> bool {
28        true
29    }
30
31    fn check(&self, doc: &Document, out: &mut Vec<Diagnostic>) {
32        for group in doc.list_groups() {
33            if group.ordered || group.items.is_empty() {
34                continue;
35            }
36            let Some(first) = group.items.first() else {
37                continue;
38            };
39            let expected = first.marker_byte;
40            for item in group.items.iter().skip(1) {
41                if item.marker_byte != expected {
42                    let actual = item.marker_byte as char;
43                    let expected_c = expected as char;
44                    let message = format!(
45                        "list marker `{actual}` does not match first item's `{expected_c}` \
46                         — keep one marker per list"
47                    );
48                    let start = item.raw_range.start;
49                    let local = 0..1;
50                    let fix = Some(Fix {
51                        replacement: expected_c.to_string(),
52                        safe: true,
53                    });
54                    if let Some(d) = Diagnostic::at(doc, start, local, message, fix) {
55                        out.push(d);
56                    }
57                }
58            }
59        }
60    }
61}