panache_parser/parser/diagnostics.rs
1//! Parser syntax-error channel.
2//!
3//! Markdown itself is never syntactically invalid — every byte sequence is some
4//! lossless CST — so the block/inline parsers emit no diagnostics. Embedded
5//! *sublanguages* are different: hashpipe and frontmatter YAML can be malformed,
6//! and (later) LaTeX math or raw HTML may be validated too. When the parser
7//! validates such a region it already knows the verdict and offset; rather than
8//! discard it and force a downstream re-parse, it records a host-ranged
9//! [`SyntaxError`] here, mirroring rust-analyzer's `Parse { green, errors }`.
10//!
11//! The CST is unchanged — invalid YAML still becomes opaque tokens. This channel
12//! is purely the *diagnostic* the parser already computed, surfaced instead of
13//! thrown away. It is empty for pure Markdown.
14
15use std::cell::RefCell;
16use std::rc::Rc;
17
18use rowan::TextRange;
19
20/// Which sublanguage validation produced a [`SyntaxError`]. Lets downstream
21/// consumers (the linter) map to the right diagnostic code without the parser
22/// knowing linter codes.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum SyntaxErrorSource {
25 /// Embedded YAML — frontmatter metadata or a hashpipe option preamble.
26 Yaml,
27}
28
29/// A syntax error the parser found in an embedded sublanguage, with a
30/// **host-aligned** byte range (ready to turn into a diagnostic without any
31/// offset remapping).
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct SyntaxError {
34 pub range: TextRange,
35 pub message: String,
36 pub source: SyntaxErrorSource,
37}
38
39/// Interior-mutable sink the single-pass parser pushes into while building.
40///
41/// Cloning shares the same backing store (it is an `Rc`), so it threads through
42/// the block dispatcher (on `BlockContext`) as an owned value — sidestepping any
43/// `&self` borrow that would clash with the `&mut GreenNodeBuilder` held during
44/// emission. The handful of clones per parse are cheap pointer bumps.
45#[derive(Debug, Clone, Default)]
46pub struct Diagnostics {
47 errors: Rc<RefCell<Vec<SyntaxError>>>,
48}
49
50impl Diagnostics {
51 pub fn new() -> Self {
52 Self::default()
53 }
54
55 /// Record a syntax error.
56 pub fn push(&self, error: SyntaxError) {
57 self.errors.borrow_mut().push(error);
58 }
59
60 /// Drain the recorded errors. Called once after the parse completes.
61 pub fn take(&self) -> Vec<SyntaxError> {
62 std::mem::take(&mut self.errors.borrow_mut())
63 }
64}