markdown_syntax/ast.rs
1//! The owned Markdown AST that [`parse()`](crate::parse()) produces and
2//! `Document::to_markdown`/`to_html` consume. Every node carries a [`NodeMeta`]
3//! with an optional source [`Span`]. [`Block`] and [`Inline`] are the two node
4//! enums; everything else is a concrete node struct or a small enum describing
5//! a node's variant.
6
7use alloc::{string::String, vec::Vec};
8
9use crate::span::Span;
10
11/// Metadata attached to every AST node; currently just the source span.
12#[derive(Clone, Debug, Default, Eq, PartialEq)]
13pub struct NodeMeta {
14 /// The node's source location, or `None` for hand-built nodes.
15 pub span: Option<Span>,
16}
17
18impl NodeMeta {
19 /// Wrap an optional [`Span`] into a [`NodeMeta`].
20 pub const fn new(span: Option<Span>) -> Self {
21 Self { span }
22 }
23}
24
25/// The root of a parsed document: a sequence of top-level [`Block`]s.
26#[derive(Clone, Debug, Default, Eq, PartialEq)]
27pub struct Document {
28 /// Node metadata (source span).
29 pub meta: NodeMeta,
30 /// The document's top-level blocks, in source order.
31 pub children: Vec<Block>,
32}
33
34/// A block-level node: the building blocks of a document's vertical structure.
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub enum Block {
37 /// A paragraph of inline content.
38 Paragraph(Paragraph),
39 /// An ATX (`# h`) or setext (underlined) heading.
40 Heading(Heading),
41 /// A thematic break / horizontal rule: `---`, `***`, or `___`.
42 ThematicBreak(ThematicBreak),
43 /// A block quote: lines prefixed with `> `.
44 BlockQuote(BlockQuote),
45 /// A GFM alert / admonition: `> [!NOTE]` etc.
46 Alert(Alert),
47 /// A bullet or ordered list.
48 List(List),
49 /// A description / definition list (term + details).
50 DescriptionList(DescriptionList),
51 /// A fenced (```` ``` ````) or indented code block.
52 CodeBlock(CodeBlock),
53 /// A raw HTML block.
54 HtmlBlock(HtmlBlock),
55 /// A link reference definition: `[label]: url "title"`.
56 Definition(Definition),
57 /// A footnote definition: `[^id]: text`.
58 FootnoteDefinition(FootnoteDefinition),
59 /// A GFM pipe table.
60 Table(Table),
61 /// A display math block: `$$ … $$`.
62 MathBlock(MathBlock),
63 /// A leading frontmatter block (`---` YAML or `+++` TOML).
64 Frontmatter(Frontmatter),
65 /// An MDX ESM block (`import`/`export` statements).
66 MdxEsm(MdxEsm),
67 /// A block-level MDX expression: `{ … }`.
68 MdxExpression(MdxExpression),
69 /// A block-level MDX JSX element.
70 MdxJsx(MdxJsx),
71 /// A leaf directive: `::name[label]{attrs}` (distinct from MDX).
72 LeafDirective(LeafDirective),
73 /// A container directive: `:::name … :::` (distinct from MDX).
74 ContainerDirective(ContainerDirective),
75}
76
77/// A paragraph: a run of inline content. Source: any plain text line(s).
78#[derive(Clone, Debug, Eq, PartialEq)]
79pub struct Paragraph {
80 /// Node metadata (source span).
81 pub meta: NodeMeta,
82 /// The paragraph's inline content.
83 pub children: Vec<Inline>,
84}
85
86/// A heading. Source: `# Title` (ATX) or `Title\n===` (setext).
87#[derive(Clone, Debug, Eq, PartialEq)]
88pub struct Heading {
89 /// Node metadata (source span).
90 pub meta: NodeMeta,
91 /// Heading level, 1..=6.
92 pub depth: u8,
93 /// Whether the heading used ATX or setext syntax.
94 pub kind: HeadingKind,
95 /// The heading's inline content.
96 pub children: Vec<Inline>,
97}
98
99/// Which heading syntax produced a [`Heading`].
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101pub enum HeadingKind {
102 /// ATX heading: `# Title` … `###### Title`.
103 Atx,
104 /// Setext heading: `Title` underlined with `===` (level 1) or `---` (level 2).
105 Setext,
106}
107
108/// A thematic break / horizontal rule. Source: `---`, `***`, or `___`.
109#[derive(Clone, Debug, Eq, PartialEq)]
110pub struct ThematicBreak {
111 /// Node metadata (source span).
112 pub meta: NodeMeta,
113 /// Which character formed the break.
114 pub marker: ThematicBreakMarker,
115}
116
117/// The character used to draw a [`ThematicBreak`].
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub enum ThematicBreakMarker {
120 /// Dashes: `---`.
121 Dash,
122 /// Asterisks: `***`.
123 Asterisk,
124 /// Underscores: `___`.
125 Underscore,
126}
127
128/// A block quote: content prefixed with `> `.
129#[derive(Clone, Debug, Eq, PartialEq)]
130pub struct BlockQuote {
131 /// Node metadata (source span).
132 pub meta: NodeMeta,
133 /// The quoted block content.
134 pub children: Vec<Block>,
135}
136
137/// A GFM alert / admonition. Source: `> [!NOTE]` followed by quoted content.
138#[derive(Clone, Debug, Eq, PartialEq)]
139pub struct Alert {
140 /// Node metadata (source span).
141 pub meta: NodeMeta,
142 /// The alert severity / type.
143 pub kind: AlertKind,
144 /// An optional custom title following the `[!KIND]` marker.
145 pub title: Option<String>,
146 /// The alert's block content.
147 pub children: Vec<Block>,
148}
149
150/// The kind of a GFM [`Alert`] (the `[!KIND]` marker).
151#[derive(Clone, Copy, Debug, Eq, PartialEq)]
152pub enum AlertKind {
153 /// `> [!NOTE]`.
154 Note,
155 /// `> [!TIP]`.
156 Tip,
157 /// `> [!IMPORTANT]`.
158 Important,
159 /// `> [!WARNING]`.
160 Warning,
161 /// `> [!CAUTION]`.
162 Caution,
163}
164
165/// A bullet or ordered list. Source: `- a` / `1. a` lines.
166#[derive(Clone, Debug, Eq, PartialEq)]
167pub struct List {
168 /// Node metadata (source span).
169 pub meta: NodeMeta,
170 /// `true` for an ordered list, `false` for a bullet list.
171 pub ordered: bool,
172 /// The starting number of an ordered list (e.g. `3.` => `Some(3)`).
173 pub start: Option<u64>,
174 /// The marker delimiter used by the list items.
175 pub delimiter: ListDelimiter,
176 /// `true` if the list is tight (no blank lines between items / no `<p>`).
177 pub tight: bool,
178 /// The list's items.
179 pub children: Vec<ListItem>,
180}
181
182/// The marker character that delimits a list's items.
183#[derive(Clone, Copy, Debug, Eq, PartialEq)]
184pub enum ListDelimiter {
185 /// Bullet `-`.
186 Dash,
187 /// Bullet `*`.
188 Asterisk,
189 /// Bullet `+`.
190 Plus,
191 /// Ordered `1.`.
192 Period,
193 /// Ordered `1)`.
194 Paren,
195}
196
197/// A single list item, optionally a GFM task-list checkbox.
198#[derive(Clone, Debug, Eq, PartialEq)]
199pub struct ListItem {
200 /// Node metadata (source span).
201 pub meta: NodeMeta,
202 /// Task-list state: `Some(true)` for `[x]`, `Some(false)` for `[ ]`, `None` otherwise.
203 pub checked: Option<bool>,
204 /// The item's block content.
205 pub children: Vec<Block>,
206}
207
208/// A description / definition list of term + details pairs.
209#[derive(Clone, Debug, Eq, PartialEq)]
210pub struct DescriptionList {
211 /// Node metadata (source span).
212 pub meta: NodeMeta,
213 /// `true` if the list is tight (no blank lines between items).
214 pub tight: bool,
215 /// The list's term/details items.
216 pub children: Vec<DescriptionItem>,
217}
218
219/// One entry of a [`DescriptionList`]: a term and its detail blocks. Source: a
220/// term line followed by `: details` lines.
221#[derive(Clone, Debug, Eq, PartialEq)]
222pub struct DescriptionItem {
223 /// Node metadata (source span).
224 pub meta: NodeMeta,
225 /// The term's inline content.
226 pub term: Vec<Inline>,
227 /// The detail group(s) attached to this term.
228 pub details: Vec<DescriptionDetails>,
229}
230
231/// The details (`: …`) attached to a [`DescriptionItem`]'s term.
232#[derive(Clone, Debug, Eq, PartialEq)]
233pub struct DescriptionDetails {
234 /// Node metadata (source span).
235 pub meta: NodeMeta,
236 /// The details' block content.
237 pub children: Vec<Block>,
238}
239
240/// A code block. Source: ```` ```lang … ``` ```` (fenced) or 4-space-indented lines.
241#[derive(Clone, Debug, Eq, PartialEq)]
242pub struct CodeBlock {
243 /// Node metadata (source span).
244 pub meta: NodeMeta,
245 /// Whether the block is fenced (and with what fence) or indented.
246 pub kind: CodeBlockKind,
247 /// The info string after a fence (e.g. the `rust` in ```` ```rust ````).
248 pub info: Option<String>,
249 /// The literal code contents.
250 pub value: String,
251}
252
253/// Whether a [`CodeBlock`] is fenced or indented.
254#[derive(Clone, Copy, Debug, Eq, PartialEq)]
255pub enum CodeBlockKind {
256 /// A fenced code block; records the fence char and its run length.
257 Fenced {
258 /// Which character formed the fence (backtick or tilde).
259 marker: FenceMarker,
260 /// The number of fence characters in the opening fence (>=3).
261 length: usize,
262 },
263 /// A 4-space-indented code block.
264 Indented,
265}
266
267/// The character used to fence a [`CodeBlock`].
268#[derive(Clone, Copy, Debug, Eq, PartialEq)]
269pub enum FenceMarker {
270 /// Backtick fence: ```` ``` ````.
271 Backtick,
272 /// Tilde fence: `~~~`.
273 Tilde,
274}
275
276/// A raw HTML block: HTML emitted verbatim.
277#[derive(Clone, Debug, Eq, PartialEq)]
278pub struct HtmlBlock {
279 /// Node metadata (source span).
280 pub meta: NodeMeta,
281 /// The literal HTML source.
282 pub value: String,
283}
284
285/// A link reference definition. Source: `[label]: destination "title"`.
286#[derive(Clone, Debug, Eq, PartialEq)]
287pub struct Definition {
288 /// Node metadata (source span).
289 pub meta: NodeMeta,
290 /// The label as written in the source (e.g. `Foo Bar`).
291 pub label: String,
292 /// The normalized lookup key (case-folded, whitespace-collapsed) for matching references.
293 pub identifier: String,
294 /// The link target URL.
295 pub destination: String,
296 /// How the destination was delimited (bare or `<…>`).
297 pub destination_kind: LinkDestinationKind,
298 /// The optional link title.
299 pub title: Option<String>,
300 /// How the title was quoted, if present.
301 pub title_kind: Option<LinkTitleKind>,
302}
303
304/// A footnote definition. Source: `[^id]: footnote text`.
305#[derive(Clone, Debug, Eq, PartialEq)]
306pub struct FootnoteDefinition {
307 /// Node metadata (source span).
308 pub meta: NodeMeta,
309 /// The label as written in the source (the text after `^`).
310 pub label: String,
311 /// The normalized lookup key matching [`FootnoteReference`]s.
312 pub identifier: String,
313 /// The footnote's block content.
314 pub children: Vec<Block>,
315}
316
317/// A GFM pipe table: a header row, an alignment row, then body rows. Source:
318/// `| a | b |` / `|---|---|` / `| 1 | 2 |`.
319#[derive(Clone, Debug, Eq, PartialEq)]
320pub struct Table {
321 /// Node metadata (source span).
322 pub meta: NodeMeta,
323 /// Per-column alignment from the delimiter row.
324 pub alignments: Vec<TableAlignment>,
325 /// All rows; the first is the header row.
326 pub rows: Vec<TableRow>,
327}
328
329/// The alignment of a [`Table`] column, from the `:---:` delimiter row.
330#[derive(Clone, Copy, Debug, Eq, PartialEq)]
331pub enum TableAlignment {
332 /// No explicit alignment: `---`.
333 None,
334 /// Left-aligned: `:---`.
335 Left,
336 /// Center-aligned: `:---:`.
337 Center,
338 /// Right-aligned: `---:`.
339 Right,
340}
341
342/// A single row of a [`Table`].
343#[derive(Clone, Debug, Eq, PartialEq)]
344pub struct TableRow {
345 /// Node metadata (source span).
346 pub meta: NodeMeta,
347 /// The row's cells.
348 pub cells: Vec<TableCell>,
349}
350
351/// A single cell of a [`TableRow`].
352#[derive(Clone, Debug, Eq, PartialEq)]
353pub struct TableCell {
354 /// Node metadata (source span).
355 pub meta: NodeMeta,
356 /// The cell's inline content.
357 pub children: Vec<Inline>,
358}
359
360/// A display math block. Source: `$$ … $$`.
361#[derive(Clone, Debug, Eq, PartialEq)]
362pub struct MathBlock {
363 /// Node metadata (source span).
364 pub meta: NodeMeta,
365 /// The literal math contents (between the `$$` fences).
366 pub value: String,
367}
368
369/// A leading frontmatter block. Source: `---` YAML or `+++` TOML at the top of
370/// the document.
371#[derive(Clone, Debug, Eq, PartialEq)]
372pub struct Frontmatter {
373 /// Node metadata (source span).
374 pub meta: NodeMeta,
375 /// Whether the frontmatter is YAML or TOML.
376 pub kind: FrontmatterKind,
377 /// The literal frontmatter contents (between the fences).
378 pub value: String,
379}
380
381/// The format of a [`Frontmatter`] block.
382#[derive(Clone, Copy, Debug, Eq, PartialEq)]
383pub enum FrontmatterKind {
384 /// YAML frontmatter, fenced by `---`.
385 Yaml,
386 /// TOML frontmatter, fenced by `+++`.
387 Toml,
388}
389
390/// An MDX ESM block: top-level `import`/`export` statements (distinct from directives).
391#[derive(Clone, Debug, Eq, PartialEq)]
392pub struct MdxEsm {
393 /// Node metadata (source span).
394 pub meta: NodeMeta,
395 /// The literal ESM source.
396 pub value: String,
397}
398
399/// A block-level MDX expression: `{ … }` (distinct from directives).
400#[derive(Clone, Debug, Eq, PartialEq)]
401pub struct MdxExpression {
402 /// Node metadata (source span).
403 pub meta: NodeMeta,
404 /// The literal expression source (between the braces).
405 pub value: String,
406}
407
408/// A block-level MDX JSX element (distinct from directives).
409#[derive(Clone, Debug, Eq, PartialEq)]
410pub struct MdxJsx {
411 /// Node metadata (source span).
412 pub meta: NodeMeta,
413 /// The literal JSX source.
414 pub value: String,
415}
416
417/// A leaf directive. Source: `::name[label]{attrs}` (a directive feature,
418/// not MDX).
419#[derive(Clone, Debug, Eq, PartialEq)]
420pub struct LeafDirective {
421 /// Node metadata (source span).
422 pub meta: NodeMeta,
423 /// The directive name following the `::`.
424 pub name: String,
425 /// The optional `[label]` inline content.
426 pub label: Vec<Inline>,
427 /// The optional `{attrs}` attributes.
428 pub attributes: Vec<DirectiveAttribute>,
429}
430
431/// A container directive. Source: `:::name[label]{attrs}` … `:::` (a directive
432/// feature, not MDX).
433#[derive(Clone, Debug, Eq, PartialEq)]
434pub struct ContainerDirective {
435 /// Node metadata (source span).
436 pub meta: NodeMeta,
437 /// The directive name following the `:::`.
438 pub name: String,
439 /// The optional `[label]` inline content.
440 pub label: Vec<Inline>,
441 /// The optional `{attrs}` attributes.
442 pub attributes: Vec<DirectiveAttribute>,
443 /// The directive's enclosed block content.
444 pub children: Vec<Block>,
445}
446
447/// An inline-level node: the leaf and span content inside blocks.
448#[derive(Clone, Debug, Eq, PartialEq)]
449pub enum Inline {
450 /// Literal text.
451 Text(Text),
452 /// A backslash escape such as `\*`.
453 Escape(Escape),
454 /// A character reference such as `&` or `÷`.
455 CharacterReference(CharacterReference),
456 /// Emphasis: `*text*` or `_text_`.
457 Emphasis(Emphasis),
458 /// Strong emphasis: `**text**` or `__text__`.
459 Strong(Strong),
460 /// Underline: `__text__`/`___text___` (underscore extension).
461 Underline(Underline),
462 /// Strikethrough: `~~text~~`.
463 Delete(Delete),
464 /// A CriticMarkup-style insertion: `++text++`.
465 Insert(Insert),
466 /// A highlight / "mark" span: `==text==`.
467 Mark(Mark),
468 /// Subscript: `~x~`.
469 Subscript(Subscript),
470 /// Superscript: `^x^`.
471 Superscript(Superscript),
472 /// A spoiler span: `||text||`.
473 Spoiler(Spoiler),
474 /// An emoji-style shortcode: `:name:`.
475 Shortcode(Shortcode),
476 /// An inline code span: `` `code` ``.
477 Code(CodeInline),
478 /// An inline link: `[text](url)`.
479 Link(Link),
480 /// An inline image: ``.
481 Image(Image),
482 /// A reference link: `[text][label]`.
483 LinkReference(LinkReference),
484 /// A reference image: `![alt][label]`.
485 ImageReference(ImageReference),
486 /// An autolink: `<url>` or a GFM bare URL.
487 Autolink(Autolink),
488 /// Raw inline HTML such as `<span>`.
489 Html(HtmlInline),
490 /// A soft line break (a plain newline within a paragraph).
491 SoftBreak(SoftBreak),
492 /// A hard line break (`\` or two trailing spaces).
493 LineBreak(LineBreak),
494 /// Inline math: `$x$`.
495 Math(MathInline),
496 /// A footnote reference: `[^id]`.
497 FootnoteReference(FootnoteReference),
498 /// An inline footnote: `^[inline note]`.
499 InlineFootnote(InlineFootnote),
500 /// A wiki link: `[[target|label]]`.
501 WikiLink(WikiLink),
502 /// An inline MDX expression: `{ … }` (distinct from directives).
503 MdxExpression(MdxExpressionInline),
504 /// An inline MDX JSX element (distinct from directives).
505 MdxJsx(MdxJsxInline),
506 /// A text directive: `:name[label]{attrs}` (distinct from MDX).
507 TextDirective(TextDirective),
508}
509
510/// Literal text content.
511#[derive(Clone, Debug, Eq, PartialEq)]
512pub struct Text {
513 /// Node metadata (source span).
514 pub meta: NodeMeta,
515 /// The text value.
516 pub value: String,
517}
518
519/// A backslash escape such as `\*` or `\\`.
520#[derive(Clone, Debug, Eq, PartialEq)]
521pub struct Escape {
522 /// Node metadata (source span).
523 pub meta: NodeMeta,
524 /// The escaped (literal) character.
525 pub value: char,
526}
527
528/// A character reference such as `&` or `÷`.
529#[derive(Clone, Debug, Eq, PartialEq)]
530pub struct CharacterReference {
531 /// Node metadata (source span).
532 pub meta: NodeMeta,
533 /// The reference as written, including `&` and `;` (e.g. `amp` for `&`).
534 pub reference: String,
535 /// The resolved character value (e.g. `&` for `&`).
536 pub value: String,
537}
538
539/// Emphasis (typically italic): `*text*` or `_text_`.
540#[derive(Clone, Debug, Eq, PartialEq)]
541pub struct Emphasis {
542 /// Node metadata (source span).
543 pub meta: NodeMeta,
544 /// The emphasized inline content.
545 pub children: Vec<Inline>,
546}
547
548/// Strong emphasis (typically bold): `**text**` or `__text__`.
549#[derive(Clone, Debug, Eq, PartialEq)]
550pub struct Strong {
551 /// Node metadata (source span).
552 pub meta: NodeMeta,
553 /// The strongly-emphasized inline content.
554 pub children: Vec<Inline>,
555}
556
557/// Underline (underscore extension): `__text__` or `___text___`.
558#[derive(Clone, Debug, Eq, PartialEq)]
559pub struct Underline {
560 /// Node metadata (source span).
561 pub meta: NodeMeta,
562 /// The underlined inline content.
563 pub children: Vec<Inline>,
564}
565
566/// Strikethrough: `~~text~~` (or single `~text~` when single-tilde is enabled).
567#[derive(Clone, Debug, Eq, PartialEq)]
568pub struct Delete {
569 /// Node metadata (source span).
570 pub meta: NodeMeta,
571 /// Whether the span used one or two tildes.
572 pub marker: DeleteMarker,
573 /// The struck-through inline content.
574 pub children: Vec<Inline>,
575}
576
577/// Which tilde run delimited a [`Delete`] span.
578#[derive(Clone, Copy, Debug, Eq, PartialEq)]
579pub enum DeleteMarker {
580 /// Single-tilde strikethrough: `~text~`.
581 SingleTilde,
582 /// Double-tilde strikethrough: `~~text~~`.
583 DoubleTilde,
584}
585
586/// A CriticMarkup-style insertion: `++text++`.
587#[derive(Clone, Debug, Eq, PartialEq)]
588pub struct Insert {
589 /// Node metadata (source span).
590 pub meta: NodeMeta,
591 /// The inserted inline content.
592 pub children: Vec<Inline>,
593}
594
595/// A highlight / "mark" span: `==text==`.
596#[derive(Clone, Debug, Eq, PartialEq)]
597pub struct Mark {
598 /// Node metadata (source span).
599 pub meta: NodeMeta,
600 /// The highlighted inline content.
601 pub children: Vec<Inline>,
602}
603
604/// Subscript: `~x~`.
605#[derive(Clone, Debug, Eq, PartialEq)]
606pub struct Subscript {
607 /// Node metadata (source span).
608 pub meta: NodeMeta,
609 /// The subscripted inline content.
610 pub children: Vec<Inline>,
611}
612
613/// Superscript: `^x^`.
614#[derive(Clone, Debug, Eq, PartialEq)]
615pub struct Superscript {
616 /// Node metadata (source span).
617 pub meta: NodeMeta,
618 /// The superscripted inline content.
619 pub children: Vec<Inline>,
620}
621
622/// A spoiler span: `||text||`.
623#[derive(Clone, Debug, Eq, PartialEq)]
624pub struct Spoiler {
625 /// Node metadata (source span).
626 pub meta: NodeMeta,
627 /// The hidden inline content.
628 pub children: Vec<Inline>,
629}
630
631/// An emoji-style shortcode: `:name:`.
632#[derive(Clone, Debug, Eq, PartialEq)]
633pub struct Shortcode {
634 /// Node metadata (source span).
635 pub meta: NodeMeta,
636 /// The shortcode name between the colons (e.g. `smile` for `:smile:`).
637 pub name: String,
638}
639
640/// An inline code span: `` `code` ``.
641#[derive(Clone, Debug, Eq, PartialEq)]
642pub struct CodeInline {
643 /// Node metadata (source span).
644 pub meta: NodeMeta,
645 /// The normalized code text (trimmed/collapsed per CommonMark).
646 pub value: String,
647 /// The raw text between the backtick fences, before normalization.
648 pub raw: String,
649 /// The number of backticks in the fence.
650 pub fence_length: usize,
651}
652
653/// An inline link: `[text](destination "title")`.
654#[derive(Clone, Debug, Eq, PartialEq)]
655pub struct Link {
656 /// Node metadata (source span).
657 pub meta: NodeMeta,
658 /// The link target URL.
659 pub destination: String,
660 /// How the destination was delimited (bare or `<…>`).
661 pub destination_kind: LinkDestinationKind,
662 /// The optional link title.
663 pub title: Option<String>,
664 /// How the title was quoted, if present.
665 pub title_kind: Option<LinkTitleKind>,
666 /// The link's inline content (the visible text).
667 pub children: Vec<Inline>,
668}
669
670/// An inline image: ``.
671#[derive(Clone, Debug, Eq, PartialEq)]
672pub struct Image {
673 /// Node metadata (source span).
674 pub meta: NodeMeta,
675 /// The image source URL.
676 pub destination: String,
677 /// How the destination was delimited (bare or `<…>`).
678 pub destination_kind: LinkDestinationKind,
679 /// The optional image title.
680 pub title: Option<String>,
681 /// How the title was quoted, if present.
682 pub title_kind: Option<LinkTitleKind>,
683 /// The image's alt-text inline content.
684 pub alt: Vec<Inline>,
685}
686
687/// How a link/image destination was delimited in the source.
688#[derive(Clone, Copy, Debug, Eq, PartialEq)]
689pub enum LinkDestinationKind {
690 /// A bare destination: `(url)`.
691 Bare,
692 /// An angle-bracketed destination: `(<url>)`.
693 Angle,
694 /// No destination present: `()`.
695 Omitted,
696}
697
698/// How a link/image title was quoted in the source.
699#[derive(Clone, Copy, Debug, Eq, PartialEq)]
700pub enum LinkTitleKind {
701 /// Double-quoted: `"title"`.
702 DoubleQuote,
703 /// Single-quoted: `'title'`.
704 SingleQuote,
705 /// Parenthesized: `(title)`.
706 Paren,
707}
708
709/// A reference link: `[text][label]`, `[text][]`, or `[text]`.
710#[derive(Clone, Debug, Eq, PartialEq)]
711pub struct LinkReference {
712 /// Node metadata (source span).
713 pub meta: NodeMeta,
714 /// The normalized lookup key matching a [`Definition`].
715 pub identifier: String,
716 /// The label as written in the source.
717 pub label: String,
718 /// Whether the reference is full, collapsed, or shortcut form.
719 pub kind: ReferenceKind,
720 /// The link's inline content (the visible text).
721 pub children: Vec<Inline>,
722}
723
724/// A reference image: `![alt][label]`, `![alt][]`, or `![alt]`.
725#[derive(Clone, Debug, Eq, PartialEq)]
726pub struct ImageReference {
727 /// Node metadata (source span).
728 pub meta: NodeMeta,
729 /// The normalized lookup key matching a [`Definition`].
730 pub identifier: String,
731 /// The label as written in the source.
732 pub label: String,
733 /// Whether the reference is full, collapsed, or shortcut form.
734 pub kind: ReferenceKind,
735 /// The image's alt-text inline content.
736 pub alt: Vec<Inline>,
737}
738
739/// The form of a reference link/image.
740#[derive(Clone, Copy, Debug, Eq, PartialEq)]
741pub enum ReferenceKind {
742 /// Full reference: `[text][label]`.
743 Full,
744 /// Collapsed reference: `[label][]`.
745 Collapsed,
746 /// Shortcut reference: `[label]`.
747 Shortcut,
748}
749
750/// An autolink: `<url>` or a GFM bare URL.
751#[derive(Clone, Debug, Eq, PartialEq)]
752pub struct Autolink {
753 /// Node metadata (source span).
754 pub meta: NodeMeta,
755 /// The resolved link href.
756 pub destination: String,
757 /// Whether the link was angle-bracketed or a GFM literal.
758 pub kind: AutolinkKind,
759}
760
761/// Whether an [`Autolink`] is angle-bracketed or a GFM bare literal.
762#[derive(Clone, Debug, Eq, PartialEq)]
763pub enum AutolinkKind {
764 /// An angle-bracket autolink `<dest>`. The destination is the raw text
765 /// between the brackets; `>` is forbidden in the destination and the
766 /// serializer re-emits `<dest>`.
767 Angle,
768 /// A GFM literal autolink (bare `www.`/`http(s)://`/`mailto:`/`xmpp:` URL
769 /// or email). `original` is the raw source text that produced the link
770 /// (the visible label); `destination` is the synthesized href (e.g. a
771 /// `http://`/`mailto:` prefix may have been prepended). The serializer
772 /// re-emits `original`, which re-parses to the same literal.
773 GfmLiteral {
774 /// The raw source text that produced the link (the visible label).
775 original: String,
776 },
777}
778
779/// Raw inline HTML such as `<span>` or `</em>`.
780#[derive(Clone, Debug, Eq, PartialEq)]
781pub struct HtmlInline {
782 /// Node metadata (source span).
783 pub meta: NodeMeta,
784 /// The literal HTML source.
785 pub value: String,
786}
787
788/// A soft line break: a plain newline within a paragraph.
789#[derive(Clone, Debug, Eq, PartialEq)]
790pub struct SoftBreak {
791 /// Node metadata (source span).
792 pub meta: NodeMeta,
793}
794
795/// A hard line break: a trailing `\` or two trailing spaces.
796#[derive(Clone, Debug, Eq, PartialEq)]
797pub struct LineBreak {
798 /// Node metadata (source span).
799 pub meta: NodeMeta,
800 /// Which syntax produced the break.
801 pub kind: LineBreakKind,
802}
803
804/// Which syntax produced a hard [`LineBreak`].
805#[derive(Clone, Copy, Debug, Eq, PartialEq)]
806pub enum LineBreakKind {
807 /// A trailing backslash: `\`.
808 Backslash,
809 /// Two or more trailing spaces.
810 Spaces,
811}
812
813/// Which syntax delimited an inline [`MathInline`] span.
814#[derive(Clone, Copy, Debug, Eq, PartialEq)]
815pub enum MathInlineKind {
816 /// Dollar-fenced inline math (`$…$`, `$$…$$`, …); `dollars` is the fence
817 /// length (>=1). Dollar math always renders inline, while a 2-dollar fence
818 /// is conventionally treated as display elsewhere in the ecosystem.
819 Dollar {
820 /// The number of `$` characters in the fence (>=1).
821 dollars: u8,
822 },
823 /// Math-code span: `$`…`$`.
824 Code,
825}
826
827/// Inline math: `$x$`.
828#[derive(Clone, Debug, Eq, PartialEq)]
829pub struct MathInline {
830 /// Node metadata (source span).
831 pub meta: NodeMeta,
832 /// The literal math contents (between the fences).
833 pub value: String,
834 /// Which delimiter syntax was used.
835 pub kind: MathInlineKind,
836}
837
838/// A footnote reference: `[^id]`.
839#[derive(Clone, Debug, Eq, PartialEq)]
840pub struct FootnoteReference {
841 /// Node metadata (source span).
842 pub meta: NodeMeta,
843 /// The label as written in the source (the text after `^`).
844 pub label: String,
845 /// The normalized lookup key matching a [`FootnoteDefinition`].
846 pub identifier: String,
847}
848
849/// An inline footnote: `^[inline note]`.
850#[derive(Clone, Debug, Eq, PartialEq)]
851pub struct InlineFootnote {
852 /// Node metadata (source span).
853 pub meta: NodeMeta,
854 /// The footnote's inline content.
855 pub children: Vec<Inline>,
856}
857
858/// A wiki link: `[[target|label]]`.
859#[derive(Clone, Debug, Eq, PartialEq)]
860pub struct WikiLink {
861 /// Node metadata (source span).
862 pub meta: NodeMeta,
863 /// The link target (page name).
864 pub target: String,
865 /// The visible label.
866 pub label: String,
867 /// Whether the label appeared before or after the `|` in the source.
868 pub label_order: WikiLinkLabelOrder,
869}
870
871/// Whether a [`WikiLink`]'s label preceded or followed the `|` separator.
872#[derive(Clone, Copy, Debug, Eq, PartialEq)]
873pub enum WikiLinkLabelOrder {
874 /// Target then label: `[[target|label]]`.
875 AfterPipe,
876 /// Label then target: `[[label|target]]`.
877 BeforePipe,
878}
879
880/// An inline MDX expression: `{ … }` (distinct from directives).
881#[derive(Clone, Debug, Eq, PartialEq)]
882pub struct MdxExpressionInline {
883 /// Node metadata (source span).
884 pub meta: NodeMeta,
885 /// The literal expression source (between the braces).
886 pub value: String,
887}
888
889/// An inline MDX JSX element (distinct from directives).
890#[derive(Clone, Debug, Eq, PartialEq)]
891pub struct MdxJsxInline {
892 /// Node metadata (source span).
893 pub meta: NodeMeta,
894 /// The literal JSX source.
895 pub value: String,
896}
897
898/// A text directive. Source: `:name[label]{attrs}` (a directive feature,
899/// not MDX).
900#[derive(Clone, Debug, Eq, PartialEq)]
901pub struct TextDirective {
902 /// Node metadata (source span).
903 pub meta: NodeMeta,
904 /// The directive name following the `:`.
905 pub name: String,
906 /// The optional `[label]` inline content.
907 pub label: Vec<Inline>,
908 /// The optional `{attrs}` attributes.
909 pub attributes: Vec<DirectiveAttribute>,
910}
911
912/// One attribute of a directive's `{name=value}` block.
913#[derive(Clone, Debug, Eq, PartialEq)]
914pub struct DirectiveAttribute {
915 /// The attribute name.
916 pub name: String,
917 /// The attribute value, or `None` for a valueless attribute.
918 pub value: Option<String>,
919}
920
921// ---------------------------------------------------------------------------
922// Ergonomic accessors and a minimal construction layer.
923//
924// Every node carries a `meta: NodeMeta`, so `meta()`/`span()` are uniform across
925// the enums and free callers from writing an exhaustive match just to read a
926// span. `From`/`new` collapse the `Variant(Struct { meta, .. })` boilerplate for
927// hand-built ASTs; the raw struct literals remain available for full control.
928// ---------------------------------------------------------------------------
929
930macro_rules! impl_meta_accessors {
931 ($enum:ident { $($variant:ident),+ $(,)? }) => {
932 impl $enum {
933 /// Borrow this node's [`NodeMeta`].
934 pub fn meta(&self) -> &NodeMeta {
935 match self { $( $enum::$variant(node) => &node.meta, )+ }
936 }
937
938 /// This node's source span, if it carries one.
939 pub fn span(&self) -> Option<Span> {
940 self.meta().span
941 }
942 }
943 };
944}
945
946macro_rules! impl_from_variants {
947 ($enum:ident { $($variant:ident($ty:ty)),+ $(,)? }) => {
948 $(
949 impl From<$ty> for $enum {
950 fn from(node: $ty) -> Self {
951 $enum::$variant(node)
952 }
953 }
954 )+
955 };
956}
957
958impl_meta_accessors!(Block {
959 Paragraph,
960 Heading,
961 ThematicBreak,
962 BlockQuote,
963 Alert,
964 List,
965 DescriptionList,
966 CodeBlock,
967 HtmlBlock,
968 Definition,
969 FootnoteDefinition,
970 Table,
971 MathBlock,
972 Frontmatter,
973 MdxEsm,
974 MdxExpression,
975 MdxJsx,
976 LeafDirective,
977 ContainerDirective,
978});
979
980impl_from_variants!(Block {
981 Paragraph(Paragraph), Heading(Heading), ThematicBreak(ThematicBreak),
982 BlockQuote(BlockQuote), Alert(Alert), List(List), DescriptionList(DescriptionList),
983 CodeBlock(CodeBlock), HtmlBlock(HtmlBlock), Definition(Definition),
984 FootnoteDefinition(FootnoteDefinition), Table(Table), MathBlock(MathBlock),
985 Frontmatter(Frontmatter), MdxEsm(MdxEsm), MdxExpression(MdxExpression),
986 MdxJsx(MdxJsx), LeafDirective(LeafDirective), ContainerDirective(ContainerDirective),
987});
988
989impl_meta_accessors!(Inline {
990 Text,
991 Escape,
992 CharacterReference,
993 Emphasis,
994 Strong,
995 Underline,
996 Delete,
997 Insert,
998 Mark,
999 Subscript,
1000 Superscript,
1001 Spoiler,
1002 Shortcode,
1003 Code,
1004 Link,
1005 Image,
1006 LinkReference,
1007 ImageReference,
1008 Autolink,
1009 Html,
1010 SoftBreak,
1011 LineBreak,
1012 Math,
1013 FootnoteReference,
1014 InlineFootnote,
1015 WikiLink,
1016 MdxExpression,
1017 MdxJsx,
1018 TextDirective,
1019});
1020
1021impl_from_variants!(Inline {
1022 Text(Text), Escape(Escape), CharacterReference(CharacterReference),
1023 Emphasis(Emphasis), Strong(Strong), Underline(Underline), Delete(Delete),
1024 Insert(Insert), Mark(Mark), Subscript(Subscript), Superscript(Superscript),
1025 Spoiler(Spoiler), Shortcode(Shortcode), Code(CodeInline), Link(Link), Image(Image),
1026 LinkReference(LinkReference), ImageReference(ImageReference), Autolink(Autolink),
1027 Html(HtmlInline), SoftBreak(SoftBreak), LineBreak(LineBreak), Math(MathInline),
1028 FootnoteReference(FootnoteReference), InlineFootnote(InlineFootnote), WikiLink(WikiLink),
1029 MdxExpression(MdxExpressionInline), MdxJsx(MdxJsxInline), TextDirective(TextDirective),
1030});
1031
1032impl Inline {
1033 /// The inline subtree of this node, or an empty slice for a leaf. Covers the
1034 /// `alt`/`label` fields uniformly, so a generic walker never silently skips
1035 /// an image's alt text or a directive's label.
1036 pub fn children(&self) -> &[Inline] {
1037 match self {
1038 Inline::Emphasis(n) => &n.children,
1039 Inline::Strong(n) => &n.children,
1040 Inline::Underline(n) => &n.children,
1041 Inline::Delete(n) => &n.children,
1042 Inline::Insert(n) => &n.children,
1043 Inline::Mark(n) => &n.children,
1044 Inline::Subscript(n) => &n.children,
1045 Inline::Superscript(n) => &n.children,
1046 Inline::Spoiler(n) => &n.children,
1047 Inline::Link(n) => &n.children,
1048 Inline::Image(n) => &n.alt,
1049 Inline::LinkReference(n) => &n.children,
1050 Inline::ImageReference(n) => &n.alt,
1051 Inline::InlineFootnote(n) => &n.children,
1052 Inline::TextDirective(n) => &n.label,
1053 _ => &[],
1054 }
1055 }
1056}
1057
1058impl Text {
1059 /// A text node with the given string value.
1060 pub fn new(value: impl Into<String>) -> Self {
1061 Self {
1062 meta: NodeMeta::default(),
1063 value: value.into(),
1064 }
1065 }
1066}
1067
1068impl From<&str> for Text {
1069 fn from(value: &str) -> Self {
1070 Text::new(value)
1071 }
1072}
1073
1074impl From<String> for Text {
1075 fn from(value: String) -> Self {
1076 Text::new(value)
1077 }
1078}
1079
1080impl Paragraph {
1081 /// A paragraph from any iterator of inline-convertible children.
1082 pub fn new<I, T>(children: I) -> Self
1083 where
1084 I: IntoIterator<Item = T>,
1085 T: Into<Inline>,
1086 {
1087 Self {
1088 meta: NodeMeta::default(),
1089 children: children.into_iter().map(Into::into).collect(),
1090 }
1091 }
1092}
1093
1094impl Heading {
1095 /// An ATX heading of the given depth.
1096 pub fn new<I, T>(depth: u8, children: I) -> Self
1097 where
1098 I: IntoIterator<Item = T>,
1099 T: Into<Inline>,
1100 {
1101 Self {
1102 meta: NodeMeta::default(),
1103 depth,
1104 kind: HeadingKind::Atx,
1105 children: children.into_iter().map(Into::into).collect(),
1106 }
1107 }
1108}
1109
1110impl Link {
1111 /// A bare-destination link with no title.
1112 pub fn new<I, T>(destination: impl Into<String>, children: I) -> Self
1113 where
1114 I: IntoIterator<Item = T>,
1115 T: Into<Inline>,
1116 {
1117 Self {
1118 meta: NodeMeta::default(),
1119 destination: destination.into(),
1120 destination_kind: LinkDestinationKind::Bare,
1121 title: None,
1122 title_kind: None,
1123 children: children.into_iter().map(Into::into).collect(),
1124 }
1125 }
1126}
1127
1128impl CodeInline {
1129 /// An inline code span with a single-backtick fence.
1130 pub fn new(value: impl Into<String>) -> Self {
1131 let value = value.into();
1132 Self {
1133 meta: NodeMeta::default(),
1134 raw: value.clone(),
1135 value,
1136 fence_length: 1,
1137 }
1138 }
1139}
1140
1141impl List {
1142 /// A tight, dash-delimited bullet list.
1143 pub fn new<I>(children: I) -> Self
1144 where
1145 I: IntoIterator<Item = ListItem>,
1146 {
1147 Self {
1148 meta: NodeMeta::default(),
1149 ordered: false,
1150 start: None,
1151 delimiter: ListDelimiter::Dash,
1152 tight: true,
1153 children: children.into_iter().collect(),
1154 }
1155 }
1156}