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    /// The 0-based column of the `#` when only whitespace precedes it on its
28    /// line (an "own-line" comment); `None` when it trails other content.
29    /// Columns count characters; a tab counts as one.
30    pub own_line_column: Option<u32>,
31}
32
33/// `&name`. `span` covers the `&` and the name.
34#[derive(Clone, Copy, Debug)]
35pub struct Anchor {
36    pub span: Span,
37}
38
39/// A tag property: `!`, `!suffix`, `!handle!suffix`, `!!suffix` or `!<verbatim>`.
40#[derive(Clone, Copy, Debug)]
41pub struct Tag {
42    pub span: Span,
43}
44
45/// A node's properties (`&anchor` / `!tag`), in either source order.
46#[derive(Clone, Copy, Debug)]
47pub struct Props {
48    pub anchor: Option<Anchor>,
49    pub tag: Option<Tag>,
50}
51
52impl Props {
53    /// Start of the first property, if any.
54    pub fn start(&self) -> Option<u32> {
55        match (self.anchor, self.tag) {
56            (Some(a), Some(t)) => Some(a.span.start.min(t.span.start)),
57            (Some(a), None) => Some(a.span.start),
58            (None, Some(t)) => Some(t.span.start),
59            (None, None) => None,
60        }
61    }
62}
63
64/// The whole stream.
65#[derive(Debug)]
66pub struct Root<'a> {
67    pub span: Span,
68    pub children: Vec<'a, Document<'a>>,
69    /// Every comment in the stream, in source order. Comments are not
70    /// attached to nodes; consumers place them positionally via spans
71    /// (the comment-cursor pattern).
72    /// No comment lies inside a block scalar's content range (see the guarantee on [`BlockScalar`]).
73    pub comments: Vec<'a, Comment>,
74}
75
76#[derive(Debug)]
77#[expect(clippy::struct_field_names)] // mirrors yaml-unist-parser's field names
78pub struct Document<'a> {
79    pub span: Span,
80    pub head: DocumentHead<'a>,
81    pub body: DocumentBody<'a>,
82    /// Span of the `---` marker if present.
83    pub directives_end_marker: Option<Span>,
84    /// Span of the `...` marker if present.
85    pub document_end_marker: Option<Span>,
86}
87
88#[derive(Debug)]
89pub struct DocumentHead<'a> {
90    pub span: Span,
91    pub directives: Vec<'a, Directive<'a>>,
92}
93
94#[derive(Debug)]
95pub struct DocumentBody<'a> {
96    pub span: Span,
97    pub content: Option<Box<'a, Node<'a>>>,
98}
99
100/// `%NAME param param`. Uninterpreted; `%YAML`/`%TAG`/unknown are all accepted.
101#[derive(Debug)]
102pub struct Directive<'a> {
103    pub span: Span,
104    pub name: &'a str,
105    pub parameters: Vec<'a, &'a str>,
106}
107
108/// A YAML node: optional properties (anchor/tag) plus the content they apply to
109/// (the spec's `node ::= properties? content` production).
110///
111/// `span` covers the props through the content end, so every child span
112/// (props, content, nested nodes) nests inside it.
113///
114/// Node positions hold `Box<Node>` so container children
115/// ([`MappingItem`] / [`SequenceItem`]) stay small in their `Vec`s;
116/// the rarely-present `Props` would otherwise inflate every element.
117#[derive(Debug)]
118pub struct Node<'a> {
119    pub span: Span,
120    pub props: Props,
121    pub content: Content<'a>,
122}
123
124/// A node's content (mirrors yaml-unist-parser's `ContentNode`).
125#[derive(Debug)]
126pub enum Content<'a> {
127    Plain(Box<'a, Plain>),
128    QuoteSingle(Box<'a, QuoteSingle>),
129    QuoteDouble(Box<'a, QuoteDouble>),
130    BlockLiteral(Box<'a, BlockScalar>),
131    BlockFolded(Box<'a, BlockScalar>),
132    Mapping(Box<'a, Mapping<'a>>),
133    Sequence(Box<'a, Sequence<'a>>),
134    FlowMapping(Box<'a, FlowMapping<'a>>),
135    FlowSequence(Box<'a, FlowSequence<'a>>),
136    Alias(Box<'a, Alias>),
137}
138
139impl Content<'_> {
140    pub fn span(&self) -> Span {
141        match self {
142            Content::Plain(n) => n.span,
143            Content::QuoteSingle(n) => n.span,
144            Content::QuoteDouble(n) => n.span,
145            Content::BlockLiteral(n) | Content::BlockFolded(n) => n.span,
146            Content::Mapping(n) => n.span,
147            Content::Sequence(n) => n.span,
148            Content::FlowMapping(n) => n.span,
149            Content::FlowSequence(n) => n.span,
150            Content::Alias(n) => n.span,
151        }
152    }
153}
154
155/// A plain (unquoted) scalar. `span` covers the raw scalar text
156/// (trailing whitespace/comments excluded).
157#[derive(Debug)]
158pub struct Plain {
159    pub span: Span,
160}
161
162/// `'...'`. `span` includes the quotes.
163#[derive(Debug)]
164pub struct QuoteSingle {
165    pub span: Span,
166}
167
168/// `"..."`. `span` includes the quotes.
169#[derive(Debug)]
170pub struct QuoteDouble {
171    pub span: Span,
172}
173
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
175pub enum Chomping {
176    /// (default) single trailing newline
177    Clip,
178    /// `+` keep all trailing newlines
179    Keep,
180    /// `-` strip all trailing newlines
181    Strip,
182}
183
184/// `|` (literal) or `>` (folded) block scalar.
185///
186/// The variant is distinguished by the enclosing [`Content`] variant. `span`
187/// covers the indicator through the end of the content (including trailing
188/// line breaks consumed while scanning — they are the token's lexical extent,
189/// and under keep chomping part of the value).
190///
191/// GUARANTEE: [`Root::comments`] never holds a comment inside `content_start..span.end`.
192/// `#` line indented to the content is VALUE,
193/// and a lesser-indented one terminates the scalar first.
194/// The only comment within `span` is the header line's trailing one (`| # ...`),
195/// which always ends before `content_start`.
196/// Asserted at the end of the scanner's block-scalar scan (`debug_assert`),
197/// so every parse exercises it.
198#[derive(Debug)]
199pub struct BlockScalar {
200    pub span: Span,
201    pub chomping: Chomping,
202    /// Explicit indentation indicator digit, if any.
203    pub indent: Option<u32>,
204    /// Offset right after the header line's line break (= where content
205    /// scanning began). The content text is `content_start..content_end`.
206    pub content_start: u32,
207    /// Offset right after the last content character (before the trailing
208    /// break run). `content_end..span.end` holds only line breaks and
209    /// blank-line indentation.
210    pub content_end: u32,
211}
212
213/// A block mapping.
214#[derive(Debug)]
215pub struct Mapping<'a> {
216    pub span: Span,
217    pub children: Vec<'a, MappingItem<'a>>,
218}
219
220/// One `key: value` pair in a block or flow mapping.
221#[derive(Debug)]
222pub struct MappingItem<'a> {
223    pub span: Span,
224    /// `None` when the source has neither a `?` indicator nor key content (`: value`).
225    pub key: Option<MappingKey<'a>>,
226    /// `None` when the source has no `:` (`? key` alone, or a lone key in a flow mapping).
227    pub value: Option<MappingValue<'a>>,
228}
229
230impl<'a> MappingItem<'a> {
231    /// The key's content node, when both the key and its content exist.
232    pub fn key_content(&self) -> Option<&Node<'a>> {
233        self.key.as_ref().and_then(|key| key.content.as_deref())
234    }
235
236    /// The value's content node, when both the value and its content exist.
237    pub fn value_content(&self) -> Option<&Node<'a>> {
238        self.value.as_ref().and_then(|value| value.content.as_deref())
239    }
240}
241
242/// A mapping key. `span` starts at the `?` indicator when explicit.
243#[derive(Debug)]
244pub struct MappingKey<'a> {
245    pub span: Span,
246    /// `None` for an explicit `?` with no content.
247    pub content: Option<Box<'a, Node<'a>>>,
248    /// `true` when written with the explicit `?` indicator.
249    pub explicit: bool,
250}
251
252/// A mapping value. `span` starts at the `:` indicator.
253#[derive(Debug)]
254pub struct MappingValue<'a> {
255    pub span: Span,
256    /// `None` for `key:` with no value.
257    pub content: Option<Box<'a, Node<'a>>>,
258}
259
260/// A block sequence.
261#[derive(Debug)]
262pub struct Sequence<'a> {
263    pub span: Span,
264    pub children: Vec<'a, SequenceItem<'a>>,
265}
266
267/// One `- item` in a block sequence. `span` starts at the `-`.
268#[derive(Debug)]
269pub struct SequenceItem<'a> {
270    pub span: Span,
271    pub content: Option<Box<'a, Node<'a>>>,
272}
273
274/// `{ ... }`.
275#[derive(Debug)]
276pub struct FlowMapping<'a> {
277    pub span: Span,
278    pub children: Vec<'a, MappingItem<'a>>,
279}
280
281/// `[ ... ]`.
282#[derive(Debug)]
283pub struct FlowSequence<'a> {
284    pub span: Span,
285    pub children: Vec<'a, FlowSequenceEntry<'a>>,
286}
287
288/// An entry in a flow sequence: a plain node, or a `key: value` pair.
289/// Both are boxed so the enum stays two words.
290#[derive(Debug)]
291pub enum FlowSequenceEntry<'a> {
292    Item(Box<'a, Node<'a>>),
293    Pair(Box<'a, MappingItem<'a>>),
294}
295
296/// `*name`. `span` covers the `*` and the name.
297#[derive(Debug)]
298pub struct Alias {
299    pub span: Span,
300}