compiler/crates/react_compiler_ast/src/common.rs RUST 194 lines View on github.com → Search inside
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}

Code quality findings 5

Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
/// subtrees parse on demand via [`RawNode::parse_value`]; paths that do so
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("serde_json::Value always serializes to valid JSON"),
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("null is valid JSON"),
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
from_json_str_unbounded(self.0.get()).expect("RawNode holds valid JSON by construction")
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
/// reparse of [`RawNode`] text must go through this: the napi entrypoint

Security findings 1

Security Info: Directly deriving Deserialize without validation can expose applications to denial-of-service (e.g., large inputs) or other risks depending on the data types. Consider adding validation attributes (e.g., with `serde_valid`) or custom deserialization logic for untrusted input.
security deserialize-unvalidated
#[derive(Deserialize)]

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.