Skip to content

fix(grammar): accept a trailing escaped paren as a value ident in every dialect - #178

Merged
matthew-dean merged 1 commit into
devfrom
fix/escaped-trailing-paren-dispatch
Sep 8, 2026
Merged

fix(grammar): accept a trailing escaped paren as a value ident in every dialect#178
matthew-dean merged 1 commit into
devfrom
fix/escaped-trailing-paren-dispatch

Conversation

@matthew-dean

Copy link
Copy Markdown
Member

Problem

color: \( (and a\() is valid CSS — an escaped code point, i.e. a value identifier whose text happens to end in ( — but all four dialects rejected it:

value before after
color: \( ❌ all four ✅ all four
color: a\( ❌ all four ✅ all four
color: foo() / \41() / url(...) / calc(...) ✅ (unchanged)

The value ident/function dispatch routed on endsWith('('), which can't tell an escaped \( from a structural (, so the token \( was misread as a function opener and the parser demanded a ). (This is the trailing-\( carve-out left open on ledger G36 by #163.)

Fix — proper dispatch, no new matcher

Each grammar's value dispatch takes a more-specific arm before the generic one:

when(endsWith('\\('), <keyword arm>),   // \( and a\( → value ident
when(endsWith('('),   <call>),          // foo( → function

Dispatch picks the first matching arm, so the escaped-paren suffix wins. It's a plain-string endsWith on the fast path — no regex, no parseman change. Applied to the value ident/function dispatch in all four grammars (css has two ladders — typed and non-typed — kept in sync). Pseudo/query/header dispatches are not value-reachable and are untouched.

The one blind spot is \\( — an escaped backslash then a real paren, a function literally named \ — which isn't valid CSS and has no reachable input; a comment records it.

Verification

  • All four dialects accept \( / a\(; foo() / \41() / url() / calc() still route to their function arms. Output is byte-identical across all four (color: \(color: \(, Keyword preserved).
  • Parser suites: css 521, scss 633, less 747, jess 552 — all green.
  • Pinned in test/css-superset-corpus.ts ('escaped paren in a value identifier', 'escaped delimiter in a value identifier') across all four dialect gates; acceptance matrix 6/6; less golden green.
  • Closes G36's trailing-\( carve-out (now SETTLED).

Follow-up to #163; no parseman dependency change.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: dd8ef89c-8556-435f-be11-efc1c9c88dc7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…ry dialect

`color: \(` (and `a\(`) is an escaped code point — a value identifier
whose text ends in `(` — not a function opener. The value ident/function
dispatch routed on `endsWith('(')`, which cannot tell an escaped `\(`
from a structural `(`, so all four dialects wrongly demanded a `)` and
rejected valid CSS.

Fix with proper dispatch ordering, not a new matcher: each grammar's
value dispatch takes a more-specific `when(endsWith('\\('), <keyword arm>)`
before the generic `when(endsWith('('), <call>)`. `foo(`, `\41(`, `url(`,
`calc(` still route to their function/call arms; `\(` and `a\(` route to
the keyword arm and emit byte-identical output across all four dialects.

The only blind spot is `\\(` — an escaped backslash then a real paren, a
function literally named `\` — which is not valid CSS and has no
reachable input; a comment records it.

Closes the trailing-`\(` carve-out on ledger G36 (now SETTLED). Pins the
escaped-value story (`\;` and `\(`) in test/css-superset-corpus.ts across
all four dialect gates. No parseman change.
@matthew-dean
matthew-dean force-pushed the fix/escaped-trailing-paren-dispatch branch from 4e65f75 to d19c229 Compare September 8, 2026 13:30
@matthew-dean
matthew-dean merged commit 15375a7 into dev Sep 8, 2026
7 checks passed
@matthew-dean
matthew-dean deleted the fix/escaped-trailing-paren-dispatch branch September 8, 2026 13:36
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.

1 participant