原文
Babel can emit statement kinds the typed AST does not model (the todo-ts-* fixtures pin three TS module-interop forms). Deserialization previously failed the whole file on the first such node, while the TS reference compiles the file and leaves the statement alone. Statement gains a final #[serde(untagged)] Unknown(UnknownStatement) variant carrying the complete raw node. Deserialization is hand-written and dispatches modeled `type` tags through a KnownStatement helper so a malformed modeled node still errors with its precise field-level message instead of degrading to Unknown; only genuinely unmodeled tags take the catch-all. The TS reference reaches its equivalent default case only via assertExhaustive (Babel's closed types), so it crashes; here unmodeled syntax is reachable by construction and degrades instead: top-level statements are preserved verbatim through re-serialization, and function-body occurrences record the standard UnsupportedSyntax bailout with an UnsupportedNode instruction carrying the raw node. A known_statements! macro is the single source for the dispatch enum, its From mapping, and the tag list, so those three cannot drift; a variant added to Statement but not the macro is the one remaining silent gap, documented on the variant. UnknownStatement caches BaseNode for position helpers; the scoped with_raw_mut mutator refreshes the cache and rejects mutations that strip `type`, so the two views cannot desync. Program-level analyses treat Unknown explicitly: the gating reference-before-declaration scan walks the raw node for identifier references (an `export = X` does reference X), and the prefilter and return-analysis arms are deliberately inert. SWC/OXC reverse converters emit a deliberate runtime tripwire (a throw in generated code) for the arms that are unreachable until the SWC forward conversion stops rewriting these statements to EmptyStatement in the next slice. Deserialization now materializes a serde_json::Value per statement before typed parsing. The cost is one move-based tree rebuild per nesting level at a one-time boundary; the previous derive also buffered every node through serde's internal Content to read the tag, so the delta is allocation shape, not asymptotics. Verified: ast unit tests including malformed/edge cases, a lowering integration test pinning the function-body bailout, round_trip green on the three fixtures, scoped and full Babel e2e green on all three with events parity, cargo test --workspace green. The scope-resolution half of test-babel-ast.sh is green on this stack's base and remains red corpus-wide on the pr-36173 tip, whose node-ID migration removed position-based keying while babel-ast-to-json.mjs still emits offset-based scope JSON; that generator gap needs its own fix before this stack rebases onto the tip. rust-port-0001-babel-ast.md's no-catch-all policy is amended to document Statement as the deliberate exception. Port adaptation for this branch's UnsupportedNode codegen fix (0957b55), which discriminated statement-vs-expression original_node by attempting a Statement deserialization. With the tolerant deserializer that attempt succeeds for every tagged object, which would silently emit expression nodes as raw statements and orphan their lvalue temporaries — regressing the ~10 fixtures that commit fixed. The codegen site now discriminates explicitly (codegen_unsupported_original_node): modeled statement tags parse typed and a parse failure is an invariant, not a degrade; tags that parse as Expression or PatternLike (both strict enums, no catch-all) flow through expression codegen unchanged, preserving the lvalue binding and the pattern placeholder fallback; only genuinely unmodeled tags — producible solely by the unknown-statement lowering bailout, i.e. from statement position — degrade to Statement::Unknown and are emitted verbatim, matching TS codegen's 'return node'. is_known_statement_type is now exposed (pub) from the known_statements! macro for this, and unit tests pin the dispatch (modeled statement tag, malformed modeled tag, expression tag, pattern tag, unknown tag).