Skip to content

Babel preset: Stop lowering named capturing groups for Hermes V1 - #59612

Draft
robhogan wants to merge 1 commit into
mainfrom
robhogan/babel-preset-hermes-named-groups
Draft

robhogan wants to merge 1 commit into
mainfrom
robhogan/babel-preset-hermes-named-groups

Conversation

@robhogan

Copy link
Copy Markdown
Collaborator

Summary

Since #59580, a u regex with a named backreference directly followed by a digit fails Hermes compilation with Invalid regular expression: Invalid escape.

The cause is @babel/plugin-transform-named-capturing-groups-regex, which rewrites \k<name> as a numbered backreference without guarding a following digit:

/(?<a>.)\k<a>1/u  ->  _wrapRegExp(/(.)\11/u, {a: 1})

\11 is then a reference to group 11, a syntax error in u mode. Before #59580 the unicode transform also dropped the u flag, and without it \11 reads as a legacy octal escape (a tab) - so these regexes compiled, but silently matched the wrong thing. Non-u regexes still have that miscompile on main.

Hermes V1 supports named capturing groups natively, so this stops lowering them for the Hermes profiles. The exception is a name repeated across alternatives (/(?<y>\d{4})-\d\d|\d\d-(?<y>\d{4})/), which Hermes doesn't support, so those regexes alone still go through @babel/plugin-transform-duplicate-named-capturing-groups-regex. Native groups also drop the _wrapRegExp helper, a RegExp subclass with a WeakMap lookup on every match.

The hermes-legacy and default profiles keep the transform, and the bug, which is in regexpu-core (still present in 6.5.3).

Test plan

  • New unit tests. The kitchen-sink snapshots for the Hermes V1 configurations change only in the named-group regex and the removed helper.
  • Hermes 260318099.0.4 passes test262's named groups, regex literal, exec, @@replace, @@matchAll, matchAll, replaceAll and match indices tests (413 run, no failures).
  • AI-driven: compiled the preset's hermes-stable output with hermesc and compared against Node 26 for \k<a>1 with and without u, a repeated-name regex, .groups and $<name> replacement - all match. Before this change, the u case failed to compile and the non-u case matched "x\t" instead of "xx1".

Changelog

[General][Fixed] - Babel preset: Hermes V1 profiles no longer lower named capturing groups, fixing named backreferences followed by a digit

Since #59580, a `u` regex with a named backreference directly followed by a digit fails Hermes compilation with `Invalid regular expression: Invalid escape`. `@babel/plugin-transform-named-capturing-groups-regex` rewrites `\k<name>` as a numbered backreference without guarding a following digit, so `/(?<a>.)\k<a>1/u` becomes `/(.)\11/u`, a reference to group 11. Without the `u` flag, `\11` reads as a legacy octal escape, so non-`u` regexes compile but silently match the wrong thing.

Hermes V1 supports named capturing groups natively, so the Hermes profiles stop lowering them, except for regexes that repeat a group name across alternatives, which Hermes doesn't support. Those still go through `@babel/plugin-transform-duplicate-named-capturing-groups-regex`. The `hermes-legacy` and `default` profiles keep the transform.

Changelog: [General][Fixed] - Babel preset: Hermes V1 profiles no longer lower named capturing groups, fixing named backreferences followed by a digit
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 29, 2026
@robhogan
robhogan marked this pull request as draft October 29, 2026 10:39
@github-actions

Copy link
Copy Markdown

Visual regression

13 visual difference(s) at 3ff60cc0130a. Visual differences need maintainer approval. Review the report, then apply visual-change-approved.

android button/color-red/default: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android button/default-styling/default: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android button/disabled/default: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android button/two-buttons-flexbox/default: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android switch/basic/default: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android switch/basic/toggled: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android switch/custom-colors/default: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android switch/custom-colors/toggled: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android switch/disabled/default: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android touchable/feedback-events/pressed: Changed, 995 px (0.220%)

diff

Expected and Captured
Expected Captured
expected captured

android touchable/without-feedback/default: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

android touchable/without-feedback/pressed: Size changed, 1080×1883 → 1080×1956

Expected and Captured
Expected Captured
expected captured

ios button/default-styling/default: Changed, 38223 px (1.533%)

diff

Expected and Captured
Expected Captured
expected captured

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant