Skip to main content

react_compiler_ast/
statements.rs

1use serde::de::Error as _;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3
4use crate::common::BaseNode;
5use crate::common::RawNode;
6
7use crate::expressions::{Expression, Identifier};
8use crate::patterns::PatternLike;
9
10fn is_false(v: &bool) -> bool {
11    !v
12}
13
14#[derive(Debug, Clone, Serialize)]
15#[serde(tag = "type")]
16pub enum Statement {
17    // Statements
18    BlockStatement(BlockStatement),
19    ReturnStatement(ReturnStatement),
20    IfStatement(IfStatement),
21    ForStatement(ForStatement),
22    WhileStatement(WhileStatement),
23    DoWhileStatement(DoWhileStatement),
24    ForInStatement(ForInStatement),
25    ForOfStatement(ForOfStatement),
26    SwitchStatement(SwitchStatement),
27    ThrowStatement(ThrowStatement),
28    TryStatement(TryStatement),
29    BreakStatement(BreakStatement),
30    ContinueStatement(ContinueStatement),
31    LabeledStatement(LabeledStatement),
32    ExpressionStatement(ExpressionStatement),
33    EmptyStatement(EmptyStatement),
34    DebuggerStatement(DebuggerStatement),
35    WithStatement(WithStatement),
36    // Declarations are also statements
37    VariableDeclaration(VariableDeclaration),
38    FunctionDeclaration(FunctionDeclaration),
39    ClassDeclaration(ClassDeclaration),
40    // Import/export declarations
41    ImportDeclaration(crate::declarations::ImportDeclaration),
42    ExportNamedDeclaration(crate::declarations::ExportNamedDeclaration),
43    ExportDefaultDeclaration(crate::declarations::ExportDefaultDeclaration),
44    ExportAllDeclaration(crate::declarations::ExportAllDeclaration),
45    // TypeScript declarations
46    TSTypeAliasDeclaration(crate::declarations::TSTypeAliasDeclaration),
47    TSInterfaceDeclaration(crate::declarations::TSInterfaceDeclaration),
48    TSEnumDeclaration(crate::declarations::TSEnumDeclaration),
49    TSModuleDeclaration(crate::declarations::TSModuleDeclaration),
50    TSDeclareFunction(crate::declarations::TSDeclareFunction),
51    // Flow declarations
52    TypeAlias(crate::declarations::TypeAlias),
53    OpaqueType(crate::declarations::OpaqueType),
54    InterfaceDeclaration(crate::declarations::InterfaceDeclaration),
55    DeclareVariable(crate::declarations::DeclareVariable),
56    DeclareFunction(crate::declarations::DeclareFunction),
57    DeclareClass(crate::declarations::DeclareClass),
58    DeclareModule(crate::declarations::DeclareModule),
59    DeclareModuleExports(crate::declarations::DeclareModuleExports),
60    DeclareExportDeclaration(crate::declarations::DeclareExportDeclaration),
61    DeclareExportAllDeclaration(crate::declarations::DeclareExportAllDeclaration),
62    DeclareInterface(crate::declarations::DeclareInterface),
63    DeclareTypeAlias(crate::declarations::DeclareTypeAlias),
64    DeclareOpaqueType(crate::declarations::DeclareOpaqueType),
65    EnumDeclaration(crate::declarations::EnumDeclaration),
66    /// Catch-all for statement `type`s the typed AST does not model, e.g. the
67    /// TypeScript module-interop statements `import x = require(...)`,
68    /// `export = x`, and `export as namespace X`. Carries the complete raw
69    /// Babel node so the Babel path can preserve unmodeled top-level
70    /// statements verbatim instead of failing the whole file.
71    ///
72    /// Deserialization dispatches through [`KnownStatement`]: a modeled `type`
73    /// whose body is malformed errors with the typed variant's precise message
74    /// rather than degrading to `Unknown`. Adding a variant to this enum
75    /// requires adding it to the `known_statements!` list below, which is the
76    /// single source for the dispatch enum, its `From` mapping, and
77    /// [`KNOWN_STATEMENT_TYPES`]. A variant added here but not there degrades
78    /// to `Unknown` silently; that is the one drift case structure cannot
79    /// catch.
80    #[serde(untagged)]
81    Unknown(UnknownStatement),
82}
83
84// NOTE: `Deserialize` for `Statement` is hand-written below; the
85// `#[serde(tag = "type")]` and `#[serde(untagged)]` attributes on the enum
86// configure only the derived `Serialize`.
87
88#[derive(Debug, Clone)]
89pub struct UnknownStatement {
90    raw: RawNode,
91    base: BaseNode,
92}
93
94impl UnknownStatement {
95    pub fn from_raw(raw: RawNode) -> Result<Self, String> {
96        match raw.type_name() {
97            Some(_) => {
98                // Parsing into BaseNode reads only the fields BaseNode declares,
99                // not the whole (arbitrarily large) unknown subtree.
100                let base = crate::common::from_json_str_unbounded::<BaseNode>(raw.get())
101                    .map_err(|err| format!("failed to read unknown statement base: {err}"))?;
102                Ok(Self { raw, base })
103            }
104            None => Err("unknown statement is missing a string `type` field".to_string()),
105        }
106    }
107
108    /// The node's `type` discriminant, read from the captured [`BaseNode`].
109    /// Falls back to `"Unknown"` rather than panicking if the raw node was
110    /// mutated out from under it.
111    pub fn node_type(&self) -> &str {
112        self.base.node_type.as_deref().unwrap_or("Unknown")
113    }
114
115    pub fn raw(&self) -> &RawNode {
116        &self.raw
117    }
118
119    /// Mutate the raw node, then refresh the cached [`BaseNode`] so `base()`
120    /// and `node_type()` cannot drift from `raw`. Mutations that remove the
121    /// string `type` field are rejected and rolled back.
122    pub fn with_raw_mut<R>(
123        &mut self,
124        f: impl FnOnce(&mut RawNode) -> R,
125    ) -> 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    }
143
144    pub fn base(&self) -> &BaseNode {
145        &self.base
146    }
147}
148
149impl Serialize for UnknownStatement {
150    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
151    where
152        S: Serializer,
153    {
154        self.raw.serialize(serializer)
155    }
156}
157
158impl<'de> Deserialize<'de> for UnknownStatement {
159    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
160    where
161        D: Deserializer<'de>,
162    {
163        let raw = RawNode::deserialize(deserializer)?;
164        Self::from_raw(raw).map_err(D::Error::custom)
165    }
166}
167
168impl<'de> Deserialize<'de> for Statement {
169    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170    where
171        D: Deserializer<'de>,
172    {
173        let raw = RawNode::deserialize(deserializer)?;
174        let node_type = raw
175            .type_name()
176            .ok_or_else(|| D::Error::custom("statement is missing a string `type` field"))?;
177
178        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}
189
190/// Single source of truth for the statement `type` tags [`Statement`] models.
191/// Generates the [`KnownStatement`] dispatch enum, its `From` mapping, and
192/// [`KNOWN_STATEMENT_TYPES`] from one list, so the three cannot drift from
193/// each other. A variant added to [`Statement`] but not listed here still
194/// degrades to [`Statement::Unknown`] silently; that residual gap is
195/// documented on the variant.
196macro_rules! known_statements {
197    ($($variant:ident => $ty:ty),+ $(,)?) => {
198        const KNOWN_STATEMENT_TYPES: &[&str] = &[$(stringify!($variant)),+];
199
200        /// Whether `node_type` is a statement `type` tag modeled by
201        /// [`Statement`], i.e. one that deserializes into a typed variant
202        /// rather than the [`Statement::Unknown`] catch-all. Callers that
203        /// need to discriminate statements from other node kinds must use
204        /// this instead of attempting a `Statement` deserialization: with
205        /// the tolerant catch-all, that attempt succeeds for any object
206        /// carrying a string `type` tag.
207        pub fn is_known_statement_type(node_type: &str) -> bool {
208            KNOWN_STATEMENT_TYPES.contains(&node_type)
209        }
210
211        #[derive(Debug, Deserialize)]
212        #[serde(tag = "type")]
213        enum KnownStatement {
214            $($variant($ty),)+
215        }
216
217        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}
226
227known_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}
273
274#[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}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct Directive {
285    #[serde(flatten)]
286    pub base: BaseNode,
287    pub value: DirectiveLiteral,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct DirectiveLiteral {
292    #[serde(flatten)]
293    pub base: BaseNode,
294    pub value: String,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct ReturnStatement {
299    #[serde(flatten)]
300    pub base: BaseNode,
301    pub argument: Option<Box<Expression>>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct ExpressionStatement {
306    #[serde(flatten)]
307    pub base: BaseNode,
308    pub expression: Box<Expression>,
309}
310
311#[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}
319
320#[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}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
331#[serde(tag = "type")]
332pub enum ForInit {
333    VariableDeclaration(VariableDeclaration),
334    #[serde(untagged)]
335    Expression(Box<Expression>),
336}
337
338#[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}
345
346#[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}
353
354#[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}
362
363#[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}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
375#[serde(tag = "type")]
376pub enum ForInOfLeft {
377    VariableDeclaration(VariableDeclaration),
378    #[serde(untagged)]
379    Pattern(Box<PatternLike>),
380}
381
382#[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}
389
390#[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}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
399pub struct ThrowStatement {
400    #[serde(flatten)]
401    pub base: BaseNode,
402    pub argument: Box<Expression>,
403}
404
405#[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}
413
414#[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}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct BreakStatement {
424    #[serde(flatten)]
425    pub base: BaseNode,
426    pub label: Option<Identifier>,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct ContinueStatement {
431    #[serde(flatten)]
432    pub base: BaseNode,
433    pub label: Option<Identifier>,
434}
435
436#[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}
443
444#[derive(Debug, Clone, Serialize, Deserialize)]
445pub struct EmptyStatement {
446    #[serde(flatten)]
447    pub base: BaseNode,
448}
449
450#[derive(Debug, Clone, Serialize, Deserialize)]
451pub struct DebuggerStatement {
452    #[serde(flatten)]
453    pub base: BaseNode,
454}
455
456#[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}
463
464#[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}
473
474#[derive(Debug, Clone, Serialize, Deserialize)]
475#[serde(rename_all = "lowercase")]
476pub enum VariableDeclarationKind {
477    Var,
478    Let,
479    Const,
480    Using,
481}
482
483#[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}
492
493#[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(...) { ... }` syntax
526    #[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(...) { ... }` syntax
533    #[serde(
534        default,
535        skip_serializing_if = "is_false",
536        rename = "__hookDeclaration"
537    )]
538    pub hook_declaration: bool,
539}
540
541#[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}
576
577#[cfg(test)]
578mod tests {
579    use serde_json::json;
580
581    use super::Statement;
582    use crate::common::RawNode;
583
584    #[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        });
611
612        let file: crate::File = serde_json::from_value(input.clone()).unwrap();
613
614        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    }
622
623    #[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        });
642
643        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    }
650
651    /// 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    }
660
661    #[test]
662    fn known_statement_type_uses_typed_variant() {
663        let stmt: Statement = serde_json::from_value(json!({
664            "type": "EmptyStatement"
665        }))
666        .unwrap();
667
668        assert!(matches!(stmt, Statement::EmptyStatement(_)));
669    }
670
671    #[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();
680
681        assert!(
682            err.to_string().contains("missing field `test`"),
683            "unexpected error: {err}"
684        );
685    }
686
687    #[test]
688    fn statement_without_type_field_errors() {
689        let err = serde_json::from_value::<Statement>(json!({
690            "start": 0,
691            "end": 1
692        }))
693        .unwrap_err();
694
695        assert!(
696            err.to_string().contains("`type`"),
697            "unexpected error: {err}"
698        );
699    }
700
701    #[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    }
709
710    #[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    }
718
719    /// Mutating the raw node through the scoped mutator refreshes the cached
720    /// 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        };
731
732        unknown
733            .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        );
745
746        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}