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;
5
6use crate::expressions::{Expression, Identifier};
7use crate::patterns::PatternLike;
8
9fn is_false(v: &bool) -> bool {
10    !v
11}
12
13#[derive(Debug, Clone, Serialize)]
14#[serde(tag = "type")]
15pub enum Statement {
16    // Statements
17    BlockStatement(BlockStatement),
18    ReturnStatement(ReturnStatement),
19    IfStatement(IfStatement),
20    ForStatement(ForStatement),
21    WhileStatement(WhileStatement),
22    DoWhileStatement(DoWhileStatement),
23    ForInStatement(ForInStatement),
24    ForOfStatement(ForOfStatement),
25    SwitchStatement(SwitchStatement),
26    ThrowStatement(ThrowStatement),
27    TryStatement(TryStatement),
28    BreakStatement(BreakStatement),
29    ContinueStatement(ContinueStatement),
30    LabeledStatement(LabeledStatement),
31    ExpressionStatement(ExpressionStatement),
32    EmptyStatement(EmptyStatement),
33    DebuggerStatement(DebuggerStatement),
34    WithStatement(WithStatement),
35    // Declarations are also statements
36    VariableDeclaration(VariableDeclaration),
37    FunctionDeclaration(FunctionDeclaration),
38    ClassDeclaration(ClassDeclaration),
39    // Import/export declarations
40    ImportDeclaration(crate::declarations::ImportDeclaration),
41    ExportNamedDeclaration(crate::declarations::ExportNamedDeclaration),
42    ExportDefaultDeclaration(crate::declarations::ExportDefaultDeclaration),
43    ExportAllDeclaration(crate::declarations::ExportAllDeclaration),
44    // TypeScript declarations
45    TSTypeAliasDeclaration(crate::declarations::TSTypeAliasDeclaration),
46    TSInterfaceDeclaration(crate::declarations::TSInterfaceDeclaration),
47    TSEnumDeclaration(crate::declarations::TSEnumDeclaration),
48    TSModuleDeclaration(crate::declarations::TSModuleDeclaration),
49    TSDeclareFunction(crate::declarations::TSDeclareFunction),
50    // Flow declarations
51    TypeAlias(crate::declarations::TypeAlias),
52    OpaqueType(crate::declarations::OpaqueType),
53    InterfaceDeclaration(crate::declarations::InterfaceDeclaration),
54    DeclareVariable(crate::declarations::DeclareVariable),
55    DeclareFunction(crate::declarations::DeclareFunction),
56    DeclareClass(crate::declarations::DeclareClass),
57    DeclareModule(crate::declarations::DeclareModule),
58    DeclareModuleExports(crate::declarations::DeclareModuleExports),
59    DeclareExportDeclaration(crate::declarations::DeclareExportDeclaration),
60    DeclareExportAllDeclaration(crate::declarations::DeclareExportAllDeclaration),
61    DeclareInterface(crate::declarations::DeclareInterface),
62    DeclareTypeAlias(crate::declarations::DeclareTypeAlias),
63    DeclareOpaqueType(crate::declarations::DeclareOpaqueType),
64    EnumDeclaration(crate::declarations::EnumDeclaration),
65    /// Catch-all for statement `type`s the typed AST does not model, e.g. the
66    /// TypeScript module-interop statements `import x = require(...)`,
67    /// `export = x`, and `export as namespace X`. Carries the complete raw
68    /// Babel node so the Babel path can preserve unmodeled top-level
69    /// statements verbatim instead of failing the whole file.
70    ///
71    /// Deserialization dispatches through [`KnownStatement`]: a modeled `type`
72    /// whose body is malformed errors with the typed variant's precise message
73    /// rather than degrading to `Unknown`. Adding a variant to this enum
74    /// requires adding it to the `known_statements!` list below, which is the
75    /// single source for the dispatch enum, its `From` mapping, and
76    /// [`KNOWN_STATEMENT_TYPES`]. A variant added here but not there degrades
77    /// to `Unknown` silently; that is the one drift case structure cannot
78    /// catch.
79    #[serde(untagged)]
80    Unknown(UnknownStatement),
81}
82
83// NOTE: `Deserialize` for `Statement` is hand-written below; the
84// `#[serde(tag = "type")]` and `#[serde(untagged)]` attributes on the enum
85// configure only the derived `Serialize`.
86
87#[derive(Debug, Clone)]
88pub struct UnknownStatement {
89    raw: serde_json::Value,
90    base: BaseNode,
91}
92
93impl UnknownStatement {
94    pub fn from_raw(raw: serde_json::Value) -> Result<Self, String> {
95        match raw.get("type").and_then(serde_json::Value::as_str) {
96            Some(_) => {
97                // By-ref deserialization clones only the fields BaseNode
98                // reads, not the whole (arbitrarily large) unknown subtree.
99                let base = BaseNode::deserialize(&raw)
100                    .map_err(|err| format!("failed to read unknown statement base: {err}"))?;
101                Ok(Self { raw, base })
102            }
103            None => Err("unknown statement is missing a string `type` field".to_string()),
104        }
105    }
106
107    /// The node's `type` discriminant, read from the captured [`BaseNode`].
108    /// Falls back to `"Unknown"` rather than panicking if the raw node was
109    /// mutated out from under it.
110    pub fn node_type(&self) -> &str {
111        self.base.node_type.as_deref().unwrap_or("Unknown")
112    }
113
114    pub fn raw(&self) -> &serde_json::Value {
115        &self.raw
116    }
117
118    /// Mutate the raw node, then refresh the cached [`BaseNode`] so `base()`
119    /// and `node_type()` cannot drift from `raw`. Mutations that remove the
120    /// string `type` field are rejected and rolled back.
121    pub fn with_raw_mut<R>(
122        &mut self,
123        f: impl FnOnce(&mut serde_json::Value) -> R,
124    ) -> Result<R, String> {
125        let saved = self.raw.clone();
126        let result = f(&mut self.raw);
127        if self.raw.get("type").and_then(serde_json::Value::as_str).is_none() {
128            self.raw = saved;
129            return Err("unknown statement mutation removed the string `type` field".to_string());
130        }
131        match BaseNode::deserialize(&self.raw) {
132            Ok(base) => {
133                self.base = base;
134                Ok(result)
135            }
136            Err(err) => {
137                self.raw = saved;
138                Err(format!("failed to refresh unknown statement base: {err}"))
139            }
140        }
141    }
142
143    pub fn base(&self) -> &BaseNode {
144        &self.base
145    }
146}
147
148impl Serialize for UnknownStatement {
149    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150    where
151        S: Serializer,
152    {
153        self.raw.serialize(serializer)
154    }
155}
156
157impl<'de> Deserialize<'de> for UnknownStatement {
158    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159    where
160        D: Deserializer<'de>,
161    {
162        let raw = serde_json::Value::deserialize(deserializer)?;
163        Self::from_raw(raw).map_err(D::Error::custom)
164    }
165}
166
167impl<'de> Deserialize<'de> for Statement {
168    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169    where
170        D: Deserializer<'de>,
171    {
172        let raw = serde_json::Value::deserialize(deserializer)?;
173        let node_type = raw
174            .get("type")
175            .and_then(serde_json::Value::as_str)
176            .ok_or_else(|| D::Error::custom("statement is missing a string `type` field"))?
177            .to_string();
178
179        if is_known_statement_type(&node_type) {
180            let known: KnownStatement = serde_json::from_value(raw).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<Box<serde_json::Value>>,
512    #[serde(
513        default,
514        skip_serializing_if = "Option::is_none",
515        rename = "typeParameters"
516    )]
517    pub type_parameters: Option<Box<serde_json::Value>>,
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<Box<serde_json::Value>>,
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<serde_json::Value>>,
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<serde_json::Value>>,
561    #[serde(
562        default,
563        skip_serializing_if = "Option::is_none",
564        rename = "superTypeParameters"
565    )]
566    pub super_type_parameters: Option<Box<serde_json::Value>>,
567    #[serde(
568        default,
569        skip_serializing_if = "Option::is_none",
570        rename = "typeParameters"
571    )]
572    pub type_parameters: Option<Box<serde_json::Value>>,
573    #[serde(default, skip_serializing_if = "Option::is_none")]
574    pub mixins: Option<Vec<serde_json::Value>>,
575}
576
577#[cfg(test)]
578mod tests {
579    use serde_json::json;
580
581    use super::Statement;
582
583    #[test]
584    fn unknown_statement_round_trips_at_program_level() {
585        let input = json!({
586            "type": "File",
587            "comments": [],
588            "errors": [],
589            "program": {
590                "type": "Program",
591                "sourceType": "module",
592                "interpreter": null,
593                "body": [
594                    {
595                        "type": "TSImportEqualsDeclaration",
596                        "start": 0,
597                        "end": 39,
598                        "importKind": "value",
599                        "isExport": false,
600                        "id": { "type": "Identifier", "name": "lib" },
601                        "moduleReference": {
602                            "type": "TSExternalModuleReference",
603                            "expression": { "type": "StringLiteral", "value": "shared-runtime" }
604                        }
605                    }
606                ],
607                "directives": []
608            }
609        });
610
611        let file: crate::File = serde_json::from_value(input.clone()).unwrap();
612
613        match &file.program.body[0] {
614            Statement::Unknown(unknown) => {
615                assert_eq!(unknown.node_type(), "TSImportEqualsDeclaration");
616            }
617            other => panic!("expected Unknown, got {other:?}"),
618        }
619        assert_eq!(serde_json::to_value(&file).unwrap(), input);
620    }
621
622    #[test]
623    fn unknown_statement_round_trips_inside_function_block() {
624        let input = json!({
625            "type": "FunctionDeclaration",
626            "id": null,
627            "generator": false,
628            "async": false,
629            "params": [],
630            "body": {
631                "type": "BlockStatement",
632                "body": [
633                    {
634                        "type": "TSExportAssignment",
635                        "expression": { "type": "Identifier", "name": "x" }
636                    }
637                ],
638                "directives": []
639            }
640        });
641
642        let stmt: Statement = serde_json::from_value(input.clone()).unwrap();
643        let Statement::FunctionDeclaration(function) = &stmt else {
644            panic!("expected function declaration, got {stmt:?}");
645        };
646        assert!(matches!(function.body.body[0], Statement::Unknown(_)));
647        assert_eq!(serde_json::to_value(&stmt).unwrap(), input);
648    }
649
650    /// The public discrimination helper mirrors the deserializer's dispatch:
651    /// exactly the macro-listed statement tags are "known".
652    #[test]
653    fn is_known_statement_type_matches_macro_list() {
654        assert!(super::is_known_statement_type("IfStatement"));
655        assert!(super::is_known_statement_type("VariableDeclaration"));
656        assert!(!super::is_known_statement_type("CallExpression"));
657        assert!(!super::is_known_statement_type("TSImportEqualsDeclaration"));
658    }
659
660    #[test]
661    fn known_statement_type_uses_typed_variant() {
662        let stmt: Statement = serde_json::from_value(json!({
663            "type": "EmptyStatement"
664        }))
665        .unwrap();
666
667        assert!(matches!(stmt, Statement::EmptyStatement(_)));
668    }
669
670    #[test]
671    fn malformed_known_statement_type_errors() {
672        let err = serde_json::from_value::<Statement>(json!({
673            "type": "IfStatement",
674            "consequent": {
675                "type": "EmptyStatement"
676            }
677        }))
678        .unwrap_err();
679
680        assert!(
681            err.to_string().contains("missing field `test`"),
682            "unexpected error: {err}"
683        );
684    }
685
686    #[test]
687    fn statement_without_type_field_errors() {
688        let err = serde_json::from_value::<Statement>(json!({
689            "start": 0,
690            "end": 1
691        }))
692        .unwrap_err();
693
694        assert!(
695            err.to_string().contains("`type`"),
696            "unexpected error: {err}"
697        );
698    }
699
700    #[test]
701    fn non_object_statement_errors() {
702        let err = serde_json::from_value::<Statement>(json!([1, 2])).unwrap_err();
703        assert!(
704            err.to_string().contains("`type`"),
705            "unexpected error: {err}"
706        );
707    }
708
709    #[test]
710    fn non_string_type_field_errors() {
711        let err = serde_json::from_value::<Statement>(json!({ "type": 7 })).unwrap_err();
712        assert!(
713            err.to_string().contains("`type`"),
714            "unexpected error: {err}"
715        );
716    }
717
718    /// Mutating the raw node through the scoped mutator refreshes the cached
719    /// base, and mutations that strip `type` are rejected.
720    #[test]
721    fn with_raw_mut_refreshes_base_and_guards_type() {
722        let raw = json!({
723            "type": "TSExportAssignment",
724            "start": 5,
725            "expression": { "type": "Identifier", "name": "x" }
726        });
727        let Statement::Unknown(mut unknown) = serde_json::from_value(raw).unwrap() else {
728            panic!("expected Unknown");
729        };
730
731        unknown
732            .with_raw_mut(|v| {
733                v["start"] = json!(9);
734                v["expression"]["name"] = json!("y");
735            })
736            .unwrap();
737        assert_eq!(unknown.base().start, Some(9));
738        assert_eq!(unknown.raw()["expression"]["name"], json!("y"));
739
740        let err = unknown.with_raw_mut(|v| {
741            v.as_object_mut().unwrap().remove("type");
742        });
743        assert!(err.is_err(), "type removal must be rejected");
744    }
745}