Skip to content

feat(parser): complete Hermes Flow stripping parity#11702

Merged
kdy1 merged 3 commits intomainfrom
kdy1/flow-hermes-stripping-parity
Mar 18, 2026
Merged

feat(parser): complete Hermes Flow stripping parity#11702
kdy1 merged 3 commits intomainfrom
kdy1/flow-hermes-stripping-parity

Conversation

@kdy1
Copy link
Copy Markdown
Member

@kdy1 kdy1 commented Mar 18, 2026

Summary

This PR completes Hermes-based Flow stripping parity by extending parser support and adding strip correctness coverage for Hermes corpus cases.

What changed

  • Extended Flow syntax flags/options in parser config:
    • decorators
    • components
    • pattern_matching
  • Updated Hermes Flow options loader to read:
    • esproposal_decorators
    • components
    • pattern_matching
    • plus fallback enablement for match/*, components/*, and hook_syntax/* fixtures.
  • Implemented Flow component/hook parsing with lowering to existing AST nodes:
    • declaration forms (component, hook, declare component, export forms)
    • type forms (component (...) renders T, hook (...) => T)
  • Implemented Flow match parsing and lowering (statement + expression paths) under pattern_matching:
    • supports Hermes corpus pattern forms (literal/unary literal, wildcard, bindings, object/array, |, as, guard)
    • expression lowering includes non-exhaustive fallback throw.
  • Added Hermes strip correctness test:
    • crates/swc/tests/flow_strip_hermes.rs
    • checks expected-errors=false fixtures, IsModule::Unknown, no __flow_ leakage, ES reparse success
    • supports strip-known-fail list + stale known-fail detection.
  • Added strip known-fail file with agreed 2 cases:
    • async_await/migrated_0020.js
    • async_await/migrated_0024.js
  • Emptied Hermes parser known-fail.txt and updated sync script to stop category-based known-fail generation.

Verification

Executed locally:

  • git submodule update --init --recursive
  • cargo test -p swc_ecma_parser --test flow_hermes --features flow
  • cargo test -p swc --test flow_strip_hermes --features flow
  • cargo test -p swc --test flow_strip_correctness --features flow -- --ignored
  • cargo test -p swc_ecma_parser
  • cargo fmt --all
  • cargo clippy --all --all-targets -- -D warnings

Additional note:

  • cargo test -p swc was also run, but tests/source_map.rs::issue_622 failed due sourcemap-validator runtime assertion (There were no mappings in the file) in local environment; this appears unrelated to the Flow/Hermes parser-strip changes.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 18, 2026

⚠️ No Changeset found

Latest commit: f8ee699

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 18, 2026

Binary Sizes

File Size
swc.linux-x64-gnu.node 28M (28963912 bytes)

Commit: dd86413

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 18, 2026

Merging this PR will not alter performance

✅ 219 untouched benchmarks


Comparing kdy1/flow-hermes-stripping-parity (f8ee699) with main (918b6ac)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (7528507) during the generation of this report, so 918b6ac was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@kdy1 kdy1 marked this pull request as ready for review March 18, 2026 03:29
@kdy1 kdy1 requested a review from a team as a code owner March 18, 2026 03:29
Copilot AI review requested due to automatic review settings March 18, 2026 03:29
@kdy1 kdy1 merged commit f041f4c into main Mar 18, 2026
115 checks passed
@kdy1 kdy1 deleted the kdy1/flow-hermes-stripping-parity branch March 18, 2026 03:29
@github-actions github-actions Bot added this to the Planned milestone Mar 18, 2026
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 18, 2026

PR Review: feat(parser): complete Hermes Flow stripping parity

Overall this is a solid, well-structured PR that adds substantial Flow syntax support (components, hooks, pattern matching, decorators) and backs it up with a thorough strip correctness test. A few observations:

Code Quality

Good:

  • Clean separation of concerns: syntax flags in syntax.rs, parsing in stmt.rs/typescript.rs, test harness in flow_strip_hermes.rs.
  • The known-fail-strip.txt + stale detection pattern is a nice way to track incremental progress.
  • Consistent use of #[cfg(feature = "flow")] / #[cfg(not(feature = "flow"))] paired stubs keeps the non-flow build clean.
  • The build_flow_syntax helper with fallback enablement by directory prefix is a pragmatic approach for the Hermes corpus.

Suggestions:

  • flow_strip_hermes.rs and flow_hermes.rs both define HermesOptions, fixture_root(), corpus_root(), read_expected_errors(), and build_flow_syntax() with essentially identical logic. Consider extracting these into a shared test utility module to reduce duplication and prevent the two copies from drifting apart over time.

Potential Bugs / Issues

  1. is_flow_match_keyword lookahead semantics (stmt.rs:1054-1060): The function calls self.input().next() to peek ahead, but next() on an Input typically advances the cursor. If this is actually advancing the token stream as a side effect of a check, then calling is_flow_match_keyword() in expr.rs:2634 before the Class / arrow checks could silently consume a token when the condition is false (i.e., when match is followed by ( but the spacing condition fails). Please verify that self.input().next() here is a non-consuming peek or that the token stream state is properly restored on the false path.

  2. flow_match_make_temp_ident naming collisions (stmt.rs:1061-1063): The generated identifier __match_subject_{span.lo.0} is deterministic per source position, which is good for a single match expression. However, if the lowered output is later fed through another parser pass (e.g., minifier or bundler), identifiers starting with __ could collide with other synthetic names. This is low-risk but worth noting.

  3. __flow_ leakage check (flow_strip_hermes.rs:202-205): The test asserts !output.code.contains("__flow_") but the match lowering generates __match_subject_ prefixed identifiers. If in the future any Flow-specific synthetic names are introduced with a different prefix, this check won't catch them. Consider also asserting !output.code.contains("__match_subject_") if the intent is that lowered output should be free of all synthetic names (or document that __match_subject_ is expected to survive stripping).

  4. Error code reuse (stmt.rs, typescript.rs): Several Flow-specific error conditions emit SyntaxError::TS1003 or SyntaxError::TS1014. While this may be intentional (reusing existing error codes), it could be confusing for users seeing TypeScript error codes in Flow parsing errors. If Flow-specific error codes are planned for the future, a tracking comment would help.

Performance Considerations

  • The flow_match_lower_pattern function clones value: Box<Expr> for each property/element in object/array patterns (e.g., value.clone() at multiple sites). For deeply nested patterns with many properties, this produces O(n) clones of the subject expression subtree per nesting level. For typical use this is fine, but for pathological cases (very large match patterns), it could be expensive. A future optimization could use a shared reference or temp variable per nesting level.

  • The is_flow_match_keyword function is called on every expression and statement parse. The early flow_pattern_matching() flag check ensures it short-circuits quickly when the feature is disabled, which is good.

Test Coverage

  • The flow_strip_hermes.rs test is thorough: it validates no __flow_ leakage, ES reparse success, known-fail management, and stale known-fail detection.
  • The known-fail list is minimal (2 async_await cases), which is a good sign for coverage completeness.
  • One gap: there don't appear to be dedicated unit/fixture tests for the new parsing logic (component/hook declarations, match expressions/statements) beyond the Hermes corpus. If any of these constructs have edge cases not covered by the Hermes fixtures, they won't be caught. Consider adding targeted fixture tests for:
    • Nested match expressions
    • Match with all pattern types combined
    • Component/hook with complex generic parameters
    • Error recovery paths (e.g., match with no cases, component with missing renders)

Minor Nits

  • stmt.rs:902-903: In flow_match_parse_array_pattern, eating a comma and immediately continue-ing means consecutive commas silently produce no element (elision). This may be intentional for alignment with JS array destructuring, but it differs from Flow's match semantics where each arm should have an explicit pattern. Worth a comment if intentional.
  • The duplicated decorator-handling blocks in module_item.rs (lines 348-357 and 397-407) are identical — consider extracting a small helper.

Security

No concerns — this is purely parsing/AST lowering with no I/O or external input beyond source files.


Verdict: This is high-quality work that significantly advances Flow support. The main actionable items are (1) verifying the is_flow_match_keyword lookahead doesn't consume tokens on the false path, (2) deduplicating the test helpers between the two test files, and (3) considering targeted fixture tests for the new syntax. The rest are minor suggestions for future improvement.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f8ee699693

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1685 to +1686
is_async: false,
is_generator: false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve enclosing async/generator context in match expr lowering

flow_match_make_iife_expr always wraps a match (...) { ... } expression in a plain function () { ... }() with is_async/is_generator hard-coded to false. In an async function, an arm like 1: await foo() now strips to return await foo() inside a non-async function, which makes the emitted JS invalid; in generators it similarly breaks yield, and it also changes lexical captures such as this and arguments.

Useful? React with 👍 / 👎.

Comment on lines +1224 to +1228
bindings.push(FlowMatchBindingInit {
kind: binding.kind,
pat: binding.pat.clone(),
init: value,
span: binding.span,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Compute object rest bindings from unmatched properties

For object patterns with a rest binding, the lowering currently initializes the binding from the entire subject object. For example, match (x) { {const a, ...const rest}: rest } will bind rest to x, not to x without a. That makes every supported object-rest case return the wrong runtime value even though pattern-object.js now exercises these forms.

Useful? React with 👍 / 👎.

Comment on lines +4037 to +4041
if self.input().syntax().flow_components() {
if self.is_flow_contextual_word("component") {
self.bump();
return self.parse_flow_component_type(start).map(Box::new);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep component contextual when parsing Flow types

This branch unconditionally treats any type-position identifier named component as the start of a Flow component type. With FlowSyntax.components enabled, valid references like type T = component; now fail because parse_flow_component_type immediately expects (; the adjacent hook branch has the same problem. These keywords need lookahead before they steal ordinary type identifiers.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for additional Hermes/Flow syntax extensions (Flow decorators, component/hook syntax, and match pattern matching) and introduces a new SWC-level fixture test to ensure Flow stripping produces re-parsable JavaScript for the Hermes corpus.

Changes:

  • Extend FlowSyntax with new feature toggles (decorators, components, pattern_matching) and wire them into SyntaxFlags.
  • Implement parsing/lowering for Flow component/hook declarations/types and Flow match expressions/statements.
  • Add flow_strip_hermes test + new known-fail-strip.txt, and reset the Hermes parser known-fail.txt generation to be empty.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/swc/tests/flow_strip_hermes.rs New integration test compiling the Hermes Flow corpus through SWC Flow-strip + ES reparse.
crates/swc_ecma_parser/tests/flow-hermes/known-fail.txt Emptied experimental known-fail list (now generated as empty).
crates/swc_ecma_parser/tests/flow-hermes/known-fail-strip.txt New list of Flow-strip known-failing fixtures.
crates/swc_ecma_parser/tests/flow_hermes.rs Reads new per-fixture options and enables Flow decorators/components/pattern-matching flags.
crates/swc_ecma_parser/src/syntax.rs Adds new FlowSyntax flags, serde aliases, and new SyntaxFlags bits/accessors.
crates/swc_ecma_parser/src/parser/typescript.rs Adds Flow component/hook parsing support in the TS/Flow parser path.
crates/swc_ecma_parser/src/parser/stmt.rs Adds Flow match parsing and lowering into standard JS AST constructs.
crates/swc_ecma_parser/src/parser/module_item.rs Adjust decorator handling for Flow-decorators mode; export-default support for component/hook decls.
crates/swc_ecma_parser/src/parser/expr.rs Recognize/parse Flow match in expression position.
crates/swc_ecma_parser/src/parser/class_and_fn.rs Adjust leading-decorator error behavior when Flow decorators are enabled.
crates/swc_ecma_parser/scripts/sync_flow_hermes.sh Stops generating known-fail.txt content (writes empty file).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +1223 to +1227
if let Some(Some(binding)) = rest {
bindings.push(FlowMatchBindingInit {
kind: binding.kind,
pat: binding.pat.clone(),
init: value,
Comment on lines +1205 to +1218
for (key, pat) in props {
let has_key = self.flow_match_bin_expr(
span,
BinaryOp::In,
self.flow_match_prop_key_expr(key),
value.clone(),
);
predicate = self.flow_match_and_expr(span, predicate, has_key);

let lowered = self.flow_match_lower_pattern(
span,
self.flow_match_member_expr(span, value.clone(), key),
pat,
);
}

fn flow_match_make_temp_ident(&self, span: Span) -> Ident {
Ident::new_no_ctxt(format!("__match_subject_{}", span.lo.0).into(), span)
@github-actions github-actions Bot modified the milestones: Planned, 1.15.21 Mar 22, 2026
@swc-project swc-project locked as resolved and limited conversation to collaborators Apr 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants