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())
59            .expect("RawNode holds valid JSON by construction")
60    }
61
62    /// The node's `"type"` field, without parsing the whole subtree into a Value.
63    pub fn type_name(&self) -> Option<String> {
64        #[derive(Deserialize)]
65        struct TypeProbe {
66            #[serde(rename = "type")]
67            type_name: Option<String>,
68        }
69        from_json_str_unbounded::<TypeProbe>(self.0.get())
70            .ok()
71            .and_then(|p| p.type_name)
72    }
73}
74
75/// Parse JSON text with serde_json's recursion limit disabled. Every internal
76/// reparse of [`RawNode`] text must go through this: the napi entrypoint
77/// deserializes arbitrarily deep ASTs with the limit disabled (on a 64MB
78/// stack), and the tolerant statement path's reparses must not quietly
79/// reintroduce the default limit.
80pub fn from_json_str_unbounded<'de, T: serde::Deserialize<'de>>(
81    s: &'de str,
82) -> serde_json::Result<T> {
83    let mut deserializer = serde_json::Deserializer::from_str(s);
84    deserializer.disable_recursion_limit();
85    T::deserialize(&mut deserializer)
86}
87
88/// Custom deserializer that distinguishes "field absent" from "field: null".
89/// - JSON field absent → `None` (via `#[serde(default)]`)
90/// - JSON field `null` → `Some(RawNode("null"))`
91/// - JSON field with value → `Some(raw value)`
92///
93/// Use with `#[serde(default, skip_serializing_if = "Option::is_none", deserialize_with = "nullable_value")]`
94pub fn nullable_value<'de, D>(deserializer: D) -> Result<Option<RawNode>, D::Error>
95where
96    D: serde::Deserializer<'de>,
97{
98    RawNode::deserialize(deserializer).map(Some)
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct Position {
103    pub line: u32,
104    pub column: u32,
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub index: Option<u32>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct SourceLocation {
111    pub start: Position,
112    pub end: Position,
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub filename: Option<String>,
115    #[serde(
116        default,
117        skip_serializing_if = "Option::is_none",
118        rename = "identifierName"
119    )]
120    pub identifier_name: Option<String>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124#[serde(tag = "type")]
125pub enum Comment {
126    CommentBlock(CommentData),
127    CommentLine(CommentData),
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct CommentData {
132    pub value: String,
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub start: Option<u32>,
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub end: Option<u32>,
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub loc: Option<SourceLocation>,
139}
140
141#[derive(Debug, Clone, Default, Serialize, Deserialize)]
142pub struct BaseNode {
143    // NOTE: When creating AST nodes for code generation output, use
144    // `BaseNode::typed("NodeTypeName")` instead of `BaseNode::default()`
145    // to ensure the "type" field is emitted during serialization.
146    /// The node type string (e.g. "BlockStatement").
147    /// When deserialized through a `#[serde(tag = "type")]` enum, the enum
148    /// consumes the "type" field so this defaults to None. When deserialized
149    /// directly, this captures the "type" field for round-trip fidelity.
150    #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
151    pub node_type: Option<String>,
152    #[serde(default, skip_serializing_if = "Option::is_none")]
153    pub start: Option<u32>,
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub end: Option<u32>,
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub loc: Option<SourceLocation>,
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub range: Option<(u32, u32)>,
160    #[serde(default, skip_serializing_if = "Option::is_none")]
161    pub extra: Option<RawNode>,
162    #[serde(
163        default,
164        skip_serializing_if = "Option::is_none",
165        rename = "leadingComments"
166    )]
167    pub leading_comments: Option<Vec<Comment>>,
168    #[serde(
169        default,
170        skip_serializing_if = "Option::is_none",
171        rename = "innerComments"
172    )]
173    pub inner_comments: Option<Vec<Comment>>,
174    #[serde(
175        default,
176        skip_serializing_if = "Option::is_none",
177        rename = "trailingComments"
178    )]
179    pub trailing_comments: Option<Vec<Comment>>,
180    #[serde(default, skip_serializing_if = "Option::is_none", rename = "_nodeId")]
181    pub node_id: Option<u32>,
182}
183
184impl BaseNode {
185    /// Create a BaseNode with the given type name.
186    /// Use this when creating AST nodes for code generation to ensure the
187    /// `"type"` field is present in serialized output.
188    pub fn typed(type_name: &str) -> Self {
189        Self {
190            node_type: Some(type_name.to_string()),
191            ..Default::default()
192        }
193    }
194}