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  /// Anchor id populated by the `AssignHeadingIds` transform (document-wide
109  /// dedupe). When `None`, `slug()` falls back to a per-heading computation.
110  #[serde(default)]
111  pub id: Option<String>,
112}
113
114impl Heading {
115  /// URL-anchor slug. Prefers the pre-computed `id` (only the document-scoped
116  /// pass can dedupe duplicates); else recomputes from heading text.
117  pub fn slug(&self) -> String {
118    if let Some(id) = &self.id {
119      return id.clone();
120    }
121    crate::slugger::github_slugify(&Self::plain_text(&self.children))
122  }
123
124  /// Flatten inline nodes to bare text. Recurses through emphasis and link
125  /// wrappers; skips JSX and images.
126  pub fn plain_text(nodes: &[Node]) -> String {
127    let mut s = String::new();
128    for n in nodes {
129      match n {
130        Node::Text(t) => s.push_str(&t.value),
131        Node::Bold(i) | Node::Italic(i) | Node::Strikethrough(i) => s.push_str(&Self::plain_text(&i.children)),
132        Node::Link(l) => s.push_str(&Self::plain_text(&l.children)),
133        Node::InlineCode(c) => s.push_str(&c.value),
134        _ => {},
135      }
136    }
137    s.trim().to_string()
138  }
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142pub struct Paragraph {
143  pub children: Vec<Node>,
144  pub span: Span,
145}
146
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
148pub struct Text {
149  pub value: String,
150  pub span: Span,
151}
152
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
154pub struct Inline {
155  pub children: Vec<Node>,
156  pub span: Span,
157}
158
159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct InlineCode {
161  pub value: String,
162  pub span: Span,
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
166pub struct CodeBlock {
167  pub lang: Option<String>,
168  pub meta: Option<String>,
169  pub value: String,
170  pub span: Span,
171}
172
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct Link {
175  pub href: String,
176  pub title: Option<String>,
177  pub children: Vec<Node>,
178  pub span: Span,
179}
180
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct Image {
183  pub src: String,
184  pub alt: String,
185  pub title: Option<String>,
186  pub span: Span,
187}
188
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190pub struct HorizontalRule {
191  pub span: Span,
192}
193
194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
195pub struct Blockquote {
196  pub children: Vec<Node>,
197  pub span: Span,
198}
199
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201pub struct List {
202  pub ordered: bool,
203  pub start: Option<u32>,
204  /// Children are `ListItem` or `TaskListItem` `Node` variants.
205  pub children: Vec<Node>,
206  pub span: Span,
207}
208
209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
210pub struct ListItem {
211  pub children: Vec<Node>,
212  pub span: Span,
213}
214
215#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
216pub struct TaskListItem {
217  pub checked: bool,
218  pub children: Vec<Node>,
219  pub span: Span,
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
223pub enum TableAlign {
224  None,
225  Left,
226  Right,
227  Center,
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
231pub struct Table {
232  pub align: Vec<TableAlign>,
233  pub children: Vec<TableRow>,
234  pub span: Span,
235}
236
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
238pub struct TableRow {
239  pub cells: Vec<TableCell>,
240  pub span: Span,
241}
242
243#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
244pub struct TableCell {
245  pub children: Vec<Node>,
246  pub span: Span,
247}
248
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250pub struct JsxElement {
251  pub name: String,
252  pub attrs: Vec<crate::ast::JsxAttr>,
253  pub children: Vec<Node>,
254  pub span: Span,
255}
256
257#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
258pub struct JsxSelfClosing {
259  pub name: String,
260  pub attrs: Vec<crate::ast::JsxAttr>,
261  pub span: Span,
262}
263
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
265pub struct JsxFragment {
266  pub children: Vec<Node>,
267  pub span: Span,
268}
269
270#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
271pub struct JsxExpression {
272  pub value: String,
273  pub span: Span,
274}
275
276#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
277pub struct BreakNode {
278  pub span: Span,
279}
280
281/// Raw HTML block (CM 4.6). Body captured verbatim; renderer emits untouched.
282#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
283pub struct Html {
284  pub value: String,
285  pub span: Span,
286}
287
288/// GFM footnote reference (`[^id]`).
289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
290pub struct FootnoteRef {
291  pub id: String,
292  pub span: Span,
293}
294
295/// GFM footnote definition (`[^id]: body`). Body is an inline subtree;
296/// renderers number definitions globally on output.
297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
298pub struct FootnoteDef {
299  pub id: String,
300  pub children: Vec<Node>,
301  pub span: Span,
302}