compiler/crates/react_compiler_ast/src/statements.rs RUST 781 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    #[serde(rename = "await using")]482    AwaitUsing,483}484485#[derive(Debug, Clone, Serialize, Deserialize)]486pub struct VariableDeclarator {487    #[serde(flatten)]488    pub base: BaseNode,489    pub id: PatternLike,490    pub init: Option<Box<Expression>>,491    #[serde(default, skip_serializing_if = "Option::is_none")]492    pub definite: Option<bool>,493}494495#[derive(Debug, Clone, Serialize, Deserialize)]496pub struct FunctionDeclaration {497    #[serde(flatten)]498    pub base: BaseNode,499    pub id: Option<Identifier>,500    pub params: Vec<PatternLike>,501    pub body: BlockStatement,502    #[serde(default)]503    pub generator: bool,504    #[serde(default, rename = "async")]505    pub is_async: bool,506    #[serde(default, skip_serializing_if = "Option::is_none")]507    pub declare: Option<bool>,508    #[serde(509        default,510        skip_serializing_if = "Option::is_none",511        rename = "returnType"512    )]513    pub return_type: Option<RawNode>,514    #[serde(515        default,516        skip_serializing_if = "Option::is_none",517        rename = "typeParameters"518    )]519    pub type_parameters: Option<RawNode>,520    #[serde(521        default,522        skip_serializing_if = "Option::is_none",523        rename = "predicate",524        deserialize_with = "crate::common::nullable_value"525    )]526    pub predicate: Option<RawNode>,527    /// Set by the Hermes parser for Flow `component Foo(...) { ... }` syntax528    #[serde(529        default,530        skip_serializing_if = "is_false",531        rename = "__componentDeclaration"532    )]533    pub component_declaration: bool,534    /// Set by the Hermes parser for Flow `hook useFoo(...) { ... }` syntax535    #[serde(536        default,537        skip_serializing_if = "is_false",538        rename = "__hookDeclaration"539    )]540    pub hook_declaration: bool,541}542543#[derive(Debug, Clone, Serialize, Deserialize)]544pub struct ClassDeclaration {545    #[serde(flatten)]546    pub base: BaseNode,547    pub id: Option<Identifier>,548    #[serde(rename = "superClass")]549    pub super_class: Option<Box<Expression>>,550    pub body: crate::expressions::ClassBody,551    #[serde(default, skip_serializing_if = "Option::is_none")]552    pub decorators: Option<Vec<RawNode>>,553    #[serde(default, skip_serializing_if = "Option::is_none", rename = "abstract")]554    pub is_abstract: Option<bool>,555    #[serde(default, skip_serializing_if = "Option::is_none")]556    pub declare: Option<bool>,557    #[serde(558        default,559        skip_serializing_if = "Option::is_none",560        rename = "implements"561    )]562    pub implements: Option<Vec<RawNode>>,563    #[serde(564        default,565        skip_serializing_if = "Option::is_none",566        rename = "superTypeParameters"567    )]568    pub super_type_parameters: Option<RawNode>,569    #[serde(570        default,571        skip_serializing_if = "Option::is_none",572        rename = "typeParameters"573    )]574    pub type_parameters: Option<RawNode>,575    #[serde(default, skip_serializing_if = "Option::is_none")]576    pub mixins: Option<Vec<RawNode>>,577}578579#[cfg(test)]580mod tests {581    use serde_json::json;582583    use super::Statement;584    use crate::common::RawNode;585586    #[test]587    fn unknown_statement_round_trips_at_program_level() {588        let input = json!({589            "type": "File",590            "comments": [],591            "errors": [],592            "program": {593                "type": "Program",594                "sourceType": "module",595                "interpreter": null,596                "body": [597                    {598                        "type": "TSImportEqualsDeclaration",599                        "start": 0,600                        "end": 39,601                        "importKind": "value",602                        "isExport": false,603                        "id": { "type": "Identifier", "name": "lib" },604                        "moduleReference": {605                            "type": "TSExternalModuleReference",606                            "expression": { "type": "StringLiteral", "value": "shared-runtime" }607                        }608                    }609                ],610                "directives": []611            }612        });613614        let file: crate::File = serde_json::from_value(input.clone()).unwrap();615616        match &file.program.body[0] {617            Statement::Unknown(unknown) => {618                assert_eq!(unknown.node_type(), "TSImportEqualsDeclaration");619            }620            other => panic!("expected Unknown, got {other:?}"),621        }622        assert_eq!(serde_json::to_value(&file).unwrap(), input);623    }624625    #[test]626    fn unknown_statement_round_trips_inside_function_block() {627        let input = json!({628            "type": "FunctionDeclaration",629            "id": null,630            "generator": false,631            "async": false,632            "params": [],633            "body": {634                "type": "BlockStatement",635                "body": [636                    {637                        "type": "TSExportAssignment",638                        "expression": { "type": "Identifier", "name": "x" }639                    }640                ],641                "directives": []642            }643        });644645        let stmt: Statement = serde_json::from_value(input.clone()).unwrap();646        let Statement::FunctionDeclaration(function) = &stmt else {647            panic!("expected function declaration, got {stmt:?}");648        };649        assert!(matches!(function.body.body[0], Statement::Unknown(_)));650        assert_eq!(serde_json::to_value(&stmt).unwrap(), input);651    }652653    /// The public discrimination helper mirrors the deserializer's dispatch:654    /// exactly the macro-listed statement tags are "known".655    #[test]656    fn is_known_statement_type_matches_macro_list() {657        assert!(super::is_known_statement_type("IfStatement"));658        assert!(super::is_known_statement_type("VariableDeclaration"));659        assert!(!super::is_known_statement_type("CallExpression"));660        assert!(!super::is_known_statement_type("TSImportEqualsDeclaration"));661    }662663    #[test]664    fn known_statement_type_uses_typed_variant() {665        let stmt: Statement = serde_json::from_value(json!({666            "type": "EmptyStatement"667        }))668        .unwrap();669670        assert!(matches!(stmt, Statement::EmptyStatement(_)));671    }672673    /// Babel serializes `using`/`await using` declarations as ordinary674    /// VariableDeclarations whose `kind` is "using" / "await using" (with a675    /// space). Both must round-trip so the NAPI boundary does not reject676    /// files containing them.677    #[test]678    fn using_declaration_kinds_round_trip() {679        for kind in ["using", "await using"] {680            let input = json!({681                "type": "VariableDeclaration",682                "kind": kind,683                "declarations": [684                    {685                        "type": "VariableDeclarator",686                        "id": { "type": "Identifier", "name": "resource" },687                        "init": { "type": "NullLiteral" }688                    }689                ]690            });691692            let stmt: Statement = serde_json::from_value(input.clone()).unwrap();693            assert!(matches!(stmt, Statement::VariableDeclaration(_)));694            assert_eq!(serde_json::to_value(&stmt).unwrap()["kind"], json!(kind));695        }696    }697698    #[test]699    fn malformed_known_statement_type_errors() {700        let err = serde_json::from_value::<Statement>(json!({701            "type": "IfStatement",702            "consequent": {703                "type": "EmptyStatement"704            }705        }))706        .unwrap_err();707708        assert!(709            err.to_string().contains("missing field `test`"),710            "unexpected error: {err}"711        );712    }713714    #[test]715    fn statement_without_type_field_errors() {716        let err = serde_json::from_value::<Statement>(json!({717            "start": 0,718            "end": 1719        }))720        .unwrap_err();721722        assert!(723            err.to_string().contains("`type`"),724            "unexpected error: {err}"725        );726    }727728    #[test]729    fn non_object_statement_errors() {730        let err = serde_json::from_value::<Statement>(json!([1, 2])).unwrap_err();731        assert!(732            err.to_string().contains("`type`"),733            "unexpected error: {err}"734        );735    }736737    #[test]738    fn non_string_type_field_errors() {739        let err = serde_json::from_value::<Statement>(json!({ "type": 7 })).unwrap_err();740        assert!(741            err.to_string().contains("`type`"),742            "unexpected error: {err}"743        );744    }745746    /// Mutating the raw node through the scoped mutator refreshes the cached747    /// base, and mutations that strip `type` are rejected.748    #[test]749    fn with_raw_mut_refreshes_base_and_guards_type() {750        let raw = json!({751            "type": "TSExportAssignment",752            "start": 5,753            "expression": { "type": "Identifier", "name": "x" }754        });755        let Statement::Unknown(mut unknown) = serde_json::from_value(raw).unwrap() else {756            panic!("expected Unknown");757        };758759        unknown760            .with_raw_mut(|v| {761                let mut parsed = v.parse_value();762                parsed["start"] = json!(9);763                parsed["expression"]["name"] = json!("y");764                *v = RawNode::from_value(&parsed);765            })766            .unwrap();767        assert_eq!(unknown.base().start, Some(9));768        assert_eq!(769            unknown.raw().parse_value()["expression"]["name"],770            json!("y")771        );772773        let err = unknown.with_raw_mut(|v| {774            let mut parsed = v.parse_value();775            parsed.as_object_mut().unwrap().remove("type");776            *v = RawNode::from_value(&parsed);777        });778        assert!(err.is_err(), "type removal must be rejected");779    }780}

Code quality findings 18

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
/// Deserialization dispatches through [`KnownStatement`]: a modeled `type`
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
/// The node's `type` discriminant, read from the captured [`BaseNode`].
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
/// Mutate the raw node, then refresh the cached [`BaseNode`] so `base()`
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
/// Single source of truth for the statement `type` tags [`Statement`] models.
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
/// Generates the [`KnownStatement`] dispatch enum, its `From` mapping, and
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
/// each other. A variant added to [`Statement`] but not listed here still
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
/// degrades to [`Statement::Unknown`] silently; that residual gap is
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
/// rather than the [`Statement::Unknown`] catch-all. Callers that
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let file: crate::File = serde_json::from_value(input.clone()).unwrap();
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
match &file.program.body[0] {
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
assert!(matches!(function.body.body[0], Statement::Unknown(_)));
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
for kind in ["using", "await using"] {
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
assert_eq!(serde_json::to_value(&stmt).unwrap()["kind"], json!(kind));
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
parsed["start"] = json!(9);
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
parsed["expression"]["name"] = json!("y");
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
.unwrap();
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
unknown.raw().parse_value()["expression"]["name"],
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
parsed.as_object_mut().unwrap().remove("type");

Get this view in your editor

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