Skip to main content

react_compiler_ast/
common.rs

1use serde::Deserialize;
2use serde::Serialize;
3
4/// An AST subtree the compiler does not model with typed nodes (type
5/// annotations, class bodies, parser extras). Wraps JSON text: serialization
6/// is verbatim pass-through and deserialization streams the subtree into text
7/// without retaining a `serde_json::Value` tree. Consumers that inspect these
8/// subtrees parse on demand via [`RawNode::parse_value`]; paths that do so
9/// repeatedly per traversal pay a parse each time, so cache the parsed Value
10/// at the call site if it shows up in profiles.
11///
12/// Deserialize is hand-implemented with a transcode rather than capturing a
13/// `RawValue` directly: most nodes sit under `#[serde(tag = "type")]` enums,
14/// whose content buffering breaks `RawValue`'s text-borrowing capture.
15#[derive(Debug, Clone, Serialize)]
16#[serde(transparent)]
17pub struct RawNode(pub Box<serde_json::value::RawValue>);
18
19impl<'de> serde::Deserialize<'de> for RawNode {
20    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
21    where
22        D: serde::Deserializer<'de>,
23    {
24        let mut buf = Vec::new();
25        let mut ser = serde_json::Serializer::new(&mut buf);
26        serde_transcode::transcode(deserializer, &mut ser).map_err(serde::de::Error::custom)?;
27        let text = String::from_utf8(buf).map_err(serde::de::Error::custom)?;
28        serde_json::value::RawValue::from_string(text)
29            .map(RawNode)
30            .map_err(serde::de::Error::custom)
31    }
32}
33
34impl RawNode {
35    pub fn from_value(value: &serde_json::Value) -> Self {
36        RawNode(
37            serde_json::value::RawValue::from_string(value.to_string())
38                .expect("serde_json::Value always serializes to valid JSON"),
39        )
40    }
41
42    pub fn null() -> Self {
43        RawNode(
44            serde_json::value::RawValue::from_string("null".to_string())
45                .expect("null is valid JSON"),
46        )
47    }
48
49    /// The raw JSON text of this subtree.
50    pub fn get(&self) -> &str {
51        self.0.get()
52    }
53
54    /// Parse the subtree into a `serde_json::Value` for structural inspection.
55    /// RawNode text is valid JSON by construction, so failure here means a
56    /// broken invariant, not bad input; fail loudly rather than degrade.
57    pub fn parse_value(&self) -> serde_json::Value {
58        from_json_str_unbounded(self.0.get()).expect("RawNode holds valid JSON by construction")
59    }
60
61    /// The node's `"type"` field, without parsing the whole subtree into a Value.
62    pub fn type_name(&self) -> Option<String> {
63        #[derive(Deserialize)]
64        struct TypeProbe {
65            #[serde(rename = "type")]
66            type_name: Option<String>,
67        }
68        from_json_str_unbounded::<TypeProbe>(self.0.get())
69            .ok()
70            .and_then(|p| p.type_name)
71    }
72}
73
74/// Parse JSON text with serde_json's recursion limit disabled. Every internal
75/// reparse of [`RawNode`] text must go through this: the napi entrypoint
76/// deserializes arbitrarily deep ASTs with the limit disabled (on a 64MB
77/// stack), and the tolerant statement path's reparses must not quietly
78/// reintroduce the default limit.
79pub fn from_json_str_unbounded<'de, T: serde::Deserialize<'de>>(
80    s: &'de str,
81) -> serde_json::Result<T> {
82    let mut deserializer = serde_json::Deserializer::from_str(s);
83    deserializer.disable_recursion_limit();
84    T::deserialize(&mut deserializer)
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct Position {
89    pub line: u32,
90    pub column: u32,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub index: Option<u32>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct SourceLocation {
97    pub start: Position,
98    pub end: Position,
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub filename: Option<String>,
101    #[serde(
102        default,
103        skip_serializing_if = "Option::is_none",
104        rename = "identifierName"
105    )]
106    pub identifier_name: Option<String>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110#[serde(tag = "type")]
111pub enum Comment {
112    CommentBlock(CommentData),
113    CommentLine(CommentData),
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct CommentData {
118    pub value: String,
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub start: Option<u32>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub end: Option<u32>,
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub loc: Option<SourceLocation>,
125}
126
127#[derive(Debug, Clone, Default, Serialize, Deserialize)]
128pub struct BaseNode {
129    // NOTE: When creating AST nodes for code generation output, use
130    // `BaseNode::typed("NodeTypeName")` instead of `BaseNode::default()`
131    // to ensure the "type" field is emitted during serialization.
132    /// The node type string (e.g. "BlockStatement").
133    /// When deserialized through a `#[serde(tag = "type")]` enum, the enum
134    /// consumes the "type" field so this defaults to None. When deserialized
135    /// directly, this captures the "type" field for round-trip fidelity.
136    #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
137    pub node_type: Option<String>,
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub start: Option<u32>,
140    #[serde(default, skip_serializing_if = "Option::is_none")]
141    pub end: Option<u32>,
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub loc: Option<SourceLocation>,
144    #[serde(default, skip_serializing_if = "Option::is_none")]
145    pub range: Option<(u32, u32)>,
146    #[serde(default, skip_serializing_if = "Option::is_none")]
147    pub extra: Option<RawNode>,
148    #[serde(
149        default,
150        skip_serializing_if = "Option::is_none",
151        rename = "leadingComments"
152    )]
153    pub leading_comments: Option<Vec<Comment>>,
154    #[serde(
155        default,
156        skip_serializing_if = "Option::is_none",
157        rename = "innerComments"
158    )]
159    pub inner_comments: Option<Vec<Comment>>,
160    #[serde(
161        default,
162        skip_serializing_if = "Option::is_none",
163        rename = "trailingComments"
164    )]
165    pub trailing_comments: Option<Vec<Comment>>,
166    #[serde(default, skip_serializing_if = "Option::is_none", rename = "_nodeId")]
167    pub node_id: Option<u32>,
168}
169
170impl BaseNode {
171    /// Create a BaseNode with the given type name.
172    /// Use this when creating AST nodes for code generation to ensure the
173    /// `"type"` field is present in serialized output.
174    pub fn typed(type_name: &str) -> Self {
175        Self {
176            node_type: Some(type_name.to_string()),
177            ..Default::default()
178        }
179    }
180}