Skip to main content

mdwright_lint/stdlib/
heading_punctuation.rs

1//! Trailing `.` or `:` on an ATX or setext heading.
2//!
3//! Headings are titles, not sentences. A trailing period reads as
4//! a stray dot; a trailing colon usually means the author wanted a
5//! list or paragraph instead.
6
7use crate::diagnostic::Diagnostic;
8use crate::rule::LintRule;
9use mdwright_document::Document;
10
11pub struct HeadingPunctuation;
12
13impl LintRule for HeadingPunctuation {
14    fn name(&self) -> &str {
15        "heading-punctuation"
16    }
17
18    fn description(&self) -> &str {
19        "Trailing `.` or `:` on a heading."
20    }
21
22    fn explain(&self) -> &str {
23        include_str!("explain/heading_punctuation.md")
24    }
25
26    fn check(&self, doc: &Document, out: &mut Vec<Diagnostic>) {
27        for h in doc.headings() {
28            let last = h.text.chars().last();
29            let Some(c) = last else { continue };
30            if c != '.' && c != ':' {
31                continue;
32            }
33            let trailing_byte_len = c.len_utf8();
34            let local_start = h.text.len().saturating_sub(trailing_byte_len);
35            let local_end = h.text.len();
36            let message = format!("heading ends with `{c}` — headings should not carry sentence punctuation");
37            if let Some(d) = Diagnostic::at(doc, h.byte_offset, local_start..local_end, message, None) {
38                out.push(d);
39            }
40        }
41    }
42}