Skip to main content

oxc_yaml_parser/
ast.rs

1//! AST node definitions.
2//!
3//! Node naming follows [yaml-unist-parser](https://github.com/prettier/yaml-unist-parser)'s unist AST
4//! The shapes themselves follow this crate's own span principles:
5//!
6//! - Child spans nest inside their parent's span; [`Node`] wraps properties
7//!   and content so anchors/tags are inside the node they apply to.
8//! - A span is the construct's full lexical extent: indicators (`?`, `:`)
9//!   are inside the wrapper they introduce, and a wrapper without source
10//!   evidence is `None` rather than an empty-span node.
11//! - Token extent and semantic boundaries are separate data
12//!   ([`BlockScalar::span`] vs `content_start`/`content_end`).
13//!
14//! Two further deliberate departures from yaml-unist-parser:
15//! - Scalar nodes do not carry cooked values; consumers slice the original source through [`Span`]s.
16//! - Comments are not attached to nodes
17//!   (yaml-unist-parser's leading/middle/trailing/end comment fields have no counterpart here).
18//!   They live in [`Root::comments`] in source order; consumers place them positionally via spans.
19
20use crate::pos::Span;
21use oxc_allocator::{Box, Vec};
22
23/// A `#` comment. `span` covers `#` through the end of the comment text.
24#[derive(Clone, Copy, Debug)]
25pub struct Comment {
26    pub span: Span,
27}
28
29/// `&name`. `span` covers the `&` and the name.
30#[derive(Clone, Copy, Debug)]
31pub struct Anchor {
32    pub span: Span,
33}
34
35/// A tag property: `!`, `!suffix`, `!handle!suffix`, `!!suffix` or `!<verbatim>`.
36#[derive(Clone, Copy, Debug)]
37pub struct Tag {
38    pub span: Span,
39}
40
41/// A node's properties (`&anchor` / `!tag`), in either source order.
42#[derive(Clone, Copy, Debug)]
43pub struct Props {
44    pub anchor: Option<Anchor>,
45    pub tag: Option<Tag>,
46}
47
48impl Props {
49    /// Start of the first property, if any.
50    pub fn start(&self) -> Option<u32> {
51        match (self.anchor, self.tag) {
52            (Some(a), Some(t)) => Some(a.span.start.min(t.span.start)),
53            (Some(a), None) => Some(a.span.start),
54            (None, Some(t)) => Some(t.span.start),
55            (None, None) => None,
56        }
57    }
58}
59
60/// The whole stream.
61#[derive(Debug)]
62pub struct Root<'a> {
63    pub span: Span,
64    pub children: Vec<'a, Document<'a>>,
65    /// Every comment in the stream, in source order. Comments are not
66    /// attached to nodes; consumers place them positionally via spans
67    /// (the comment-cursor pattern).
68    pub comments: Vec<'a, Comment>,
69}
70
71#[derive(Debug)]
72#[expect(clippy::struct_field_names)] // mirrors yaml-unist-parser's field names
73pub struct Document<'a> {
74    pub span: Span,
75    pub head: DocumentHead<'a>,
76    pub body: DocumentBody<'a>,
77    /// Span of the `---` marker if present.
78    pub directives_end_marker: Option<Span>,
79    /// Span of the `...` marker if present.
80    pub document_end_marker: Option<Span>,
81}
82
83#[derive(Debug)]
84pub struct DocumentHead<'a> {
85    pub span: Span,
86    pub directives: Vec<'a, Directive<'a>>,
87}
88
89#[derive(Debug)]
90pub struct DocumentBody<'a> {
91    pub span: Span,
92    pub content: Option<Box<'a, Node<'a>>>,
93}
94
95/// `%NAME param param`. Uninterpreted; `%YAML`/`%TAG`/unknown are all accepted.
96#[derive(Debug)]
97pub struct Directive<'a> {
98    pub span: Span,
99    pub name: &'a str,
100    pub parameters: Vec<'a, &'a str>,
101}
102
103/// A YAML node: optional properties (anchor/tag) plus the content they apply to
104/// (the spec's `node ::= properties? content` production).
105///
106/// `span` covers the props through the content end, so every child span
107/// (props, content, nested nodes) nests inside it.
108///
109/// Node positions hold `Box<Node>` so container children
110/// ([`MappingItem`] / [`SequenceItem`]) stay small in their `Vec`s;
111/// the rarely-present `Props` would otherwise inflate every element.
112#[derive(Debug)]
113pub struct Node<'a> {
114    pub span: Span,
115    pub props: Props,
116    pub content: Content<'a>,
117}
118
119/// A node's content (mirrors yaml-unist-parser's `ContentNode`).
120#[derive(Debug)]
121pub enum Content<'a> {
122    Plain(Box<'a, Plain>),
123    QuoteSingle(Box<'a, QuoteSingle>),
124    QuoteDouble(Box<'a, QuoteDouble>),
125    BlockLiteral(Box<'a, BlockScalar>),
126    BlockFolded(Box<'a, BlockScalar>),
127    Mapping(Box<'a, Mapping<'a>>),
128    Sequence(Box<'a, Sequence<'a>>),
129    FlowMapping(Box<'a, FlowMapping<'a>>),
130    FlowSequence(Box<'a, FlowSequence<'a>>),
131    Alias(Box<'a, Alias>),
132}
133
134impl Content<'_> {
135    pub fn span(&self) -> Span {
136        match self {
137            Content::Plain(n) => n.span,
138            Content::QuoteSingle(n) => n.span,
139            Content::QuoteDouble(n) => n.span,
140            Content::BlockLiteral(n) | Content::BlockFolded(n) => n.span,
141            Content::Mapping(n) => n.span,
142            Content::Sequence(n) => n.span,
143            Content::FlowMapping(n) => n.span,
144            Content::FlowSequence(n) => n.span,
145            Content::Alias(n) => n.span,
146        }
147    }
148}
149
150/// A plain (unquoted) scalar. `span` covers the raw scalar text
151/// (trailing whitespace/comments excluded).
152#[derive(Debug)]
153pub struct Plain {
154    pub span: Span,
155}
156
157/// `'...'`. `span` includes the quotes.
158#[derive(Debug)]
159pub struct QuoteSingle {
160    pub span: Span,
161}
162
163/// `"..."`. `span` includes the quotes.
164#[derive(Debug)]
165pub struct QuoteDouble {
166    pub span: Span,
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
170pub enum Chomping {
171    /// (default) single trailing newline
172    Clip,
173    /// `+` keep all trailing newlines
174    Keep,
175    /// `-` strip all trailing newlines
176    Strip,
177}
178
179/// `|` (literal) or `>` (folded) block scalar.
180///
181/// The variant is distinguished by the enclosing [`Content`] variant. `span`
182/// covers the indicator through the end of the content (including trailing
183/// line breaks consumed while scanning — they are the token's lexical extent,
184/// and under keep chomping part of the value).
185#[derive(Debug)]
186pub struct BlockScalar {
187    pub span: Span,
188    pub chomping: Chomping,
189    /// Explicit indentation indicator digit, if any.
190    pub indent: Option<u32>,
191    /// Offset right after the header line's line break (= where content
192    /// scanning began). The content text is `content_start..content_end`.
193    pub content_start: u32,
194    /// Offset right after the last content character (before the trailing
195    /// break run). `content_end..span.end` holds only line breaks and
196    /// blank-line indentation.
197    pub content_end: u32,
198}
199
200/// A block mapping.
201#[derive(Debug)]
202pub struct Mapping<'a> {
203    pub span: Span,
204    pub children: Vec<'a, MappingItem<'a>>,
205}
206
207/// One `key: value` pair in a block or flow mapping.
208#[derive(Debug)]
209pub struct MappingItem<'a> {
210    pub span: Span,
211    /// `None` when the source has neither a `?` indicator nor key content (`: value`).
212    pub key: Option<MappingKey<'a>>,
213    /// `None` when the source has no `:` (`? key` alone, or a lone key in a flow mapping).
214    pub value: Option<MappingValue<'a>>,
215}
216
217impl<'a> MappingItem<'a> {
218    /// The key's content node, when both the key and its content exist.
219    pub fn key_content(&self) -> Option<&Node<'a>> {
220        self.key.as_ref().and_then(|key| key.content.as_deref())
221    }
222
223    /// The value's content node, when both the value and its content exist.
224    pub fn value_content(&self) -> Option<&Node<'a>> {
225        self.value.as_ref().and_then(|value| value.content.as_deref())
226    }
227}
228
229/// A mapping key. `span` starts at the `?` indicator when explicit.
230#[derive(Debug)]
231pub struct MappingKey<'a> {
232    pub span: Span,
233    /// `None` for an explicit `?` with no content.
234    pub content: Option<Box<'a, Node<'a>>>,
235    /// `true` when written with the explicit `?` indicator.
236    pub explicit: bool,
237}
238
239/// A mapping value. `span` starts at the `:` indicator.
240#[derive(Debug)]
241pub struct MappingValue<'a> {
242    pub span: Span,
243    /// `None` for `key:` with no value.
244    pub content: Option<Box<'a, Node<'a>>>,
245}
246
247/// A block sequence.
248#[derive(Debug)]
249pub struct Sequence<'a> {
250    pub span: Span,
251    pub children: Vec<'a, SequenceItem<'a>>,
252}
253
254/// One `- item` in a block sequence. `span` starts at the `-`.
255#[derive(Debug)]
256pub struct SequenceItem<'a> {
257    pub span: Span,
258    pub content: Option<Box<'a, Node<'a>>>,
259}
260
261/// `{ ... }`.
262#[derive(Debug)]
263pub struct FlowMapping<'a> {
264    pub span: Span,
265    pub children: Vec<'a, MappingItem<'a>>,
266}
267
268/// `[ ... ]`.
269#[derive(Debug)]
270pub struct FlowSequence<'a> {
271    pub span: Span,
272    pub children: Vec<'a, FlowSequenceEntry<'a>>,
273}
274
275/// An entry in a flow sequence: a plain node, or a `key: value` pair.
276/// Both are boxed so the enum stays two words.
277#[derive(Debug)]
278pub enum FlowSequenceEntry<'a> {
279    Item(Box<'a, Node<'a>>),
280    Pair(Box<'a, MappingItem<'a>>),
281}
282
283/// `*name`. `span` covers the `*` and the name.
284#[derive(Debug)]
285pub struct Alias {
286    pub span: Span,
287}