Skip to main content

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, sync::Arc, vec::Vec};
8
9use crate::diagnostic::Diagnostic;
10use crate::parse::Definitions;
11use crate::span::Span;
12
13/// Metadata attached to every AST node; currently just the source span.
14#[derive(Clone, Debug, Default, Eq, PartialEq)]
15pub struct NodeMeta {
16    /// The node's source location, or `None` for hand-built nodes.
17    pub span: Option<Span>,
18}
19
20impl NodeMeta {
21    /// Wrap an optional [`Span`] into a [`NodeMeta`].
22    pub const fn new(span: Option<Span>) -> Self {
23        Self { span }
24    }
25}
26
27/// The root of a document: a sequence of top-level [`Block`]s, and for a
28/// parsed document the source it was parsed from.
29///
30/// The source is the only truth: writing a parsed document back with
31/// [`Document::to_markdown`] reuses the source of every top-level block that
32/// did not change. Only [`parse()`](crate::parse()) gives a document a
33/// source; [`Document::without_source`] drops it, and a document without one
34/// is written as canonical Markdown.
35#[derive(Clone, Debug, Default, Eq, PartialEq)]
36pub struct Document {
37    /// Node metadata (source span).
38    pub meta: NodeMeta,
39    /// The document's top-level blocks, in source order.
40    pub children: Vec<Block>,
41    /// The input `parse` read, shared by clones.
42    source: Option<Arc<str>>,
43}
44
45impl Document {
46    /// A document holding `children` and no source.
47    pub fn new(children: Vec<Block>) -> Self {
48        Self {
49            meta: NodeMeta::default(),
50            children,
51            source: None,
52        }
53    }
54
55    /// The document `parse` returns: `children` read from `source`.
56    pub(crate) fn parsed(meta: NodeMeta, children: Vec<Block>, source: &str) -> Self {
57        Self {
58            meta,
59            children,
60            source: Some(Arc::from(source)),
61        }
62    }
63
64    /// The input this document was parsed from, or `None` for a document
65    /// built by hand or returned by [`Document::without_source`].
66    pub fn source(&self) -> Option<&str> {
67        self.source.as_deref()
68    }
69
70    /// This document without its source; writing it gives canonical
71    /// Markdown.
72    #[must_use]
73    pub fn without_source(mut self) -> Self {
74        self.source = None;
75        self
76    }
77}
78
79impl From<Vec<Block>> for Document {
80    fn from(children: Vec<Block>) -> Self {
81        Self::new(children)
82    }
83}
84
85/// Child indices from a document down to one node. A list's children are its
86/// items, a table's are its rows, and a row's are its cells; every other
87/// container's are its blocks.
88#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
89pub struct NodePath(pub Vec<usize>);
90
91impl From<Vec<usize>> for NodePath {
92    fn from(indices: Vec<usize>) -> Self {
93        Self(indices)
94    }
95}
96
97impl From<&[usize]> for NodePath {
98    fn from(indices: &[usize]) -> Self {
99        Self(indices.into())
100    }
101}
102
103impl<const N: usize> From<[usize; N]> for NodePath {
104    fn from(indices: [usize; N]) -> Self {
105        Self(indices.into())
106    }
107}
108
109/// A block-level node: the building blocks of a document's vertical structure.
110#[derive(Clone, Debug, Eq, PartialEq)]
111pub enum Block {
112    /// A paragraph of inline content.
113    Paragraph(Paragraph),
114    /// An ATX (`# h`) or setext (underlined) heading.
115    Heading(Heading),
116    /// A thematic break / horizontal rule: `---`, `***`, or `___`.
117    ThematicBreak(ThematicBreak),
118    /// A block quote: lines prefixed with `> `.
119    BlockQuote(BlockQuote),
120    /// A GFM alert / admonition: `> [!NOTE]` etc.
121    Alert(Alert),
122    /// A bullet or ordered list.
123    List(List),
124    /// A fenced (```` ``` ````) or indented code block.
125    CodeBlock(CodeBlock),
126    /// A raw HTML block.
127    HtmlBlock(HtmlBlock),
128    /// A structured raw HTML container whose interior is parsed as Markdown.
129    HtmlContainer(HtmlContainer),
130    /// A link reference definition: `[label]: url "title"`.
131    Definition(Definition),
132    /// A footnote definition: `[^id]: text`.
133    FootnoteDefinition(FootnoteDefinition),
134    /// A GFM pipe table.
135    Table(Table),
136    /// A display math block: `$$ … $$`.
137    MathBlock(MathBlock),
138    /// A leading frontmatter block (`---` YAML or `+++` TOML).
139    Frontmatter(Frontmatter),
140    /// A leaf directive: `::name[label]{attrs}`.
141    LeafDirective(LeafDirective),
142    /// A container directive: `:::name … :::`.
143    ContainerDirective(ContainerDirective),
144    /// A blank line beyond the one that separates two blocks.
145    BlankLine(BlankLine),
146}
147
148/// A paragraph: a run of inline content. Source: any plain text line(s).
149#[derive(Clone, Debug, Eq, PartialEq)]
150pub struct Paragraph {
151    /// Node metadata (source span).
152    pub meta: NodeMeta,
153    /// The paragraph's inline content.
154    pub children: Vec<Inline>,
155    /// The text the content was built from, by [`Paragraph::with_source`].
156    pub(crate) source: Option<String>,
157}
158
159/// A heading. Source: `# Title` (ATX) or `Title\n===` (setext).
160#[derive(Clone, Debug, Eq, PartialEq)]
161pub struct Heading {
162    /// Node metadata (source span).
163    pub meta: NodeMeta,
164    /// Heading level, 1..=6.
165    pub depth: u8,
166    /// Whether the heading used ATX or setext syntax.
167    pub kind: HeadingKind,
168    /// The heading's inline content.
169    pub children: Vec<Inline>,
170    /// The text the content was built from, by [`Heading::with_source`].
171    pub(crate) source: Option<String>,
172}
173
174/// Which heading syntax produced a [`Heading`].
175#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176pub enum HeadingKind {
177    /// ATX heading: `# Title` … `###### Title`.
178    Atx,
179    /// Setext heading: `Title` underlined with `===` (level 1) or `---` (level 2).
180    Setext,
181}
182
183/// A blank line the source holds beyond the one that separates two blocks:
184/// K blank lines between two blocks are one separator and K − 1 of these.
185/// Before a document's first block, at the start or end of a block quote,
186/// alert, HTML container, or container directive, and after a document's
187/// last line, each blank line is one. Source: a line holding only
188/// whitespace and its containers' `>` markers.
189#[derive(Clone, Debug, Default, Eq, PartialEq)]
190pub struct BlankLine {
191    /// Node metadata (source span): the line with its line ending.
192    pub meta: NodeMeta,
193}
194
195/// A thematic break / horizontal rule. Source: `---`, `***`, or `___`.
196#[derive(Clone, Debug, Eq, PartialEq)]
197pub struct ThematicBreak {
198    /// Node metadata (source span).
199    pub meta: NodeMeta,
200    /// Which character formed the break.
201    pub marker: ThematicBreakMarker,
202}
203
204/// The character used to draw a [`ThematicBreak`].
205#[derive(Clone, Copy, Debug, Eq, PartialEq)]
206pub enum ThematicBreakMarker {
207    /// Dashes: `---`.
208    Dash,
209    /// Asterisks: `***`.
210    Asterisk,
211    /// Underscores: `___`.
212    Underscore,
213}
214
215/// A block quote: content prefixed with `> `.
216#[derive(Clone, Debug, Eq, PartialEq)]
217pub struct BlockQuote {
218    /// Node metadata (source span).
219    pub meta: NodeMeta,
220    /// The quoted block content.
221    pub children: Vec<Block>,
222}
223
224/// A GFM alert / admonition. Source: `> [!NOTE]` followed by quoted content.
225#[derive(Clone, Debug, Eq, PartialEq)]
226pub struct Alert {
227    /// Node metadata (source span).
228    pub meta: NodeMeta,
229    /// The alert severity / type.
230    pub kind: AlertKind,
231    /// An optional custom title following the `[!KIND]` marker.
232    pub title: Option<String>,
233    /// The alert's block content.
234    pub children: Vec<Block>,
235}
236
237/// The kind of a GFM [`Alert`] (the `[!KIND]` marker).
238#[derive(Clone, Copy, Debug, Eq, PartialEq)]
239pub enum AlertKind {
240    /// `> [!NOTE]`.
241    Note,
242    /// `> [!TIP]`.
243    Tip,
244    /// `> [!IMPORTANT]`.
245    Important,
246    /// `> [!WARNING]`.
247    Warning,
248    /// `> [!CAUTION]`.
249    Caution,
250}
251
252/// A bullet or ordered list. Source: `- a` / `1. a` lines.
253#[derive(Clone, Debug, Eq, PartialEq)]
254pub struct List {
255    /// Node metadata (source span).
256    pub meta: NodeMeta,
257    /// `true` for an ordered list, `false` for a bullet list.
258    pub ordered: bool,
259    /// The marker delimiter used by the list items.
260    pub delimiter: ListDelimiter,
261    /// `true` if the list is tight (no blank lines between items / no `<p>`).
262    pub tight: bool,
263    /// The list's items.
264    pub children: Vec<ListItem>,
265}
266
267/// The marker character that delimits a list's items.
268#[derive(Clone, Copy, Debug, Eq, PartialEq)]
269pub enum ListDelimiter {
270    /// Bullet `-`.
271    Dash,
272    /// Bullet `*`.
273    Asterisk,
274    /// Bullet `+`.
275    Plus,
276    /// Ordered `1.`.
277    Period,
278    /// Ordered `1)`.
279    Paren,
280}
281
282/// A single list item, optionally a GFM task-list checkbox.
283#[derive(Clone, Debug, Eq, PartialEq)]
284pub struct ListItem {
285    /// Node metadata (source span).
286    pub meta: NodeMeta,
287    /// Task-list state: `Some(true)` for `[x]`, `Some(false)` for `[ ]`, `None` otherwise.
288    pub checked: Option<bool>,
289    /// The number an ordered item is written with (`3.` => `Some(3)`), which
290    /// for the first item is the list's start. `None` is written as the
291    /// previous item's number plus one, or 1 for the first item; a bullet
292    /// item holds `None`.
293    pub number: Option<u64>,
294    /// The item's block content.
295    pub children: Vec<Block>,
296}
297
298/// A code block. Source: ```` ```lang … ``` ```` (fenced) or 4-space-indented lines.
299#[derive(Clone, Debug, Eq, PartialEq)]
300pub struct CodeBlock {
301    /// Node metadata (source span).
302    pub meta: NodeMeta,
303    /// Whether the block is fenced (and with what fence) or indented.
304    pub kind: CodeBlockKind,
305    /// The info string after a fence (e.g. the `rust` in ```` ```rust ````).
306    pub info: Option<String>,
307    /// The literal code contents.
308    pub value: String,
309}
310
311/// Whether a [`CodeBlock`] is fenced or indented.
312#[derive(Clone, Copy, Debug, Eq, PartialEq)]
313pub enum CodeBlockKind {
314    /// A fenced code block; records the fence char and its run length.
315    Fenced {
316        /// Which character formed the fence (backtick or tilde).
317        marker: FenceMarker,
318        /// The number of fence characters in the opening fence (>=3).
319        length: usize,
320    },
321    /// A 4-space-indented code block.
322    Indented,
323}
324
325/// The character used to fence a [`CodeBlock`].
326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
327pub enum FenceMarker {
328    /// Backtick fence: ```` ``` ````.
329    Backtick,
330    /// Tilde fence: `~~~`.
331    Tilde,
332}
333
334/// A raw HTML block: HTML emitted verbatim.
335#[derive(Clone, Debug, Eq, PartialEq)]
336pub struct HtmlBlock {
337    /// Node metadata (source span).
338    pub meta: NodeMeta,
339    /// The literal HTML source.
340    pub value: String,
341}
342
343/// A structured raw HTML container, such as `<details> ... </details>`.
344#[derive(Clone, Debug, Eq, PartialEq)]
345pub struct HtmlContainer {
346    /// Node metadata (source span).
347    pub meta: NodeMeta,
348    /// The opening HTML tag.
349    pub opening: HtmlTag,
350    /// The parsed container content.
351    pub content: HtmlContainerContent,
352    /// The closing HTML tag.
353    pub closing: HtmlTag,
354}
355
356/// A raw HTML tag that bounds an [`HtmlContainer`].
357#[derive(Clone, Debug, Eq, PartialEq)]
358pub struct HtmlTag {
359    /// Node metadata (source span).
360    pub meta: NodeMeta,
361    /// The normalized lowercase tag name.
362    pub name: String,
363    /// The literal tag source, including angle brackets and attributes.
364    pub raw: String,
365}
366
367/// The parsed content inside an [`HtmlContainer`].
368#[derive(Clone, Debug, Eq, PartialEq)]
369pub enum HtmlContainerContent {
370    /// Block-level Markdown content.
371    Blocks(Vec<Block>),
372    /// Inline-level Markdown content.
373    Inlines(Vec<Inline>),
374}
375
376/// A link reference definition. Source: `[label]: destination "title"`.
377#[derive(Clone, Debug, Eq, PartialEq)]
378pub struct Definition {
379    /// Node metadata (source span).
380    pub meta: NodeMeta,
381    /// The label as written in the source (e.g. `Foo Bar`).
382    pub label: String,
383    /// The normalized lookup key (case-folded, whitespace-collapsed) for matching references.
384    pub identifier: String,
385    /// The link target URL.
386    pub destination: String,
387    /// How the destination was delimited (bare or `<…>`).
388    pub destination_kind: LinkDestinationKind,
389    /// The optional link title, with the quotes it is written in.
390    pub title: Option<Title>,
391}
392
393/// A footnote definition. Source: `[^id]: footnote text`.
394#[derive(Clone, Debug, Eq, PartialEq)]
395pub struct FootnoteDefinition {
396    /// Node metadata (source span).
397    pub meta: NodeMeta,
398    /// The label as written in the source (the text after `^`).
399    pub label: String,
400    /// The normalized lookup key matching [`FootnoteReference`]s.
401    pub identifier: String,
402    /// The footnote's block content.
403    pub children: Vec<Block>,
404}
405
406/// A GFM pipe table: a header row, an alignment row, then body rows. Source:
407/// `| a | b |` / `|---|---|` / `| 1 | 2 |`.
408#[derive(Clone, Debug, Eq, PartialEq)]
409pub struct Table {
410    /// Node metadata (source span).
411    pub meta: NodeMeta,
412    /// Per-column alignment from the delimiter row.
413    pub alignments: Vec<TableAlignment>,
414    /// All rows; the first is the header row.
415    pub rows: Vec<TableRow>,
416}
417
418/// The alignment of a [`Table`] column, from the `:---:` delimiter row.
419#[derive(Clone, Copy, Debug, Eq, PartialEq)]
420pub enum TableAlignment {
421    /// No explicit alignment: `---`.
422    None,
423    /// Left-aligned: `:---`.
424    Left,
425    /// Center-aligned: `:---:`.
426    Center,
427    /// Right-aligned: `---:`.
428    Right,
429}
430
431/// A single row of a [`Table`].
432#[derive(Clone, Debug, Eq, PartialEq)]
433pub struct TableRow {
434    /// Node metadata (source span).
435    pub meta: NodeMeta,
436    /// The row's cells.
437    pub cells: Vec<TableCell>,
438}
439
440/// A single cell of a [`TableRow`].
441#[derive(Clone, Debug, Eq, PartialEq)]
442pub struct TableCell {
443    /// Node metadata (source span).
444    pub meta: NodeMeta,
445    /// The cell's inline content.
446    pub children: Vec<Inline>,
447    /// The text the content was built from, by [`TableCell::with_source`].
448    pub(crate) source: Option<String>,
449}
450
451/// A display math block. Source: `$$ … $$`.
452#[derive(Clone, Debug, Eq, PartialEq)]
453pub struct MathBlock {
454    /// Node metadata (source span).
455    pub meta: NodeMeta,
456    /// The literal math contents (between the `$$` fences).
457    pub value: String,
458}
459
460/// A leading frontmatter block. Source: `---` YAML or `+++` TOML at the top of
461/// the document.
462#[derive(Clone, Debug, Eq, PartialEq)]
463pub struct Frontmatter {
464    /// Node metadata (source span).
465    pub meta: NodeMeta,
466    /// Whether the frontmatter is YAML or TOML.
467    pub kind: FrontmatterKind,
468    /// The literal frontmatter contents (between the fences).
469    pub value: String,
470}
471
472/// The format of a [`Frontmatter`] block.
473#[derive(Clone, Copy, Debug, Eq, PartialEq)]
474pub enum FrontmatterKind {
475    /// YAML frontmatter, fenced by `---`.
476    Yaml,
477    /// TOML frontmatter, fenced by `+++`.
478    Toml,
479}
480
481/// A leaf directive. Source: `::name[label]{attrs}`.
482#[derive(Clone, Debug, Eq, PartialEq)]
483pub struct LeafDirective {
484    /// Node metadata (source span).
485    pub meta: NodeMeta,
486    /// The directive name following the `::`.
487    pub name: String,
488    /// The optional `[label]` inline content.
489    pub label: Vec<Inline>,
490    /// The optional `{attrs}` attributes.
491    pub attributes: Vec<DirectiveAttribute>,
492    /// The text the label was built from, by [`LeafDirective::with_source`].
493    pub(crate) label_source: Option<String>,
494}
495
496/// A container directive. Source: `:::name[label]{attrs}` … `:::`.
497#[derive(Clone, Debug, Eq, PartialEq)]
498pub struct ContainerDirective {
499    /// Node metadata (source span).
500    pub meta: NodeMeta,
501    /// The directive name following the `:::`.
502    pub name: String,
503    /// The optional `[label]` inline content.
504    pub label: Vec<Inline>,
505    /// The optional `{attrs}` attributes.
506    pub attributes: Vec<DirectiveAttribute>,
507    /// The directive's enclosed block content.
508    pub children: Vec<Block>,
509    /// The text the label was built from, by
510    /// [`ContainerDirective::with_source`].
511    pub(crate) label_source: Option<String>,
512}
513
514/// An inline-level node: the leaf and span content inside blocks.
515#[derive(Clone, Debug, Eq, PartialEq)]
516pub enum Inline {
517    /// Literal text.
518    Text(Text),
519    /// A backslash escape such as `\*`.
520    Escape(Escape),
521    /// A character reference such as `&amp;` or `&#247;`.
522    CharacterReference(CharacterReference),
523    /// Emphasis: `*text*` or `_text_`.
524    Emphasis(Emphasis),
525    /// Strong emphasis: `**text**` or `__text__`.
526    Strong(Strong),
527    /// Strikethrough: `~~text~~`.
528    Delete(Delete),
529    /// A highlight / "mark" span: `==text==`.
530    Mark(Mark),
531    /// An emoji-style shortcode: `:name:`.
532    Shortcode(Shortcode),
533    /// An inline code span: `` `code` ``.
534    Code(CodeInline),
535    /// An inline link: `[text](url)`.
536    Link(Link),
537    /// An autolink: `<https://example.com>` or a bare literal URL or email.
538    Autolink(Autolink),
539    /// An inline image: `![alt](url)`.
540    Image(Image),
541    /// A reference link: `[text][label]`.
542    LinkReference(LinkReference),
543    /// A reference image: `![alt][label]`.
544    ImageReference(ImageReference),
545    /// Raw inline HTML such as `<span>`.
546    Html(HtmlInline),
547    /// A soft line break (a plain newline within a paragraph).
548    SoftBreak(SoftBreak),
549    /// A hard line break (`\` or two trailing spaces).
550    LineBreak(LineBreak),
551    /// Inline math: `$x$`.
552    Math(MathInline),
553    /// A footnote reference: `[^id]`.
554    FootnoteReference(FootnoteReference),
555    /// An inline footnote: `^[inline note]`.
556    InlineFootnote(InlineFootnote),
557    /// A wiki link: `[[target|label]]`.
558    WikiLink(WikiLink),
559    /// A text directive: `:name[label]{attrs}`.
560    TextDirective(TextDirective),
561    /// A tag: `#name`.
562    Tag(Tag),
563}
564
565/// Literal text content.
566#[derive(Clone, Debug, Eq, PartialEq)]
567pub struct Text {
568    /// Node metadata (source span).
569    pub meta: NodeMeta,
570    /// The text value.
571    pub value: String,
572}
573
574/// A backslash escape such as `\*` or `\\`.
575#[derive(Clone, Debug, Eq, PartialEq)]
576pub struct Escape {
577    /// Node metadata (source span).
578    pub meta: NodeMeta,
579    /// The escaped (literal) character.
580    pub value: char,
581}
582
583/// A character reference such as `&amp;` or `&#247;`. The reference as
584/// written is the node's one fact; [`CharacterReference::value`] decodes it.
585#[derive(Clone, Debug, Eq, PartialEq)]
586pub struct CharacterReference {
587    /// Node metadata (source span).
588    pub meta: NodeMeta,
589    /// The reference as written, including `&` and `;` (e.g. `&amp;`).
590    pub reference: String,
591}
592
593/// Emphasis (typically italic): `*text*` or `_text_`.
594#[derive(Clone, Debug, Eq, PartialEq)]
595pub struct Emphasis {
596    /// Node metadata (source span).
597    pub meta: NodeMeta,
598    /// The delimiter the span was written with.
599    pub delimiter: EmphasisDelimiter,
600    /// The emphasized inline content.
601    pub children: Vec<Inline>,
602}
603
604/// Strong emphasis (typically bold): `**text**` or `__text__`.
605#[derive(Clone, Debug, Eq, PartialEq)]
606pub struct Strong {
607    /// Node metadata (source span).
608    pub meta: NodeMeta,
609    /// The delimiter the span was written with.
610    pub delimiter: EmphasisDelimiter,
611    /// The strongly-emphasized inline content.
612    pub children: Vec<Inline>,
613}
614
615/// The delimiter character of an [`Emphasis`] or [`Strong`] span.
616#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
617pub enum EmphasisDelimiter {
618    /// `*text*` / `**text**`.
619    #[default]
620    Asterisk,
621    /// `_text_` / `__text__`.
622    Underscore,
623}
624
625/// Strikethrough: `~~text~~`.
626#[derive(Clone, Debug, Eq, PartialEq)]
627pub struct Delete {
628    /// Node metadata (source span).
629    pub meta: NodeMeta,
630    /// The struck-through inline content.
631    pub children: Vec<Inline>,
632}
633
634/// A highlight / "mark" span: `==text==`.
635#[derive(Clone, Debug, Eq, PartialEq)]
636pub struct Mark {
637    /// Node metadata (source span).
638    pub meta: NodeMeta,
639    /// The highlighted inline content.
640    pub children: Vec<Inline>,
641}
642
643/// An emoji shortcode: `:name:`, where `name` is a gemoji name or alias.
644#[derive(Clone, Debug, Eq, PartialEq)]
645pub struct Shortcode {
646    /// Node metadata (source span).
647    pub meta: NodeMeta,
648    /// The shortcode name between the colons (e.g. `smile` for `:smile:`).
649    pub name: String,
650}
651
652/// A tag: `#` and a name, such as `#rust` or `#ai/agents`.
653#[derive(Clone, Debug, Eq, PartialEq)]
654pub struct Tag {
655    /// Node metadata (source span).
656    pub meta: NodeMeta,
657    /// The name as written, without the `#`. A `/` separates levels, which
658    /// the caller splits.
659    pub name: String,
660}
661
662/// An inline code span: `` `code` ``. The fence and the padding spaces are
663/// not recorded: the serializer chooses them from the value.
664#[derive(Clone, Debug, Eq, PartialEq)]
665pub struct CodeInline {
666    /// Node metadata (source span).
667    pub meta: NodeMeta,
668    /// The code text, normalized as CommonMark reads it: line endings as
669    /// spaces, and one space stripped from each end when both ends hold one
670    /// and the text is not all spaces.
671    pub value: String,
672}
673
674/// An inline link: `[text](destination "title")`.
675#[derive(Clone, Debug, Eq, PartialEq)]
676pub struct Link {
677    /// Node metadata (source span).
678    pub meta: NodeMeta,
679    /// The link target URL.
680    pub destination: String,
681    /// How the destination was delimited (bare or `<…>`).
682    pub destination_kind: LinkDestinationKind,
683    /// The optional link title, with the quotes it is written in.
684    pub title: Option<Title>,
685    /// The link's inline content (the visible text).
686    pub children: Vec<Inline>,
687}
688
689/// An autolink: `<https://example.com>`, `<a@b.c>`, or a GFM literal
690/// autolink, a bare `https://…`, `www.…`, or email address. The text as
691/// written is the node's one fact; [`Autolink::destination`] derives the link
692/// target from it.
693#[derive(Clone, Debug, Eq, PartialEq)]
694pub struct Autolink {
695    /// Node metadata (source span). An angle-bracket autolink's span covers
696    /// its brackets.
697    pub meta: NodeMeta,
698    /// Which autolink syntax the text is written in.
699    pub form: AutolinkForm,
700    /// The URL or address as written, without an angle-bracket autolink's
701    /// brackets.
702    pub text: String,
703}
704
705/// How an [`Autolink`] is written.
706#[derive(Clone, Copy, Debug, Eq, PartialEq)]
707pub enum AutolinkForm {
708    /// An angle-bracket autolink: `<https://example.com>` or `<a@b.c>`.
709    Angle,
710    /// A GFM literal autolink: a bare `https://…`, `www.…`, or email address.
711    Literal,
712}
713
714/// An inline image: `![alt](destination "title")`.
715#[derive(Clone, Debug, Eq, PartialEq)]
716pub struct Image {
717    /// Node metadata (source span).
718    pub meta: NodeMeta,
719    /// The image source URL.
720    pub destination: String,
721    /// How the destination was delimited (bare or `<…>`).
722    pub destination_kind: LinkDestinationKind,
723    /// The optional image title, with the quotes it is written in.
724    pub title: Option<Title>,
725    /// The image's alt-text inline content.
726    pub alt: Vec<Inline>,
727}
728
729/// How a link/image destination was delimited in the source.
730#[derive(Clone, Copy, Debug, Eq, PartialEq)]
731pub enum LinkDestinationKind {
732    /// A bare destination: `(url)`.
733    Bare,
734    /// An angle-bracketed destination: `(<url>)`.
735    Angle,
736    /// No destination present: `()`.
737    Omitted,
738}
739
740/// How a link/image title was quoted in the source.
741#[derive(Clone, Copy, Debug, Eq, PartialEq)]
742pub enum LinkTitleKind {
743    /// Double-quoted: `"title"`.
744    DoubleQuote,
745    /// Single-quoted: `'title'`.
746    SingleQuote,
747    /// Parenthesized: `(title)`.
748    Paren,
749}
750
751/// The title of a [`Link`], [`Image`], or [`Definition`]: its text and the
752/// quotes it is written in.
753#[derive(Clone, Debug, Eq, PartialEq)]
754pub struct Title {
755    /// The title text, with its escapes and character references decoded.
756    pub value: String,
757    /// How the title is quoted.
758    pub kind: LinkTitleKind,
759}
760
761/// A reference link: `[text][label]`, `[text][]`, or `[text]`.
762#[derive(Clone, Debug, Eq, PartialEq)]
763pub struct LinkReference {
764    /// Node metadata (source span).
765    pub meta: NodeMeta,
766    /// The normalized lookup key matching a [`Definition`].
767    pub identifier: String,
768    /// The label as written in the source.
769    pub label: String,
770    /// Whether the reference is full, collapsed, or shortcut form.
771    pub kind: ReferenceKind,
772    /// The link's inline content (the visible text).
773    pub children: Vec<Inline>,
774}
775
776/// A reference image: `![alt][label]`, `![alt][]`, or `![alt]`.
777#[derive(Clone, Debug, Eq, PartialEq)]
778pub struct ImageReference {
779    /// Node metadata (source span).
780    pub meta: NodeMeta,
781    /// The normalized lookup key matching a [`Definition`].
782    pub identifier: String,
783    /// The label as written in the source.
784    pub label: String,
785    /// Whether the reference is full, collapsed, or shortcut form.
786    pub kind: ReferenceKind,
787    /// The image's alt-text inline content.
788    pub alt: Vec<Inline>,
789}
790
791/// The form of a reference link/image.
792#[derive(Clone, Copy, Debug, Eq, PartialEq)]
793pub enum ReferenceKind {
794    /// Full reference: `[text][label]`.
795    Full,
796    /// Collapsed reference: `[label][]`.
797    Collapsed,
798    /// Shortcut reference: `[label]`.
799    Shortcut,
800}
801
802/// Raw inline HTML such as `<span>` or `</em>`.
803#[derive(Clone, Debug, Eq, PartialEq)]
804pub struct HtmlInline {
805    /// Node metadata (source span).
806    pub meta: NodeMeta,
807    /// The literal HTML source.
808    pub value: String,
809}
810
811/// A soft line break: a plain newline within a paragraph.
812#[derive(Clone, Debug, Eq, PartialEq)]
813pub struct SoftBreak {
814    /// Node metadata (source span).
815    pub meta: NodeMeta,
816}
817
818/// A hard line break: a trailing `\` or two trailing spaces.
819#[derive(Clone, Debug, Eq, PartialEq)]
820pub struct LineBreak {
821    /// Node metadata (source span).
822    pub meta: NodeMeta,
823    /// Which syntax produced the break.
824    pub kind: LineBreakKind,
825}
826
827/// Which syntax produced a hard [`LineBreak`].
828#[derive(Clone, Copy, Debug, Eq, PartialEq)]
829pub enum LineBreakKind {
830    /// A trailing backslash: `\`.
831    Backslash,
832    /// Two or more trailing spaces.
833    Spaces,
834}
835
836/// Which syntax delimited an inline [`MathInline`] span.
837#[derive(Clone, Copy, Debug, Eq, PartialEq)]
838pub enum MathInlineKind {
839    /// Dollar-fenced inline math (`$…$`, `$$…$$`, …); `dollars` is the fence
840    /// length (>=1). Dollar math always renders inline, while a 2-dollar fence
841    /// is conventionally treated as display elsewhere in the ecosystem.
842    Dollar {
843        /// The number of `$` characters in the fence (>=1).
844        dollars: u8,
845    },
846    /// Math-code span: `$`…`$`.
847    Code,
848}
849
850/// Inline math: `$x$`.
851#[derive(Clone, Debug, Eq, PartialEq)]
852pub struct MathInline {
853    /// Node metadata (source span).
854    pub meta: NodeMeta,
855    /// The literal math contents (between the fences).
856    pub value: String,
857    /// Which delimiter syntax was used.
858    pub kind: MathInlineKind,
859}
860
861/// A footnote reference: `[^id]`.
862#[derive(Clone, Debug, Eq, PartialEq)]
863pub struct FootnoteReference {
864    /// Node metadata (source span).
865    pub meta: NodeMeta,
866    /// The label as written in the source (the text after `^`).
867    pub label: String,
868    /// The normalized lookup key matching a [`FootnoteDefinition`].
869    pub identifier: String,
870}
871
872/// An inline footnote: `^[inline note]`.
873#[derive(Clone, Debug, Eq, PartialEq)]
874pub struct InlineFootnote {
875    /// Node metadata (source span).
876    pub meta: NodeMeta,
877    /// The footnote's inline content.
878    pub children: Vec<Inline>,
879}
880
881/// A wiki link: `[[target|label]]`, or an embed: `![[target|label]]`.
882#[derive(Clone, Debug, Eq, PartialEq)]
883pub struct WikiLink {
884    /// Node metadata (source span). An embed's span starts at its `!`.
885    pub meta: NodeMeta,
886    /// The link target (page name) as written in the source, its backslash
887    /// escapes and character references kept; [`WikiLink::decoded_target`]
888    /// gives the text they stand for.
889    pub target: String,
890    /// The visible label as written in the source, its backslash escapes and
891    /// character references kept; [`WikiLink::decoded_label`] gives the text
892    /// they stand for. A label equal to `target` means the link is written
893    /// without a `|label` part.
894    pub label: String,
895    /// Whether a `!` directly before the `[[` marks the link as an embed.
896    pub embed: bool,
897}
898
899/// A text directive. Source: `:name[label]{attrs}`.
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    CodeBlock,
966    HtmlBlock,
967    HtmlContainer,
968    Definition,
969    FootnoteDefinition,
970    Table,
971    MathBlock,
972    Frontmatter,
973    LeafDirective,
974    ContainerDirective,
975    BlankLine,
976});
977
978impl_from_variants!(Block {
979    Paragraph(Paragraph), Heading(Heading), ThematicBreak(ThematicBreak),
980    BlockQuote(BlockQuote), Alert(Alert), List(List),
981    CodeBlock(CodeBlock), HtmlBlock(HtmlBlock), Definition(Definition),
982    HtmlContainer(HtmlContainer),
983    FootnoteDefinition(FootnoteDefinition), Table(Table), MathBlock(MathBlock),
984    Frontmatter(Frontmatter), LeafDirective(LeafDirective), ContainerDirective(ContainerDirective),
985    BlankLine(BlankLine),
986});
987
988impl_meta_accessors!(Inline {
989    Text,
990    Escape,
991    CharacterReference,
992    Emphasis,
993    Strong,
994    Delete,
995    Mark,
996    Shortcode,
997    Code,
998    Link,
999    Autolink,
1000    Image,
1001    LinkReference,
1002    ImageReference,
1003    Html,
1004    SoftBreak,
1005    LineBreak,
1006    Math,
1007    FootnoteReference,
1008    InlineFootnote,
1009    WikiLink,
1010    TextDirective,
1011    Tag,
1012});
1013
1014impl_from_variants!(Inline {
1015    Text(Text), Escape(Escape), CharacterReference(CharacterReference),
1016    Emphasis(Emphasis), Strong(Strong), Delete(Delete), Mark(Mark),
1017    Shortcode(Shortcode), Code(CodeInline), Link(Link), Autolink(Autolink), Image(Image),
1018    LinkReference(LinkReference), ImageReference(ImageReference), Html(HtmlInline),
1019    SoftBreak(SoftBreak), LineBreak(LineBreak), Math(MathInline),
1020    FootnoteReference(FootnoteReference), InlineFootnote(InlineFootnote), WikiLink(WikiLink),
1021    TextDirective(TextDirective), Tag(Tag),
1022});
1023
1024impl Inline {
1025    /// The inline subtree of this node, or an empty slice for a leaf. Covers the
1026    /// `alt`/`label` fields uniformly, so a generic walker never silently skips
1027    /// an image's alt text or a directive's label.
1028    pub fn children(&self) -> &[Inline] {
1029        match self {
1030            Inline::Emphasis(n) => &n.children,
1031            Inline::Strong(n) => &n.children,
1032            Inline::Delete(n) => &n.children,
1033            Inline::Mark(n) => &n.children,
1034            Inline::Link(n) => &n.children,
1035            Inline::Image(n) => &n.alt,
1036            Inline::LinkReference(n) => &n.children,
1037            Inline::ImageReference(n) => &n.alt,
1038            Inline::InlineFootnote(n) => &n.children,
1039            Inline::TextDirective(n) => &n.label,
1040            Inline::Text(_)
1041            | Inline::Escape(_)
1042            | Inline::CharacterReference(_)
1043            | Inline::Shortcode(_)
1044            | Inline::Code(_)
1045            | Inline::Autolink(_)
1046            | Inline::Html(_)
1047            | Inline::SoftBreak(_)
1048            | Inline::LineBreak(_)
1049            | Inline::Math(_)
1050            | Inline::FootnoteReference(_)
1051            | Inline::WikiLink(_)
1052            | Inline::Tag(_) => &[],
1053        }
1054    }
1055}
1056
1057impl CharacterReference {
1058    /// A character reference node for `reference`, written with its `&` and
1059    /// `;` (e.g. `&amp;`).
1060    pub fn new(reference: impl Into<String>) -> Self {
1061        Self {
1062            meta: NodeMeta::default(),
1063            reference: reference.into(),
1064        }
1065    }
1066
1067    /// The character the reference decodes to, or `None` when
1068    /// [`reference`](Self::reference) is not exactly one character reference.
1069    /// A valid document holds only references that decode.
1070    pub fn value(&self) -> Option<String> {
1071        crate::decode::decode_character_reference(&self.reference)
1072    }
1073}
1074
1075impl Autolink {
1076    /// An autolink of `form` whose text is `text`.
1077    pub fn new(form: AutolinkForm, text: impl Into<String>) -> Self {
1078        Self {
1079            meta: NodeMeta::default(),
1080            form,
1081            text: text.into(),
1082        }
1083    }
1084
1085    /// The link target the text writes, or `None` when the text is not
1086    /// exactly one autolink of its form. An angle-bracket URI links to itself
1087    /// and an email address to it after `mailto:`; a literal `www.` domain
1088    /// links to it after `http://`, a literal email address to it after
1089    /// `mailto:`, and any other literal autolink to itself. A valid document
1090    /// holds only autolinks with a destination.
1091    pub fn destination(&self) -> Option<String> {
1092        match self.form {
1093            AutolinkForm::Angle => crate::parse::angle_autolink_destination(&self.text),
1094            AutolinkForm::Literal => crate::parse::literal_autolink_destination(&self.text),
1095        }
1096    }
1097}
1098
1099impl Title {
1100    /// A title holding `value`, quoted as `kind`.
1101    pub fn new(value: impl Into<String>, kind: LinkTitleKind) -> Self {
1102        Self {
1103            value: value.into(),
1104            kind,
1105        }
1106    }
1107}
1108
1109impl Shortcode {
1110    /// The emoji the crate's pinned gemoji table gives this shortcode's
1111    /// name, or `None` for a name the table does not hold.
1112    pub fn glyph(&self) -> Option<&'static str> {
1113        crate::gemoji::glyph(&self.name)
1114    }
1115}
1116
1117impl Tag {
1118    /// A tag named `name`, written without its `#`.
1119    pub fn new(name: impl Into<String>) -> Self {
1120        Self {
1121            meta: NodeMeta::default(),
1122            name: name.into(),
1123        }
1124    }
1125}
1126
1127impl WikiLink {
1128    /// The target with its backslash escapes and character references
1129    /// decoded, as CommonMark decodes a link destination: `a \| b &amp; c`
1130    /// decodes to `a | b & c`.
1131    pub fn decoded_target(&self) -> String {
1132        crate::decode::decode_escapes_and_references(&self.target)
1133    }
1134
1135    /// The label with its backslash escapes and character references
1136    /// decoded, as [`WikiLink::decoded_target`] decodes the target.
1137    pub fn decoded_label(&self) -> String {
1138        crate::decode::decode_escapes_and_references(&self.label)
1139    }
1140}
1141
1142impl Text {
1143    /// A text node with the given string value.
1144    pub fn new(value: impl Into<String>) -> Self {
1145        Self {
1146            meta: NodeMeta::default(),
1147            value: value.into(),
1148        }
1149    }
1150}
1151
1152impl From<&str> for Text {
1153    fn from(value: &str) -> Self {
1154        Text::new(value)
1155    }
1156}
1157
1158impl From<String> for Text {
1159    fn from(value: String) -> Self {
1160        Text::new(value)
1161    }
1162}
1163
1164impl Paragraph {
1165    /// A paragraph from any iterator of inline-convertible children.
1166    pub fn new<I, T>(children: I) -> Self
1167    where
1168        I: IntoIterator<Item = T>,
1169        T: Into<Inline>,
1170    {
1171        Self {
1172            meta: NodeMeta::default(),
1173            children: children.into_iter().map(Into::into).collect(),
1174            source: None,
1175        }
1176    }
1177
1178    /// A paragraph whose content is `text`, read as
1179    /// [`parse_inline`](crate::parse_inline) reads it with `definitions`, and
1180    /// the diagnostics of that read. The paragraph keeps `text`, which the
1181    /// writer writes in place of its inlines while they are the ones `text`
1182    /// reads as; the spans of its inlines are offsets in `text`. Pass the
1183    /// definitions of the document the paragraph will be written in
1184    /// ([`Definitions::of`]); after they change, or for a leaf built with
1185    /// others, [`Document::reread_kept_sources`] reads its text again.
1186    pub fn with_source(text: &str, definitions: &Definitions) -> (Self, Vec<Diagnostic>) {
1187        let (children, diagnostics) = read_source(text, definitions);
1188        let paragraph = Self {
1189            source: Some(text.into()),
1190            ..Self::new(children)
1191        };
1192        (paragraph, diagnostics)
1193    }
1194
1195    /// The text [`Paragraph::with_source`] built the paragraph from.
1196    pub fn source(&self) -> Option<&str> {
1197        self.source.as_deref()
1198    }
1199}
1200
1201impl Heading {
1202    /// An ATX heading of the given depth.
1203    pub fn new<I, T>(depth: u8, children: I) -> Self
1204    where
1205        I: IntoIterator<Item = T>,
1206        T: Into<Inline>,
1207    {
1208        Self {
1209            meta: NodeMeta::default(),
1210            depth,
1211            kind: HeadingKind::Atx,
1212            children: children.into_iter().map(Into::into).collect(),
1213            source: None,
1214        }
1215    }
1216
1217    /// A heading of the given depth and kind whose content is `text`, built
1218    /// as [`Paragraph::with_source`] builds a paragraph.
1219    pub fn with_source(
1220        depth: u8,
1221        kind: HeadingKind,
1222        text: &str,
1223        definitions: &Definitions,
1224    ) -> (Self, Vec<Diagnostic>) {
1225        let (children, diagnostics) = read_source(text, definitions);
1226        let heading = Self {
1227            kind,
1228            source: Some(text.into()),
1229            ..Self::new(depth, children)
1230        };
1231        (heading, diagnostics)
1232    }
1233
1234    /// The text [`Heading::with_source`] built the heading from.
1235    pub fn source(&self) -> Option<&str> {
1236        self.source.as_deref()
1237    }
1238}
1239
1240impl TableCell {
1241    /// A cell from any iterator of inline-convertible children.
1242    pub fn new<I, T>(children: I) -> Self
1243    where
1244        I: IntoIterator<Item = T>,
1245        T: Into<Inline>,
1246    {
1247        Self {
1248            meta: NodeMeta::default(),
1249            children: children.into_iter().map(Into::into).collect(),
1250            source: None,
1251        }
1252    }
1253
1254    /// A cell whose content is `text`, built as [`Paragraph::with_source`]
1255    /// builds a paragraph. A `|` in `text` is content: the writer escapes it.
1256    pub fn with_source(text: &str, definitions: &Definitions) -> (Self, Vec<Diagnostic>) {
1257        let (children, diagnostics) = read_source(text, definitions);
1258        let cell = Self {
1259            source: Some(text.into()),
1260            ..Self::new(children)
1261        };
1262        (cell, diagnostics)
1263    }
1264
1265    /// The text [`TableCell::with_source`] built the cell from.
1266    pub fn source(&self) -> Option<&str> {
1267        self.source.as_deref()
1268    }
1269}
1270
1271impl LeafDirective {
1272    /// A leaf directive named `name`, with no label and no attributes.
1273    pub fn new(name: impl Into<String>) -> Self {
1274        Self {
1275            meta: NodeMeta::default(),
1276            name: name.into(),
1277            label: Vec::new(),
1278            attributes: Vec::new(),
1279            label_source: None,
1280        }
1281    }
1282
1283    /// A leaf directive named `name` whose label is `text`, built as
1284    /// [`Paragraph::with_source`] builds a paragraph's content.
1285    pub fn with_source(
1286        name: impl Into<String>,
1287        text: &str,
1288        definitions: &Definitions,
1289    ) -> (Self, Vec<Diagnostic>) {
1290        let (label, diagnostics) = read_source(text, definitions);
1291        let directive = Self {
1292            label,
1293            label_source: Some(text.into()),
1294            ..Self::new(name)
1295        };
1296        (directive, diagnostics)
1297    }
1298
1299    /// The text [`LeafDirective::with_source`] built the label from.
1300    pub fn label_source(&self) -> Option<&str> {
1301        self.label_source.as_deref()
1302    }
1303}
1304
1305impl ContainerDirective {
1306    /// A container directive named `name` holding `children`, with no label
1307    /// and no attributes.
1308    pub fn new(name: impl Into<String>, children: Vec<Block>) -> Self {
1309        Self {
1310            meta: NodeMeta::default(),
1311            name: name.into(),
1312            label: Vec::new(),
1313            attributes: Vec::new(),
1314            children,
1315            label_source: None,
1316        }
1317    }
1318
1319    /// A container directive named `name` holding `children`, whose label
1320    /// is `text`, built as [`Paragraph::with_source`] builds a paragraph's
1321    /// content.
1322    pub fn with_source(
1323        name: impl Into<String>,
1324        text: &str,
1325        children: Vec<Block>,
1326        definitions: &Definitions,
1327    ) -> (Self, Vec<Diagnostic>) {
1328        let (label, diagnostics) = read_source(text, definitions);
1329        let directive = Self {
1330            label,
1331            label_source: Some(text.into()),
1332            ..Self::new(name, children)
1333        };
1334        (directive, diagnostics)
1335    }
1336
1337    /// The text [`ContainerDirective::with_source`] built the label from.
1338    pub fn label_source(&self) -> Option<&str> {
1339        self.label_source.as_deref()
1340    }
1341}
1342
1343/// The inlines `text` reads as, with the diagnostics of the read.
1344fn read_source(text: &str, definitions: &Definitions) -> (Vec<Inline>, Vec<Diagnostic>) {
1345    let output = crate::parse::parse_inline_tree(text, definitions);
1346    (output.inlines, output.diagnostics)
1347}
1348
1349impl Link {
1350    /// An inline, bare-destination link with no title.
1351    pub fn new<I, T>(destination: impl Into<String>, children: I) -> Self
1352    where
1353        I: IntoIterator<Item = T>,
1354        T: Into<Inline>,
1355    {
1356        Self {
1357            meta: NodeMeta::default(),
1358            destination: destination.into(),
1359            destination_kind: LinkDestinationKind::Bare,
1360            title: None,
1361            children: children.into_iter().map(Into::into).collect(),
1362        }
1363    }
1364}
1365
1366impl CodeInline {
1367    /// An inline code span holding `value`.
1368    pub fn new(value: impl Into<String>) -> Self {
1369        Self {
1370            meta: NodeMeta::default(),
1371            value: value.into(),
1372        }
1373    }
1374}
1375
1376impl List {
1377    /// A tight, dash-delimited bullet list.
1378    pub fn new<I>(children: I) -> Self
1379    where
1380        I: IntoIterator<Item = ListItem>,
1381    {
1382        Self {
1383            meta: NodeMeta::default(),
1384            ordered: false,
1385            delimiter: ListDelimiter::Dash,
1386            tight: true,
1387            children: children.into_iter().collect(),
1388        }
1389    }
1390}
1391
1392impl ListItem {
1393    /// An item holding `children`, with no task marker and no number.
1394    pub fn new<I, T>(children: I) -> Self
1395    where
1396        I: IntoIterator<Item = T>,
1397        T: Into<Block>,
1398    {
1399        Self {
1400            meta: NodeMeta::default(),
1401            checked: None,
1402            number: None,
1403            children: children.into_iter().map(Into::into).collect(),
1404        }
1405    }
1406}
1407
1408impl BlankLine {
1409    /// A blank line.
1410    pub fn new() -> Self {
1411        Self::default()
1412    }
1413}