compiler/crates/react_compiler_ast/src/statements.rs RUST 754 lines View on github.com → Search inside
1use serde::Deserialize;2use serde::Deserializer;3use serde::Serialize;4use serde::Serializer;5use serde::de::Error as _;67use crate::common::BaseNode;8use crate::common::RawNode;9use crate::expressions::Expression;10use crate::expressions::Identifier;11use crate::patterns::PatternLike;1213fn is_false(v: &bool) -> bool {14    !v15}1617#[derive(Debug, Clone, Serialize)]18#[serde(tag = "type")]19pub enum Statement {20    // Statements21    BlockStatement(BlockStatement),22    ReturnStatement(ReturnStatement),23    IfStatement(IfStatement),24    ForStatement(ForStatement),25    WhileStatement(WhileStatement),26    DoWhileStatement(DoWhileStatement),27    ForInStatement(ForInStatement),28    ForOfStatement(ForOfStatement),29    SwitchStatement(SwitchStatement),30    ThrowStatement(ThrowStatement),31    TryStatement(TryStatement),32    BreakStatement(BreakStatement),33    ContinueStatement(ContinueStatement),34    LabeledStatement(LabeledStatement),35    ExpressionStatement(ExpressionStatement),36    EmptyStatement(EmptyStatement),37    DebuggerStatement(DebuggerStatement),38    WithStatement(WithStatement),39    // Declarations are also statements40    VariableDeclaration(VariableDeclaration),41    FunctionDeclaration(FunctionDeclaration),42    ClassDeclaration(ClassDeclaration),43    // Import/export declarations44    ImportDeclaration(crate::declarations::ImportDeclaration),45    ExportNamedDeclaration(crate::declarations::ExportNamedDeclaration),46    ExportDefaultDeclaration(crate::declarations::ExportDefaultDeclaration),47    ExportAllDeclaration(crate::declarations::ExportAllDeclaration),48    // TypeScript declarations49    TSTypeAliasDeclaration(crate::declarations::TSTypeAliasDeclaration),50    TSInterfaceDeclaration(crate::declarations::TSInterfaceDeclaration),51    TSEnumDeclaration(crate::declarations::TSEnumDeclaration),52    TSModuleDeclaration(crate::declarations::TSModuleDeclaration),53    TSDeclareFunction(crate::declarations::TSDeclareFunction),54    // Flow declarations55    TypeAlias(crate::declarations::TypeAlias),56    OpaqueType(crate::declarations::OpaqueType),57    InterfaceDeclaration(crate::declarations::InterfaceDeclaration),58    DeclareVariable(crate::declarations::DeclareVariable),59    DeclareFunction(crate::declarations::DeclareFunction),60    DeclareClass(crate::declarations::DeclareClass),61    DeclareModule(crate::declarations::DeclareModule),62    DeclareModuleExports(crate::declarations::DeclareModuleExports),63    DeclareExportDeclaration(crate::declarations::DeclareExportDeclaration),64    DeclareExportAllDeclaration(crate::declarations::DeclareExportAllDeclaration),65    DeclareInterface(crate::declarations::DeclareInterface),66    DeclareTypeAlias(crate::declarations::DeclareTypeAlias),67    DeclareOpaqueType(crate::declarations::DeclareOpaqueType),68    EnumDeclaration(crate::declarations::EnumDeclaration),69    /// Catch-all for statement `type`s the typed AST does not model, e.g. the70    /// TypeScript module-interop statements `import x = require(...)`,71    /// `export = x`, and `export as namespace X`. Carries the complete raw72    /// Babel node so the Babel path can preserve unmodeled top-level73    /// statements verbatim instead of failing the whole file.74    ///75    /// Deserialization dispatches through [`KnownStatement`]: a modeled `type`76    /// whose body is malformed errors with the typed variant's precise message77    /// rather than degrading to `Unknown`. Adding a variant to this enum78    /// requires adding it to the `known_statements!` list below, which is the79    /// single source for the dispatch enum, its `From` mapping, and80    /// [`KNOWN_STATEMENT_TYPES`]. A variant added here but not there degrades81    /// to `Unknown` silently; that is the one drift case structure cannot82    /// catch.83    #[serde(untagged)]84    Unknown(UnknownStatement),85}8687// NOTE: `Deserialize` for `Statement` is hand-written below; the88// `#[serde(tag = "type")]` and `#[serde(untagged)]` attributes on the enum89// configure only the derived `Serialize`.9091#[derive(Debug, Clone)]92pub struct UnknownStatement {93    raw: RawNode,94    base: BaseNode,95}9697impl UnknownStatement {98    pub fn from_raw(raw: RawNode) -> Result<Self, String> {99        match raw.type_name() {100            Some(_) => {101                // Parsing into BaseNode reads only the fields BaseNode declares,102                // not the whole (arbitrarily large) unknown subtree.103                let base = crate::common::from_json_str_unbounded::<BaseNode>(raw.get())104                    .map_err(|err| format!("failed to read unknown statement base: {err}"))?;105                Ok(Self { raw, base })106            }107            None => Err("unknown statement is missing a string `type` field".to_string()),108        }109    }110111    /// The node's `type` discriminant, read from the captured [`BaseNode`].112    /// Falls back to `"Unknown"` rather than panicking if the raw node was113    /// mutated out from under it.114    pub fn node_type(&self) -> &str {115        self.base.node_type.as_deref().unwrap_or("Unknown")116    }117118    pub fn raw(&self) -> &RawNode {119        &self.raw120    }121122    /// Mutate the raw node, then refresh the cached [`BaseNode`] so `base()`123    /// and `node_type()` cannot drift from `raw`. Mutations that remove the124    /// string `type` field are rejected and rolled back.125    pub fn with_raw_mut<R>(&mut self, f: impl FnOnce(&mut RawNode) -> R) -> Result<R, String> {126        let saved = self.raw.clone();127        let result = f(&mut self.raw);128        if self.raw.type_name().is_none() {129            self.raw = saved;130            return Err("unknown statement mutation removed the string `type` field".to_string());131        }132        match crate::common::from_json_str_unbounded::<BaseNode>(self.raw.get()) {133            Ok(base) => {134                self.base = base;135                Ok(result)136            }137            Err(err) => {138                self.raw = saved;139                Err(format!("failed to refresh unknown statement base: {err}"))140            }141        }142    }143144    pub fn base(&self) -> &BaseNode {145        &self.base146    }147}148149impl Serialize for UnknownStatement {150    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>151    where152        S: Serializer,153    {154        self.raw.serialize(serializer)155    }156}157158impl<'de> Deserialize<'de> for UnknownStatement {159    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>160    where161        D: Deserializer<'de>,162    {163        let raw = RawNode::deserialize(deserializer)?;164        Self::from_raw(raw).map_err(D::Error::custom)165    }166}167168impl<'de> Deserialize<'de> for Statement {169    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>170    where171        D: Deserializer<'de>,172    {173        let raw = RawNode::deserialize(deserializer)?;174        let node_type = raw175            .type_name()176            .ok_or_else(|| D::Error::custom("statement is missing a string `type` field"))?;177178        if is_known_statement_type(&node_type) {179            let known: KnownStatement =180                crate::common::from_json_str_unbounded(raw.get()).map_err(D::Error::custom)?;181            Ok(known.into())182        } else {183            UnknownStatement::from_raw(raw)184                .map(Statement::Unknown)185                .map_err(D::Error::custom)186        }187    }188}189190/// Single source of truth for the statement `type` tags [`Statement`] models.191/// Generates the [`KnownStatement`] dispatch enum, its `From` mapping, and192/// [`KNOWN_STATEMENT_TYPES`] from one list, so the three cannot drift from193/// each other. A variant added to [`Statement`] but not listed here still194/// degrades to [`Statement::Unknown`] silently; that residual gap is195/// documented on the variant.196macro_rules! known_statements {197    ($($variant:ident => $ty:ty),+ $(,)?) => {198        const KNOWN_STATEMENT_TYPES: &[&str] = &[$(stringify!($variant)),+];199200        /// Whether `node_type` is a statement `type` tag modeled by201        /// [`Statement`], i.e. one that deserializes into a typed variant202        /// rather than the [`Statement::Unknown`] catch-all. Callers that203        /// need to discriminate statements from other node kinds must use204        /// this instead of attempting a `Statement` deserialization: with205        /// the tolerant catch-all, that attempt succeeds for any object206        /// carrying a string `type` tag.207        pub fn is_known_statement_type(node_type: &str) -> bool {208            KNOWN_STATEMENT_TYPES.contains(&node_type)209        }210211        #[derive(Debug, Deserialize)]212        #[serde(tag = "type")]213        enum KnownStatement {214            $($variant($ty),)+215        }216217        impl From<KnownStatement> for Statement {218            fn from(value: KnownStatement) -> Self {219                match value {220                    $(KnownStatement::$variant(s) => Statement::$variant(s),)+221                }222            }223        }224    };225}226227known_statements! {228    BlockStatement => BlockStatement,229    ReturnStatement => ReturnStatement,230    IfStatement => IfStatement,231    ForStatement => ForStatement,232    WhileStatement => WhileStatement,233    DoWhileStatement => DoWhileStatement,234    ForInStatement => ForInStatement,235    ForOfStatement => ForOfStatement,236    SwitchStatement => SwitchStatement,237    ThrowStatement => ThrowStatement,238    TryStatement => TryStatement,239    BreakStatement => BreakStatement,240    ContinueStatement => ContinueStatement,241    LabeledStatement => LabeledStatement,242    ExpressionStatement => ExpressionStatement,243    EmptyStatement => EmptyStatement,244    DebuggerStatement => DebuggerStatement,245    WithStatement => WithStatement,246    VariableDeclaration => VariableDeclaration,247    FunctionDeclaration => FunctionDeclaration,248    ClassDeclaration => ClassDeclaration,249    ImportDeclaration => crate::declarations::ImportDeclaration,250    ExportNamedDeclaration => crate::declarations::ExportNamedDeclaration,251    ExportDefaultDeclaration => crate::declarations::ExportDefaultDeclaration,252    ExportAllDeclaration => crate::declarations::ExportAllDeclaration,253    TSTypeAliasDeclaration => crate::declarations::TSTypeAliasDeclaration,254    TSInterfaceDeclaration => crate::declarations::TSInterfaceDeclaration,255    TSEnumDeclaration => crate::declarations::TSEnumDeclaration,256    TSModuleDeclaration => crate::declarations::TSModuleDeclaration,257    TSDeclareFunction => crate::declarations::TSDeclareFunction,258    TypeAlias => crate::declarations::TypeAlias,259    OpaqueType => crate::declarations::OpaqueType,260    InterfaceDeclaration => crate::declarations::InterfaceDeclaration,261    DeclareVariable => crate::declarations::DeclareVariable,262    DeclareFunction => crate::declarations::DeclareFunction,263    DeclareClass => crate::declarations::DeclareClass,264    DeclareModule => crate::declarations::DeclareModule,265    DeclareModuleExports => crate::declarations::DeclareModuleExports,266    DeclareExportDeclaration => crate::declarations::DeclareExportDeclaration,267    DeclareExportAllDeclaration => crate::declarations::DeclareExportAllDeclaration,268    DeclareInterface => crate::declarations::DeclareInterface,269    DeclareTypeAlias => crate::declarations::DeclareTypeAlias,270    DeclareOpaqueType => crate::declarations::DeclareOpaqueType,271    EnumDeclaration => crate::declarations::EnumDeclaration,272}273274#[derive(Debug, Clone, Serialize, Deserialize)]275pub struct BlockStatement {276    #[serde(flatten)]277    pub base: BaseNode,278    pub body: Vec<Statement>,279    #[serde(default)]280    pub directives: Vec<Directive>,281}282283#[derive(Debug, Clone, Serialize, Deserialize)]284pub struct Directive {285    #[serde(flatten)]286    pub base: BaseNode,287    pub value: DirectiveLiteral,288}289290#[derive(Debug, Clone, Serialize, Deserialize)]291pub struct DirectiveLiteral {292    #[serde(flatten)]293    pub base: BaseNode,294    pub value: String,295}296297#[derive(Debug, Clone, Serialize, Deserialize)]298pub struct ReturnStatement {299    #[serde(flatten)]300    pub base: BaseNode,301    pub argument: Option<Box<Expression>>,302}303304#[derive(Debug, Clone, Serialize, Deserialize)]305pub struct ExpressionStatement {306    #[serde(flatten)]307    pub base: BaseNode,308    pub expression: Box<Expression>,309}310311#[derive(Debug, Clone, Serialize, Deserialize)]312pub struct IfStatement {313    #[serde(flatten)]314    pub base: BaseNode,315    pub test: Box<Expression>,316    pub consequent: Box<Statement>,317    pub alternate: Option<Box<Statement>>,318}319320#[derive(Debug, Clone, Serialize, Deserialize)]321pub struct ForStatement {322    #[serde(flatten)]323    pub base: BaseNode,324    pub init: Option<Box<ForInit>>,325    pub test: Option<Box<Expression>>,326    pub update: Option<Box<Expression>>,327    pub body: Box<Statement>,328}329330#[derive(Debug, Clone, Serialize, Deserialize)]331#[serde(tag = "type")]332pub enum ForInit {333    VariableDeclaration(VariableDeclaration),334    #[serde(untagged)]335    Expression(Box<Expression>),336}337338#[derive(Debug, Clone, Serialize, Deserialize)]339pub struct WhileStatement {340    #[serde(flatten)]341    pub base: BaseNode,342    pub test: Box<Expression>,343    pub body: Box<Statement>,344}345346#[derive(Debug, Clone, Serialize, Deserialize)]347pub struct DoWhileStatement {348    #[serde(flatten)]349    pub base: BaseNode,350    pub test: Box<Expression>,351    pub body: Box<Statement>,352}353354#[derive(Debug, Clone, Serialize, Deserialize)]355pub struct ForInStatement {356    #[serde(flatten)]357    pub base: BaseNode,358    pub left: Box<ForInOfLeft>,359    pub right: Box<Expression>,360    pub body: Box<Statement>,361}362363#[derive(Debug, Clone, Serialize, Deserialize)]364pub struct ForOfStatement {365    #[serde(flatten)]366    pub base: BaseNode,367    pub left: Box<ForInOfLeft>,368    pub right: Box<Expression>,369    pub body: Box<Statement>,370    #[serde(default, rename = "await")]371    pub is_await: bool,372}373374#[derive(Debug, Clone, Serialize, Deserialize)]375#[serde(tag = "type")]376pub enum ForInOfLeft {377    VariableDeclaration(VariableDeclaration),378    #[serde(untagged)]379    Pattern(Box<PatternLike>),380}381382#[derive(Debug, Clone, Serialize, Deserialize)]383pub struct SwitchStatement {384    #[serde(flatten)]385    pub base: BaseNode,386    pub discriminant: Box<Expression>,387    pub cases: Vec<SwitchCase>,388}389390#[derive(Debug, Clone, Serialize, Deserialize)]391pub struct SwitchCase {392    #[serde(flatten)]393    pub base: BaseNode,394    pub test: Option<Box<Expression>>,395    pub consequent: Vec<Statement>,396}397398#[derive(Debug, Clone, Serialize, Deserialize)]399pub struct ThrowStatement {400    #[serde(flatten)]401    pub base: BaseNode,402    pub argument: Box<Expression>,403}404405#[derive(Debug, Clone, Serialize, Deserialize)]406pub struct TryStatement {407    #[serde(flatten)]408    pub base: BaseNode,409    pub block: BlockStatement,410    pub handler: Option<CatchClause>,411    pub finalizer: Option<BlockStatement>,412}413414#[derive(Debug, Clone, Serialize, Deserialize)]415pub struct CatchClause {416    #[serde(flatten)]417    pub base: BaseNode,418    pub param: Option<PatternLike>,419    pub body: BlockStatement,420}421422#[derive(Debug, Clone, Serialize, Deserialize)]423pub struct BreakStatement {424    #[serde(flatten)]425    pub base: BaseNode,426    pub label: Option<Identifier>,427}428429#[derive(Debug, Clone, Serialize, Deserialize)]430pub struct ContinueStatement {431    #[serde(flatten)]432    pub base: BaseNode,433    pub label: Option<Identifier>,434}435436#[derive(Debug, Clone, Serialize, Deserialize)]437pub struct LabeledStatement {438    #[serde(flatten)]439    pub base: BaseNode,440    pub label: Identifier,441    pub body: Box<Statement>,442}443444#[derive(Debug, Clone, Serialize, Deserialize)]445pub struct EmptyStatement {446    #[serde(flatten)]447    pub base: BaseNode,448}449450#[derive(Debug, Clone, Serialize, Deserialize)]451pub struct DebuggerStatement {452    #[serde(flatten)]453    pub base: BaseNode,454}455456#[derive(Debug, Clone, Serialize, Deserialize)]457pub struct WithStatement {458    #[serde(flatten)]459    pub base: BaseNode,460    pub object: Box<Expression>,461    pub body: Box<Statement>,462}463464#[derive(Debug, Clone, Serialize, Deserialize)]465pub struct VariableDeclaration {466    #[serde(flatten)]467    pub base: BaseNode,468    pub declarations: Vec<VariableDeclarator>,469    pub kind: VariableDeclarationKind,470    #[serde(default, skip_serializing_if = "Option::is_none")]471    pub declare: Option<bool>,472}473474#[derive(Debug, Clone, Serialize, Deserialize)]475#[serde(rename_all = "lowercase")]476pub enum VariableDeclarationKind {477    Var,478    Let,479    Const,480    Using,481}482483#[derive(Debug, Clone, Serialize, Deserialize)]484pub struct VariableDeclarator {485    #[serde(flatten)]486    pub base: BaseNode,487    pub id: PatternLike,488    pub init: Option<Box<Expression>>,489    #[serde(default, skip_serializing_if = "Option::is_none")]490    pub definite: Option<bool>,491}492493#[derive(Debug, Clone, Serialize, Deserialize)]494pub struct FunctionDeclaration {495    #[serde(flatten)]496    pub base: BaseNode,497    pub id: Option<Identifier>,498    pub params: Vec<PatternLike>,499    pub body: BlockStatement,500    #[serde(default)]501    pub generator: bool,502    #[serde(default, rename = "async")]503    pub is_async: bool,504    #[serde(default, skip_serializing_if = "Option::is_none")]505    pub declare: Option<bool>,506    #[serde(507        default,508        skip_serializing_if = "Option::is_none",509        rename = "returnType"510    )]511    pub return_type: Option<RawNode>,512    #[serde(513        default,514        skip_serializing_if = "Option::is_none",515        rename = "typeParameters"516    )]517    pub type_parameters: Option<RawNode>,518    #[serde(519        default,520        skip_serializing_if = "Option::is_none",521        rename = "predicate",522        deserialize_with = "crate::common::nullable_value"523    )]524    pub predicate: Option<RawNode>,525    /// Set by the Hermes parser for Flow `component Foo(...) { ... }` syntax526    #[serde(527        default,528        skip_serializing_if = "is_false",529        rename = "__componentDeclaration"530    )]531    pub component_declaration: bool,532    /// Set by the Hermes parser for Flow `hook useFoo(...) { ... }` syntax533    #[serde(534        default,535        skip_serializing_if = "is_false",536        rename = "__hookDeclaration"537    )]538    pub hook_declaration: bool,539}540541#[derive(Debug, Clone, Serialize, Deserialize)]542pub struct ClassDeclaration {543    #[serde(flatten)]544    pub base: BaseNode,545    pub id: Option<Identifier>,546    #[serde(rename = "superClass")]547    pub super_class: Option<Box<Expression>>,548    pub body: crate::expressions::ClassBody,549    #[serde(default, skip_serializing_if = "Option::is_none")]550    pub decorators: Option<Vec<RawNode>>,551    #[serde(default, skip_serializing_if = "Option::is_none", rename = "abstract")]552    pub is_abstract: Option<bool>,553    #[serde(default, skip_serializing_if = "Option::is_none")]554    pub declare: Option<bool>,555    #[serde(556        default,557        skip_serializing_if = "Option::is_none",558        rename = "implements"559    )]560    pub implements: Option<Vec<RawNode>>,561    #[serde(562        default,563        skip_serializing_if = "Option::is_none",564        rename = "superTypeParameters"565    )]566    pub super_type_parameters: Option<RawNode>,567    #[serde(568        default,569        skip_serializing_if = "Option::is_none",570        rename = "typeParameters"571    )]572    pub type_parameters: Option<RawNode>,573    #[serde(default, skip_serializing_if = "Option::is_none")]574    pub mixins: Option<Vec<RawNode>>,575}576577#[cfg(test)]578mod tests {579    use serde_json::json;580581    use super::Statement;582    use crate::common::RawNode;583584    #[test]585    fn unknown_statement_round_trips_at_program_level() {586        let input = json!({587            "type": "File",588            "comments": [],589            "errors": [],590            "program": {591                "type": "Program",592                "sourceType": "module",593                "interpreter": null,594                "body": [595                    {596                        "type": "TSImportEqualsDeclaration",597                        "start": 0,598                        "end": 39,599                        "importKind": "value",600                        "isExport": false,601                        "id": { "type": "Identifier", "name": "lib" },602                        "moduleReference": {603                            "type": "TSExternalModuleReference",604                            "expression": { "type": "StringLiteral", "value": "shared-runtime" }605                        }606                    }607                ],608                "directives": []609            }610        });611612        let file: crate::File = serde_json::from_value(input.clone()).unwrap();613614        match &file.program.body[0] {615            Statement::Unknown(unknown) => {616                assert_eq!(unknown.node_type(), "TSImportEqualsDeclaration");617            }618            other => panic!("expected Unknown, got {other:?}"),619        }620        assert_eq!(serde_json::to_value(&file).unwrap(), input);621    }622623    #[test]624    fn unknown_statement_round_trips_inside_function_block() {625        let input = json!({626            "type": "FunctionDeclaration",627            "id": null,628            "generator": false,629            "async": false,630            "params": [],631            "body": {632                "type": "BlockStatement",633                "body": [634                    {635                        "type": "TSExportAssignment",636                        "expression": { "type": "Identifier", "name": "x" }637                    }638                ],639                "directives": []640            }641        });642643        let stmt: Statement = serde_json::from_value(input.clone()).unwrap();644        let Statement::FunctionDeclaration(function) = &stmt else {645            panic!("expected function declaration, got {stmt:?}");646        };647        assert!(matches!(function.body.body[0], Statement::Unknown(_)));648        assert_eq!(serde_json::to_value(&stmt).unwrap(), input);649    }650651    /// The public discrimination helper mirrors the deserializer's dispatch:652    /// exactly the macro-listed statement tags are "known".653    #[test]654    fn is_known_statement_type_matches_macro_list() {655        assert!(super::is_known_statement_type("IfStatement"));656        assert!(super::is_known_statement_type("VariableDeclaration"));657        assert!(!super::is_known_statement_type("CallExpression"));658        assert!(!super::is_known_statement_type("TSImportEqualsDeclaration"));659    }660661    #[test]662    fn known_statement_type_uses_typed_variant() {663        let stmt: Statement = serde_json::from_value(json!({664            "type": "EmptyStatement"665        }))666        .unwrap();667668        assert!(matches!(stmt, Statement::EmptyStatement(_)));669    }670671    #[test]672    fn malformed_known_statement_type_errors() {673        let err = serde_json::from_value::<Statement>(json!({674            "type": "IfStatement",675            "consequent": {676                "type": "EmptyStatement"677            }678        }))679        .unwrap_err();680681        assert!(682            err.to_string().contains("missing field `test`"),683            "unexpected error: {err}"684        );685    }686687    #[test]688    fn statement_without_type_field_errors() {689        let err = serde_json::from_value::<Statement>(json!({690            "start": 0,691            "end": 1692        }))693        .unwrap_err();694695        assert!(696            err.to_string().contains("`type`"),697            "unexpected error: {err}"698        );699    }700701    #[test]702    fn non_object_statement_errors() {703        let err = serde_json::from_value::<Statement>(json!([1, 2])).unwrap_err();704        assert!(705            err.to_string().contains("`type`"),706            "unexpected error: {err}"707        );708    }709710    #[test]711    fn non_string_type_field_errors() {712        let err = serde_json::from_value::<Statement>(json!({ "type": 7 })).unwrap_err();713        assert!(714            err.to_string().contains("`type`"),715            "unexpected error: {err}"716        );717    }718719    /// Mutating the raw node through the scoped mutator refreshes the cached720    /// base, and mutations that strip `type` are rejected.721    #[test]722    fn with_raw_mut_refreshes_base_and_guards_type() {723        let raw = json!({724            "type": "TSExportAssignment",725            "start": 5,726            "expression": { "type": "Identifier", "name": "x" }727        });728        let Statement::Unknown(mut unknown) = serde_json::from_value(raw).unwrap() else {729            panic!("expected Unknown");730        };731732        unknown733            .with_raw_mut(|v| {734                let mut parsed = v.parse_value();735                parsed["start"] = json!(9);736                parsed["expression"]["name"] = json!("y");737                *v = RawNode::from_value(&parsed);738            })739            .unwrap();740        assert_eq!(unknown.base().start, Some(9));741        assert_eq!(742            unknown.raw().parse_value()["expression"]["name"],743            json!("y")744        );745746        let err = unknown.with_raw_mut(|v| {747            let mut parsed = v.parse_value();748            parsed.as_object_mut().unwrap().remove("type");749            *v = RawNode::from_value(&parsed);750        });751        assert!(err.is_err(), "type removal must be rejected");752    }753}

Findings

✓ No findings reported for this file.

Get this view in your editor

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