Rust Port of React Compiler's Babel AST: What's Inside PR #36173

A sprawling pull request on the React repository (PR #36173) ports the React compiler's Babel AST handling to Rust, addressing long-standing round-trip JSON discrepancies, adding support for Flow's match syntax, and introducing a tolerant deserialization strategy for unmodeled TypeScript statements. The PR touches nearly every corner of the compiler's frontend, from predicate field handling to SWC converter fixes.

Round-Trip JSON Fixes for Predicate Fields

The Babel parser emits "predicate": null on function-like nodes (FunctionDeclaration, ArrowFunctionExpression, etc.) to signal absence of a Flow %checks predicate. The Rust port's plain Option deserialization collapsed both "absent" and "explicit null" into None, and skip_serializing_if = "Option::is_none" dropped the field on serialization, breaking byte-equivalent comparison. The fix applies the existing crate::common::nullable_value deserializer to all five predicate fields, distinguishing:

After the change, round-trip tests jumped from 1778/1779 to 1782/1782 (baseline had 1 failure). The PR also removed several known_failures entries that were no longer needed.

Flow Match Syntax Support

Six Flow match fixtures had been renamed to error.todo-* because the compiler couldn't parse them. The PR ports actual support from commits 0dc7f2e and d8aae6e, leveraging hermes-parser >= 0.28 with enableExperimentalFlowMatchSyntax flag. The snap package was pinned to hermes-parser 0.25.1 — now bumped to ^0.28.0. All six fixtures pass against checked-in snapshots without regeneration. The PR also updated .prettierignore and added a .prettierrc.js override using prettier-plugin-hermes-parser to format the match fixtures.

Tolerant Deserialization for Unmodeled TypeScript Statements

Three TypeScript statement types — TSImportEqualsDeclaration, TSExportAssignment, and TSNamespaceExportDeclaration — were previously not modeled in the Rust AST. The Babel/NAPI path threw "Failed to parse AST JSON: unknown variant ...", failing the entire file. The SWC converter silently rewrote them to EmptyStatement, erasing output. OXC panicked. The PR introduces a Statement::Unknown(UnknownStatement) variant via a #[serde(untagged)] catch-all, preserving the raw node. Deserialization is hand-written, dispatching modeled type tags through a KnownStatement helper, and unmodeled tags fall through to Unknown. A known_statements! macro ensures the dispatch enum, From mapping, and tag list stay in sync. Unknown statements are preserved verbatim in output; function-body occurrences trigger an UnsupportedSyntax bailout.

SWC Converter Fixes

The SWC forward converter previously rewrote the three unmodeled statements to EmptyStatement. The PR routes them through the same Statement::Unknown carrier, building a Babel-compatible raw node. The reverse converter rebuilds real SWC module declarations at the ModuleItem layer. Additionally, a ts_namespace_export_fixup module handles a misprint in swc_ecma_codegen v24 that outputs TsNamespaceExportDecl as TsExportAssignment shape. The fixup uses source map positions to identify affected lines.

Test Results

What This Means for Developers

If you work with the React compiler or contribute to its Rust port, this PR dramatically improves robustness. The round-trip JSON fixes ensure AST fidelity across serialization boundaries. Flow match syntax support opens the door for pattern matching in React components. The tolerant deserialization strategy means the compiler can handle TypeScript code that uses module-interop forms like export = X without crashing or silently dropping statements.

The PR also demonstrates a pragmatic approach to AST handling: instead of modeling every possible syntax variant upfront, the team chose to degrade gracefully with UnknownStatement, preserving output while tracking unsupported nodes. This is a pattern worth adopting in other compiler frontends.

Next Steps

The PR notes that the scope-resolution half of test-babel-ast.sh remains red on the pr-36173 tip due to a node-ID migration that removed position-based keying while babel-ast-to-json.mjs still emits offset-based scope JSON. That generator gap needs its own fix before rebasing. Developers should expect follow-up PRs to address this and to complete the SWC/OXC reverse converter coverage for the three unmodeled statements.