Skip to content

Allow escaped characters in the class name - #1658

Open
samuil-banti-wpenigne wants to merge 6 commits into
MyIntervals:mainfrom
samuil-banti-wpenigne:enable-escaped-symbols-in-the-class-names
Open

samuil-banti-wpenigne wants to merge 6 commits into
MyIntervals:mainfrom
samuil-banti-wpenigne:enable-escaped-symbols-in-the-class-names

Conversation

@samuil-banti-wpenigne

Copy link
Copy Markdown
Contributor

Tailwind CSS v4 may contain square brackets in the class names i.e.

.\[\&
_\[data-piko-package\=review-card\]\]\:px-\[25px\] [data-piko-package=review-card]{pa
dding-inline:25px}

Other valid characters are also affected by the parser at this point:

{}[]~+"

So the goal of the PR is to enable parsing of such selectors.

@coveralls

coveralls commented Oct 22, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 75.45% (+0.02%) from 75.43% — samuil-banti-wpenigne:enable-escaped-symbols-in-the-class-names into MyIntervals:main

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The escape helper repeatedly copies a growing array, introducing quadratic overhead for long compound selectors.

1 open issue
What changed in this PR

This PR extends selector parsing to support escaped structural characters in class names, including Tailwind CSS v4 arbitrary variants.

Changes:

  • Apply escape detection before interpreting brackets, parentheses, braces, and selector separators.
  • Add regression tests for escaped characters, backslash parity, and selector boundaries.
  • Document the parsing improvement in the changelog.
File Description
tests/​Unit/​Property/​SelectorTest.php Test escaped selectors and component boundaries.
tests/​Unit/​Property/​Selector/​CompoundSelectorTest.php Test escaped structural characters and stop-character handling.
src/​Property/​Selector/​CompoundSelector.php Extend escape detection across structural characters.
CHANGELOG.md Record support for escaped selector characters.

Review effort: Balanced

Comment thread src/Property/Selector/CompoundSelector.php Outdated
samuil-banti-wpenigne and others added 2 commits October 22, 2026 14:38
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@JakeQZ JakeQZ left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @samuil-banti-wpenigne, this looks great, with excellent tests.

I've just spent several hours trying to decipher the CSS spec, which is as clear as mud (but more interesting than trying to patch up web servers ;)). Think I've got to the bottom of it. We're not handling the \r and \n cases correctly, as they cannot be escaped, but I think those should be left alone for now to be addressed in a future PR. Most other characters can be escaped and this change seems to address that very well.

I've made a couple of other minor suggested improvements.

Comment on lines 175 to 184
case ',':
// The fallthrough is intentional.
case ' ':
// The fallthrough is intentional.
case "\t":
// The fallthrough is intentional.
case "\n":
// The fallthrough is intentional.
case "\r":
// The fallthrough is intentional.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm not sure the handling of the whitespace cases was originally correct.

According to https://drafts.csswg.org/selectors/#case-sensitive which in turn references https://drafts.csswg.org/css2/#characters:

Any character (except a hexadecimal digit, linefeed, carriage return, or form feed) can be escaped with a backslash to remove its special meaning.

Outside a string, a backslash followed by a newline stands for itself (i.e., a DELIM followed by a newline).

There's no mention of spaces and tabs that I can find. I note one of your tests includes an escaped space, so I assume that's valid.

I think we should keep the handling of CR and LF as it is for now, and address that as a separate PR.

The handling of the comma (,) can be moved along with >, +, ~, as that seems obviously correct, along with space and tab which is presumably correct.

Comment on lines 107 to +108
$nextCharacter = $parserState->peek();
$nextCharacterIsEscaped = self::isCharacterEscaped($selectorParts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we can just pass \end($selectorParts) to the helper method, so it can just focus on a single string.

Also, I think it's more logical to have these two assignations in the other order, as $nextCharacterIsEscaped is looking at the last character(s) parsed, and $nextCharacter is looking at what is to come.

Comment on lines +295 to +300
private static function isCharacterEscaped(array $selectorParts): bool
{
$lastPart = $selectorParts[\count($selectorParts) - 1] ?? null;
if (!\is_string($lastPart)) {
return false;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Neat refactoring. But I think this could just be passed a string which is the last chunk parsed - see other comment.

@JakeQZ

JakeQZ commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

excellent tests.

Some may belong in the Functional rather than Unit division due to their dependency on other classes, but that can be tidied-up post-PR, as I think we have other misplaced tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants