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