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