Skip to main content

libgraphql_parser/ast/
ast_node.rs

1use crate::ByteSpan;
2use crate::SourceMap;
3use crate::SourceSpan;
4
5/// Append the source text for `span` to `sink` by slicing
6/// directly from `source` via byte offsets (zero-copy,
7/// lossless).
8pub(crate) fn append_span_source_slice(
9    span: ByteSpan,
10    sink: &mut String,
11    source: &str,
12) {
13    let start = span.start as usize;
14    let end = span.end as usize;
15    debug_assert!(
16        start <= end,
17        "append_span_source_slice: inverted span \
18         (start {start} > end {end})",
19    );
20    debug_assert!(
21        end <= source.len(),
22        "append_span_source_slice: span byte range \
23         {}..{} exceeds source length {}",
24        start,
25        end,
26        source.len(),
27    );
28    sink.push_str(&source[start..end]);
29}
30
31/// Trait implemented by all AST node types. Provides source
32/// reconstruction and span access methods.
33///
34/// All AST node types implement this trait via
35/// `#[inherent] impl AstNode`, giving each node both inherent
36/// methods (no trait import needed) and a trait bound for generic
37/// utilities (error formatters, linters, etc.).
38///
39/// # Source Reconstruction Modes
40///
41/// - **Source-slice mode (fast, lossless):** When `source` is
42///   `Some(s)`, [`append_source`](AstNode::append_source) slices
43///   `&s[span.start.byte_offset..span.end.byte_offset]`. This is
44///   the common path for string-based token sources. Zero
45///   allocation.
46///
47/// - **Synthetic-formatting mode (slower, lossy):** When `source`
48///   is `None`, [`append_source`](AstNode::append_source) walks the
49///   AST and emits keywords, names, values, and punctuation with
50///   standard spacing. The output is semantically equivalent but not
51///   formatting-identical.
52///
53/// # Span Access
54///
55/// Every AST node carries a [`ByteSpan`] recording its
56/// byte-offset range in the source text.
57/// [`byte_span()`](AstNode::byte_span) exposes this uniformly
58/// across all node types, and
59/// [`source_span()`](AstNode::source_span) resolves it to
60/// line/column coordinates on demand via a [`SourceMap`].
61pub trait AstNode {
62    /// Append this node's source representation to `sink`.
63    ///
64    /// When `source` is `Some(s)`, slices the original source text
65    /// directly via byte offsets (zero-copy, lossless). When
66    /// `source` is `None`, reconstructs from semantic data with
67    /// standard formatting (lossy but semantically equivalent).
68    fn append_source(
69        &self,
70        sink: &mut String,
71        source: Option<&str>,
72    );
73
74    /// Returns this node's byte-offset span within the source
75    /// text.
76    ///
77    /// The returned [`ByteSpan`] is a compact `[start, end)`
78    /// byte range that can be resolved to line/column positions
79    /// via [`source_span()`](AstNode::source_span) or
80    /// [`ByteSpan::resolve()`].
81    fn byte_span(&self) -> ByteSpan;
82
83    /// Resolves this node's position to line/column coordinates
84    /// using the given [`SourceMap`].
85    ///
86    /// Returns [`None`] if the byte offsets cannot be resolved
87    /// (e.g. the node was synthetically constructed without
88    /// valid span data).
89    fn source_span(
90        &self,
91        source_map: &SourceMap,
92    ) -> Option<SourceSpan> {
93        self.byte_span().resolve(source_map)
94    }
95
96    /// Return this node as a source string.
97    ///
98    /// Convenience wrapper around
99    /// [`append_source`](AstNode::append_source).
100    fn to_source(
101        &self,
102        source: Option<&str>,
103    ) -> String {
104        let mut s = String::new();
105        self.append_source(&mut s, source);
106        s
107    }
108}