1use serde::Deserialize;2use serde::Serialize;34/// An AST subtree the compiler does not model with typed nodes (type5/// annotations, class bodies, parser extras). Wraps JSON text: serialization6/// is verbatim pass-through and deserialization streams the subtree into text7/// without retaining a `serde_json::Value` tree. Consumers that inspect these8/// subtrees parse on demand via [`RawNode::parse_value`]; paths that do so9/// repeatedly per traversal pay a parse each time, so cache the parsed Value10/// at the call site if it shows up in profiles.11///12/// Deserialize is hand-implemented with a transcode rather than capturing a13/// `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>);1819impl<'de> serde::Deserialize<'de> for RawNode {20 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>21 where22 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}3334impl 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 }4142 pub fn null() -> Self {43 RawNode(44 serde_json::value::RawValue::from_string("null".to_string())45 .expect("null is valid JSON"),46 )47 }4849 /// The raw JSON text of this subtree.50 pub fn get(&self) -> &str {51 self.0.get()52 }5354 /// Parse the subtree into a `serde_json::Value` for structural inspection.55 /// RawNode text is valid JSON by construction, so failure here means a56 /// 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 }6061 /// 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}7374/// Parse JSON text with serde_json's recursion limit disabled. Every internal75/// reparse of [`RawNode`] text must go through this: the napi entrypoint76/// deserializes arbitrarily deep ASTs with the limit disabled (on a 64MB77/// stack), and the tolerant statement path's reparses must not quietly78/// 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}8687/// Custom deserializer that distinguishes "field absent" from "field: null".88/// - JSON field absent → `None` (via `#[serde(default)]`)89/// - JSON field `null` → `Some(RawNode("null"))`90/// - JSON field with value → `Some(raw value)`91///92/// Use with `#[serde(default, skip_serializing_if = "Option::is_none", deserialize_with = "nullable_value")]`93pub fn nullable_value<'de, D>(deserializer: D) -> Result<Option<RawNode>, D::Error>94where95 D: serde::Deserializer<'de>,96{97 RawNode::deserialize(deserializer).map(Some)98}99100#[derive(Debug, Clone, Serialize, Deserialize)]101pub struct Position {102 pub line: u32,103 pub column: u32,104 #[serde(default, skip_serializing_if = "Option::is_none")]105 pub index: Option<u32>,106}107108#[derive(Debug, Clone, Serialize, Deserialize)]109pub struct SourceLocation {110 pub start: Position,111 pub end: Position,112 #[serde(default, skip_serializing_if = "Option::is_none")]113 pub filename: Option<String>,114 #[serde(115 default,116 skip_serializing_if = "Option::is_none",117 rename = "identifierName"118 )]119 pub identifier_name: Option<String>,120}121122#[derive(Debug, Clone, Serialize, Deserialize)]123#[serde(tag = "type")]124pub enum Comment {125 CommentBlock(CommentData),126 CommentLine(CommentData),127}128129#[derive(Debug, Clone, Serialize, Deserialize)]130pub struct CommentData {131 pub value: String,132 #[serde(default, skip_serializing_if = "Option::is_none")]133 pub start: Option<u32>,134 #[serde(default, skip_serializing_if = "Option::is_none")]135 pub end: Option<u32>,136 #[serde(default, skip_serializing_if = "Option::is_none")]137 pub loc: Option<SourceLocation>,138}139140#[derive(Debug, Clone, Default, Serialize, Deserialize)]141pub struct BaseNode {142 // NOTE: When creating AST nodes for code generation output, use143 // `BaseNode::typed("NodeTypeName")` instead of `BaseNode::default()`144 // to ensure the "type" field is emitted during serialization.145 /// The node type string (e.g. "BlockStatement").146 /// When deserialized through a `#[serde(tag = "type")]` enum, the enum147 /// consumes the "type" field so this defaults to None. When deserialized148 /// directly, this captures the "type" field for round-trip fidelity.149 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]150 pub node_type: Option<String>,151 #[serde(default, skip_serializing_if = "Option::is_none")]152 pub start: Option<u32>,153 #[serde(default, skip_serializing_if = "Option::is_none")]154 pub end: Option<u32>,155 #[serde(default, skip_serializing_if = "Option::is_none")]156 pub loc: Option<SourceLocation>,157 #[serde(default, skip_serializing_if = "Option::is_none")]158 pub range: Option<(u32, u32)>,159 #[serde(default, skip_serializing_if = "Option::is_none")]160 pub extra: Option<RawNode>,161 #[serde(162 default,163 skip_serializing_if = "Option::is_none",164 rename = "leadingComments"165 )]166 pub leading_comments: Option<Vec<Comment>>,167 #[serde(168 default,169 skip_serializing_if = "Option::is_none",170 rename = "innerComments"171 )]172 pub inner_comments: Option<Vec<Comment>>,173 #[serde(174 default,175 skip_serializing_if = "Option::is_none",176 rename = "trailingComments"177 )]178 pub trailing_comments: Option<Vec<Comment>>,179 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_nodeId")]180 pub node_id: Option<u32>,181}182183impl BaseNode {184 /// Create a BaseNode with the given type name.185 /// Use this when creating AST nodes for code generation to ensure the186 /// `"type"` field is present in serialized output.187 pub fn typed(type_name: &str) -> Self {188 Self {189 node_type: Some(type_name.to_string()),190 ..Default::default()191 }192 }193}