Skip to main content

mdwright_lint/stdlib/
math_unbalanced_braces.rs

1//! `math/unbalanced-braces` — `{` / `}` inside a recognised math body
2//! do not balance.
3//!
4//! The math recogniser only checks that opening and closing delimiter
5//! tokens (`\[ \]`, `\( \)`, `$ $`, `$$ $$`, `\begin / \end`) match.
6//! Brace balance inside the body is a separate invariant: TeX uses
7//! `{` / `}` for argument grouping, and an imbalance there means the
8//! canonicalise pass cannot safely normalise the body. When the rule
9//! fires, body rewrites for that region are skipped, but the underlying
10//! typo still needs a human fix.
11//!
12//! Companion rules [`super::math_unbalanced_delim::MathUnbalancedDelim`]
13//! and [`super::math_unbalanced_env::MathUnbalancedEnv`] cover the
14//! marker-level imbalances.
15
16use crate::diagnostic::Diagnostic;
17use crate::rule::LintRule;
18use mdwright_document::Document;
19use mdwright_document::MathError;
20
21pub struct MathUnbalancedBraces;
22
23impl LintRule for MathUnbalancedBraces {
24    fn name(&self) -> &str {
25        "math/unbalanced-braces"
26    }
27
28    fn description(&self) -> &str {
29        "`{` / `}` inside a math body do not balance; math body normalisation is skipped for that region."
30    }
31
32    fn explain(&self) -> &str {
33        include_str!("explain/math__unbalanced_braces.md")
34    }
35
36    fn check(&self, doc: &Document, out: &mut Vec<Diagnostic>) {
37        for err in doc.math_errors() {
38            let MathError::UnbalancedBraces { offset, region } = err else {
39                continue;
40            };
41            let span = (*offset)..offset.saturating_add(1).min(region.end);
42            let message = "unbalanced `{` / `}` inside math body — math body normalisation is skipped";
43            if let Some(d) = Diagnostic::at(doc, 0, span, message.to_owned(), None) {
44                out.push(d);
45            }
46        }
47    }
48}