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}