[babel-plugin] (edge case) Insert constant values verbatim, not as replacement patterns - #1872
Open
henryqdineen wants to merge 1 commit into
Open
henryqdineen wants to merge 1 commit into
henryqdineen wants to merge 1 commit into
Conversation
|
@henryqdineen is attempting to deploy a commit to the Meta Open Source Team on Vercel. A member of the Team first needs to authorize it. |
…tterns A `defineConsts` value was substituted with a string replacement, so `String.prototype.replace`/`replaceAll` expanded the `$` sequences in it: `$&` re-inserted the matched text, `$$` collapsed to a single `$`, and `` $` ``/`$'` inserted the text surrounding the match. Values are data, never replacement patterns. `$&` was the worst case. In the alias pre-collapse, replacing `var(--b)` with `$&` re-inserted `var(--b)` over itself, so the loop made no progress and -- having already cleared `visited` and reset `lastIndex` -- rescanned from the start forever. That hangs the build with no error; the circular reference guard can't catch it, because it detects a cycle within one recursive descent, not a substitution that fails to advance. Two sites needed it: the alias pre-collapse in resolveConstant, and the per-rule substitution. Switching both to function replacers, which are immune to `$` expansion, fixes them without changing anything else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
henryqdineen
force-pushed
the
hqd-fix-const-dollar-replacement
branch
from
September 10, 2026 18:36
8c1eb0e to
d2e3cd2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed / motivation ?
This came out of an agentic code review of #1700. The review flagged that constant values are substituted with a string replacement; checking the claim against
mainconfirmed it's a pre-existing bug, that it affects a second call site as well, and that in one case it hangs the build. It's independent of #1700, so it's split out here.This is an edge case: it only affects a
defineConstsvalue that contains a$. Nothing else changes. But within that narrow case the output is silently wrong, and one shape hangs the build outright — so it seems worth fixing even though most codebases will never hit it.The cause: values are substituted with a string replacement, so
String.prototype.replace/replaceAllexpand the$sequences inside them. A constant's value is data, never a replacement pattern. Per MDN — specifying a string as the replacement,$&inserts the matched substring,$$inserts a literal$, and$`/$'insert the portion before/after the match. Two call sites are affected — the alias pre-collapse inresolveConstant, and the per-rule substitution — and both switch to function replacers, which are not subject to that expansion. Two lines, no other behavior change.The plausible case:
$$in acontentstring. A price-range indicator, the familiar$/$$/$$$scale:Before, every tier past the first loses exactly one
$, because$$is the escape for a literal$:After:
So the UI renders
$,$,$$— "cheap" and "moderate" become visually identical and the whole scale shifts down one, with no error or warning. Any doubled dollar hits this: currency labels,contentstrings, tokens exported from a spreadsheet or design tool.The contrived case:
$&hangs the build. Replacingvar(--b)with$&re-insertsvar(--b)over itself, so the pre-collapse loop makes no progress and — having already clearedvisitedand resetlastIndex— rescans from the start forever. Thecircular reference detectedguard can't catch it: it detects a cycle within one recursive descent, not a substitution that fails to advance.These definitions alone hang the build, because the pre-collapse walks every entry in
constsMapwhether or not anything references it. No usage ofprefixis required:After this change that compiles to
.x11xwbpo{content:"$&"}. The alias chain needs a verbatim--key (#1460) for one const to name another; a plain$&value doesn't hang, but is still silently left unsubstituted.Linked PR/Issues
No tracking issue. Relates to #1460 (verbatim
--const keys, which make const→const aliasing possible) and #1700, where this was found — that PR rewrites the per-rule substitution loop and already uses function replacers there, so it independently fixes one of these two sites. Whichever lands second has a one-hunk conflict on thereplaceAllline, resolved by taking the newer side. TheresolveConstantfix doesn't conflict.Additional Context
Verified against unfixed
main(ee1d8a91): each of the three added tests fails, and the$&chain case hangs (killed by a 30s watchdog; the suite otherwise finishes in ~0.7s). The price-tier CSS above is real output from the transform on both sides.Note the
$®ression test hangs rather than fails if the fix regresses, since it's a synchronous loop Jest can't interrupt. The other two fail cleanly.Tests: three cases in
transform-process-test.js— a$&value reached through an alias chain (the hang), a$&value with no chain (silently unsubstituted by the per-rule site), and$$/$`/$'values preserved verbatim.Pre-flight checklist
🤖 Generated with Claude Code