Skip to content

fix: handle @scope at-rule edge cases#91

Open
merklebonsai wants to merge 2 commits intocss-modules:masterfrom
merklebonsai:fix/scope-params-tokenizer
Open

fix: handle @scope at-rule edge cases#91
merklebonsai wants to merge 2 commits intocss-modules:masterfrom
merklebonsai:fix/scope-params-tokenizer

Conversation

@merklebonsai
Copy link
Copy Markdown

@merklebonsai merklebonsai commented Apr 29, 2026

Two related fixes for the @scope at-rule handler. Two atomic commits.

Commit 1 — fix: parse @scope params with paren-aware tokenizer

Fixes #90.

The current @scope handler splits atRule.params on the substring "to":

atRule.params.split("to")

This naively matches to anywhere in the params, including inside class names (.button contains bu + to + n; same goes for .tooltip, .toolbar, .photo, .story, .into-view, etc.) and inside attribute selector values ([data-section="footer"], [role="button"]). The result is malformed CSS with extra to () clauses and class names truncated mid-token, that never match the DOM.

The fix replaces .split("to") with parseScopeParams — a small character-by-character tokenizer that walks the params with paren-depth and string-literal tracking. The to keyword is detected only at depth 0, between two parenthesized groups, with a word boundary check so identifiers starting with to (like tools, topology) don't match.

The same tokenizer also handles two adjacent legal-CSS edge cases that the old split("to") would have mishandled regardless of the substring issue:

  • CSS comments inside params (/* something with parens */)
  • CSS identifier escapes (\(, \)) — legal per the CSS spec, occasionally emitted by CSS-in-JS pipelines

When params can't be parsed, the plugin now emits result.warn(...) via the postcss API so module-name leakage is diagnosable rather than silent.

Commit 2 — fix: prevent crash on body-less @scope at-rule

The @scope branch in the at-rule walker calls atRule.nodes.forEach(...) unconditionally. When postcss parses an
@scope at-rule with no body (e.g. @scope (.foo);), atRule.nodes is undefined and .forEach throws Cannot read properties of undefined (reading 'forEach').

The non-scope at-rule branch already handles this with else if (atRule.nodes). This commit adds the same guard to the @scope branch.

Why we hit this

We're shipping a visual app builder that emits CSS with @scope rules where component-boundary markers can land in any of the params positions, and component names are user-supplied. Most names hit at least one of the failure modes (any class containing the substring to). Without these fixes, next build — which uses postcss-modules through webpack by default — silently produces broken styles in our generated output. Our codegen needs arbitrary user input to round-trip correctly through every CSS Modules mangler in the ecosystem.

Test plan

  • All 249 existing tests pass unchanged.
  • 9 new tests across both commits:
    • Class name contains to substring (canonical Bug in handling @scope rule - "to" as a part of a selector #90 case)
    • Multiple classes with to in scope-end
    • Attribute selector value contains to
    • Bare class with to inside but no scope-end clause
    • Uppercase TO keyword (CSS keywords are case-insensitive)
    • Nested :is() / :not() selectors (parser depth correctness)
    • CSS comment containing to and unbalanced parens
    • Escaped \( in identifier
    • Body-less @scope no longer crashes
  • Total: 258 passing.
  • yarn lint (eslint + prettier) clean.

@merklebonsai
Copy link
Copy Markdown
Author

Under honest disclosure - this PR is produced together with Claude. Yet, this is not a slop; more of an opposite actually. I've specifically ran Claude to identify edge cases I'm missing (e.g. comments that include ( or ) - he found several genuine ones I did not think about). I'm using @scope in automated codegen scenarios and needed to be sure it will not break under any edge cases.

Comment thread src/index.js
}

return { start, end };
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it AI generated code? because I see a lot of rooms to improve

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ask Claude to simplify it and make faster, he knows how to do it

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.

Bug in handling @scope rule - "to" as a part of a selector

2 participants