Skip to main content

dmc_parser/ast/
node.rs

1use duck_diagnostic::Span;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub enum Node {
6  Document(Document),
7  Frontmatter(Frontmatter),
8  Import(Import),
9  Export(Export),
10  Heading(Heading),
11  Paragraph(Paragraph),
12  Text(Text),
13  Bold(Inline),
14  Italic(Inline),
15  Strikethrough(Inline),
16  InlineCode(InlineCode),
17  CodeBlock(CodeBlock),
18  Link(Link),
19  Image(Image),
20  HorizontalRule(HorizontalRule),
21  Blockquote(Blockquote),
22  List(List),
23  ListItem(ListItem),
24  TaskListItem(TaskListItem),
25  Table(Table),
26  TableRow(TableRow),
27  TableCell(TableCell),
28  JsxElement(JsxElement),
29  JsxSelfClosing(JsxSelfClosing),
30  JsxFragment(JsxFragment),
31  JsxExpression(JsxExpression),
32  HardBreak(BreakNode),
33  SoftBreak(BreakNode),
34  Html(Html),
35  FootnoteRef(FootnoteRef),
36  FootnoteDef(FootnoteDef),
37}
38
39impl Node {
40  pub fn children_of(node: &Node) -> &[Node] {
41    match node {
42      Node::Document(n) => &n.children,
43      Node::Heading(n) => &n.children,
44      Node::Paragraph(n) => &n.children,
45      Node::Bold(n) | Node::Italic(n) | Node::Strikethrough(n) => &n.children,
46      Node::Link(n) => &n.children,
47      Node::Blockquote(n) => &n.children,
48      Node::List(n) => &n.children,
49      Node::ListItem(n) => &n.children,
50      Node::TaskListItem(n) => &n.children,
51      Node::TableCell(n) => &n.children,
52      Node::JsxElement(n) => &n.children,
53      Node::JsxFragment(n) => &n.children,
54      Node::FootnoteDef(n) => &n.children,
55      _ => &[],
56    }
57  }
58
59  pub fn children_of_mut(node: &mut Node) -> Option<&mut Vec<Node>> {
60    match node {
61      Node::Document(n) => Some(&mut n.children),
62      Node::Heading(n) => Some(&mut n.children),
63      Node::Paragraph(n) => Some(&mut n.children),
64      Node::Bold(n) | Node::Italic(n) | Node::Strikethrough(n) => Some(&mut n.children),
65      Node::Link(n) => Some(&mut n.children),
66      Node::Blockquote(n) => Some(&mut n.children),
67      Node::List(n) => Some(&mut n.children),
68      Node::ListItem(n) => Some(&mut n.children),
69      Node::TaskListItem(n) => Some(&mut n.children),
70      Node::TableCell(n) => Some(&mut n.children),
71      Node::JsxElement(n) => Some(&mut n.children),
72      Node::JsxFragment(n) => Some(&mut n.children),
73      Node::FootnoteDef(n) => Some(&mut n.children),
74      _ => None,
75    }
76  }
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub struct Document {
81  pub children: Vec<Node>,
82  pub span: Span,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct Frontmatter {
87  pub raw: String,
88  pub span: Span,
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
92pub struct Import {
93  pub raw: String,
94  pub span: Span,
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98pub struct Export {
99  pub raw: String,
100  pub span: Span,
101}
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104pub struct Heading {
105  pub level: u8,
106  pub children: Vec<Node>,
107  pub span: Span,
108  /// Pre-computed anchor id, populated by the `AssignHeadingIds` transform
109  /// (github-slugger algorithm with document-wide dedupe). When `None`,
110  /// `slug()` falls back to a fresh per-heading computation, so headings
111  /// emitted before the transform pass still have a usable anchor.
112  #[serde(default)]
113  pub id: Option<String>,
114}
115
116impl Heading {
117  /// URL-anchor slug. Returns the pre-computed `id` when available
118  /// (preferred - only the document-scoped pass can dedupe duplicates),
119  /// else a one-shot github-slugger computation from the heading text.
120  pub fn slug(&self) -> String {
121    if let Some(id) = &self.id {
122      return id.clone();
123    }
124    crate::slugger::github_slugify(&Self::plain_text(&self.children))
125  }
126
127  /// Flatten inline nodes to bare text. Recurses through emphasis and link
128  /// wrappers but skips JSX and images.
129  pub fn plain_text(nodes: &[Node]) -> String {
130    let mut s = String::new();
131    for n in nodes {
132      match n {
133        Node::Text(t) => s.push_str(&t.value),
134        Node::Bold(i) | Node::Italic(i) | Node::Strikethrough(i) => s.push_str(&Self::plain_text(&i.children)),
135        Node::Link(l) => s.push_str(&Self::plain_text(&l.children)),
136        Node::InlineCode(c) => s.push_str(&c.value),
137        _ => {},
138      }
139    }
140    s.trim().to_string()
141  }
142}
143
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct Paragraph {
146  pub children: Vec<Node>,
147  pub span: Span,
148}
149
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151pub struct Text {
152  pub value: String,
153  pub span: Span,
154}
155
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157pub struct Inline {
158  pub children: Vec<Node>,
159  pub span: Span,
160}
161
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163pub struct InlineCode {
164  pub value: String,
165  pub span: Span,
166}
167
168#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
169pub struct CodeBlock {
170  pub lang: Option<String>,
171  pub meta: Option<String>,
172  pub value: String,
173  pub span: Span,
174}
175
176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
177pub struct Link {
178  pub href: String,
179  pub title: Option<String>,
180  pub children: Vec<Node>,
181  pub span: Span,
182}
183
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
185pub struct Image {
186  pub src: String,
187  pub alt: String,
188  pub title: Option<String>,
189  pub span: Span,
190}
191
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct HorizontalRule {
194  pub span: Span,
195}
196
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
198pub struct Blockquote {
199  pub children: Vec<Node>,
200  pub span: Span,
201}
202
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
204pub struct List {
205  pub ordered: bool,
206  pub start: Option<u32>,
207  /// Children are `ListItem` or `TaskListItem` `Node` variants.
208  pub children: Vec<Node>,
209  pub span: Span,
210}
211
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213pub struct ListItem {
214  pub children: Vec<Node>,
215  pub span: Span,
216}
217
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
219pub struct TaskListItem {
220  pub checked: bool,
221  pub children: Vec<Node>,
222  pub span: Span,
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
226pub enum TableAlign {
227  None,
228  Left,
229  Right,
230  Center,
231}
232
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234pub struct Table {
235  pub align: Vec<TableAlign>,
236  pub children: Vec<TableRow>,
237  pub span: Span,
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
241pub struct TableRow {
242  pub cells: Vec<TableCell>,
243  pub span: Span,
244}
245
246#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247pub struct TableCell {
248  pub children: Vec<Node>,
249  pub span: Span,
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253pub struct JsxElement {
254  pub name: String,
255  pub attrs: Vec<crate::ast::JsxAttr>,
256  pub children: Vec<Node>,
257  pub span: Span,
258}
259
260#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
261pub struct JsxSelfClosing {
262  pub name: String,
263  pub attrs: Vec<crate::ast::JsxAttr>,
264  pub span: Span,
265}
266
267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
268pub struct JsxFragment {
269  pub children: Vec<Node>,
270  pub span: Span,
271}
272
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274pub struct JsxExpression {
275  pub value: String,
276  pub span: Span,
277}
278
279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
280pub struct BreakNode {
281  pub span: Span,
282}
283
284/// Raw HTML block (CommonMark 4.6). Lexer classifies the type via
285/// `HtmlBlockKind`; the parser captures the body verbatim and the
286/// renderer emits it untouched per CM rules.
287#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
288pub struct Html {
289  pub value: String,
290  pub span: Span,
291}
292
293/// GFM footnote reference (`[^id]`).
294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
295pub struct FootnoteRef {
296  pub id: String,
297  pub span: Span,
298}
299
300/// GFM footnote definition (`[^id]: body`). The body is an inline
301/// subtree; renderers number the definition globally on output.
302#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
303pub struct FootnoteDef {
304  pub id: String,
305  pub children: Vec<Node>,
306  pub span: Span,
307}