Skip to content

fix(ecmascript): support $ substitution patterns in String.prototype.replace/replaceAll - #991

Open
chiliec wants to merge 1 commit into
trynova:mainfrom
chiliec:fix/string-replace-substitution-patterns
Open

fix(ecmascript): support $ substitution patterns in String.prototype.replace/replaceAll#991
chiliec wants to merge 1 commit into
trynova:mainfrom
chiliec:fix/string-replace-substitution-patterns

Conversation

@chiliec

@chiliec chiliec commented Sep 6, 2026

Copy link
Copy Markdown

What

Closes #938.

String.prototype.replace and String.prototype.replaceAll with a non-callable replaceValue ignored the $ replacement patterns. They did a plain substring replacement, so "abc".replace("b", "[$&]") returned a[$&]c instead of a[b]c.

Fix

Route the string-replacement paths (functionalReplace = false) through the existing get_substitution (GetSubstitution, spec 22.1.3.19.1), which handles $$, $&, $`, $' and $n.

Doing that surfaced two latent bugs in get_substitution itself, both fixed here:

  • The fall-through branch (template char that isn't a $ pattern) set ref to the entire remaining template and consumed it in one step, so any $ pattern occurring after a literal prefix (e.g. [$&], a($1$1)) was never substituted. It now advances one code point at a time.
  • The $' branch computed tailPos = position + matchLength using the UTF-16 position but then indexed the UTF-8 str, panicking on non-ASCII input ('Ninguém'.replaceAll('é', "($'")). It now uses the UTF-8 offset consistently, matching the sibling $` branch.

get_substitution is pure (no regex), so its #[cfg(feature = "regexp")] gate is removed and get/Scoped moved to the always-available imports.

Tests

test262. This flips 17 tests from FAIL→PASS with no regressions across the entire built-ins/String and built-ins/RegExp trees:

  • String/prototype/replaceAll/getSubstitution-0x0024* (5)
  • String/prototype/replace/regexp-capture-by-index
  • RegExp/prototype/Symbol.replace/{subst-after,subst-before,subst-matched,subst-capture-idx-1,subst-capture-idx-2,result-coerce-*} (9)
  • RegExp/S15.10.2.8_A3_T18, RegExp/named-groups/string-replace-nocaptures

The RegExp Symbol.replace tests flip because they share the same get_substitution.

Validation (real commands, real results)

# RED→GREEN: revert only the source, keep the updated expectations → 17 flipped tests fail; restore → pass.

$ cargo build -p nova_cli -j 1
$ cargo build --bin test262 -j 1

# full regression sweep (no --update): runner exits 0 = no unexpected results
$ ./target/debug/test262 --noprogress built-ins/String built-ins/RegExp
Found (found) 2134 pass / 329 fail over 2465 tests, 0 unexpected results  # runner exit 0

$ cargo fmt --check      # clean
$ cargo clippy -p nova_vm # clean, no warnings

# behavioural repro
$ echo 'print("abcabc".replace("b", "[$&]")); print("abc".replace("b", "$$"));' | nova_cli eval
a[b]cabc
a$c

expectations.json and metrics.json are updated to reflect the newly-passing tests. I ran the built-ins/String and built-ins/RegExp subtrees rather than the whole suite locally (host RAM limits); metrics.json totals were adjusted by the exact +17 pass / −17 fail delta — the full-suite metrics are regenerated by CI anyway.

Happy to adjust anything.

…replace/replaceAll

String.prototype.replace and replaceAll with a non-callable replaceValue
did a plain substring replacement, ignoring the $$, $&, $`, $' and $n
replacement patterns required by GetSubstitution (22.1.3.19.1). Route the
string-replacement paths through get_substitution, and fix two latent bugs
in get_substitution itself: the fall-through branch consumed the whole
remaining template at once (so patterns after a literal prefix were never
substituted), and the $' branch mixed a UTF-16 position with UTF-8 byte
lengths, panicking on non-ASCII input.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

String.prototype.replace does not work with replacement patterns

1 participant