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}