Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix incorrect selector transformation when a substring of a selector matches another local classname #1505

Merged
merged 1 commit into from
Nov 24, 2024

Conversation

askoufis
Copy link
Contributor

@askoufis askoufis commented Nov 20, 2024

Fixes #1501.

The bug

Depending on the hash of a scoped classname, a selector such as && would end up transformed into a selector that was not scoped to the classname represented by &, which would cause an invalid selector error.

An example from the test case I added involves the local classnames debugName_hash1 and debugName_hash1d. The && selector would become debugName_hash1debugName_hash1, which contains debugName_hash1d as a substring.

When this occurs, the local classname search returns 3 results of the form:

[
  [14, ['debugName_hash1']],
  [15, ['debugName_hash1d']],
  [29, ['debugName_hash1']],
]

Note that we iterate over these search results in reverse order. In the above case, the replacement for the middle element would overlap with characters already replaced by the last element, which causes the bug.

It might not be obvious as to when this specific set of classnames would ever be generated. That is, a classname that both contains a substring equal to another classnae, and both starts and ends with the same character.

As mentioned in #1501, the refCount appended to a classname hash increments by 1 for every classname generated in a file. This value is encoded as a base 36 value, so it goes from 0-9a-z, then 1(0-9a-z), etc.

This means that once you roll over to the a-z range, you'll eventually end up with a classname that satisfies both of the constraints mentioned above. Ideally you wouldn't need to write a selector like && as increasing specificity shouldn't be much of an issue if you're using scoped classnames, but regardless this shouldn't break selector transformation.

The Fix

The previous logic of checking startIndex >= lastReplaceIndex didn't cover this case. By ensuring that we only ever do a replacement when lastReplaceIndex > endIndex, the replacement will only affect characters that have not already been replaced. We actually want to negate this logic as we continue when we don't want to replace. This is how we end up with lastReplaceIndex <= endIndex as the skip condition.

To test out this fix, install @vanilla-extract/css@fix-buggy-selector-transform.

Copy link

changeset-bot bot commented Nov 20, 2024

🦋 Changeset detected

Latest commit: 4329b36

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@vanilla-extract/css Patch
@vanilla-extract/integration Patch
@vanilla-extract/rollup-plugin Patch
@vanilla-extract/esbuild-plugin Patch
@vanilla-extract/jest-transform Patch
@vanilla-extract/parcel-transformer Patch
@vanilla-extract/vite-plugin Patch
@vanilla-extract/webpack-plugin Patch
@vanilla-extract/next-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@askoufis askoufis merged commit 103ce57 into master Nov 24, 2024
12 checks passed
@askoufis askoufis deleted the fix-buggy-selector-transformation branch November 24, 2024 05:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants