Repository navigation
Drop nested rules that put a combinator after a pseudo-element - #20573
WeAreResilience wants to merge 1 commit into
Conversation
A functional utility with a nested child rule, such as `& > *`, used under a pseudo-element variant like `selection:` or `marker:` was flattened into `.x::selection > *`. A pseudo-element can only be followed by pseudo-classes, so that selector never matches and Lightning CSS warns about it while optimizing. When flattening a nested rule, drop each selector that puts a combinator after a pseudo-element, and drop the rule when none are left. Fixes #20571
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughNested selector processing now removes selectors where a combinator follows a pseudo-element. It filters selector lists individually and skips a rule if no selectors remain. Tests cover standard and legacy pseudo-elements, valid selector forms, selector lists, and generated utility output for pseudo-element variants. Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to
|
[Medium impact] The PR appears safe to merge; no actionable issues were found. Reviews (1) · Last reviewed commit: "Drop nested rules that put a combinator ..." · Reviewed by Greptile |
|
Thanks a lot for the PR and for tracking this down, @WeAreResilience. Your root cause analysis matches what we fixed: a combinator after a pseudo-element can never match. We merged the fix in #20575 instead of this PR. The core idea is the same. The implementation differs in these ways:
Your report and your test cases helped a lot. We close this PR because the merged change covers it. Sent by an AI assistant on behalf of @RobinMalfait. |
|
Thanks for the detailed breakdown. The |
…indlabs#20575) This PR fixes an issue where a utility with a nested rule, used with a pseudo-element variant, results in a selector that can never match anything. ```css @Utility bg-* { & > * { --nearest-bg: --value(--color-*); } } ``` With `selection:bg-red-500`, the nested rule was flattened to: ```css .selection\:bg-red-500::selection > * { --nearest-bg: var(--color-red-500); } ``` A pseudo-element can only be followed by pseudo-classes, so this never matches. Lightning CSS drops these rules, but warns about each of them on every build: ``` Pseudo-elements like '::before' or '::after' can't be followed by selectors like 'Delim('>')' ``` The same happens for `backdrop:` and `marker:`. For `marker:` the `::-webkit-details-marker > *` rule was even kept in the output. When we flatten nested rules in `handleNesting`, we now drop each selector that puts a combinator after a pseudo-element (e.g. `::selection`, `::part(…)`, vendor-prefixed ones like `::-webkit-details-marker`, and the legacy `:before`, `:after`, `:first-line` and `:first-letter`). This also covers `:is(…)` and `:where(…)` when all of their arguments are dead, which means that built-in utilities like `selection:space-x-4` no longer emit an empty `:where()` rule after optimization. If none of the selectors are left, then the declarations of the rule are dropped. Rules nested inside of it are still processed, because they can still match (e.g. `:is(&, .fallback)`). This also applies to rules inside hoistable at-rules such as `@media` and `@scope`. An `@scope` is only dropped when its `<scope-start>` selector can never match. Empty `@layer` rules are kept (without the dead selector), because they can establish the layer order. Things that are intentionally left alone: - Pseudo-classes after a pseudo-element (e.g. `.a::before:hover`). - Combinators before a pseudo-element (e.g. `.a > ::before`). - Top-level selectors written by the user (e.g. `.a::selection > * { … }`). - Unknown pseudo-elements, such as framework specific deep combinators like `::ng-deep` (Angular) and `::v-deep` (Vue 2). Another compiler rewrites them later on. - `before:` and `after:`. The parent rule has declarations (`content: var(--tw-content)`), so the nested rule stays nested as `& > *`. Browsers and Lightning CSS already drop it without a warning. I also moved the `isPseudoElement` helper from `canonicalize-candidates.ts` to `selector-parser.ts` so both places can use it. Fixes: tailwindlabs#20571 Closes tailwindlabs#20573 ## Test plan 1. Added tests to `ast.test.ts` for dropped selectors, partially dropped selector lists, selectors that must be kept, unknown pseudo-elements, and nested rules, `@scope` and `@layer` rules inside a dropped rule. 2. Added a test to `utilities.test.ts` for the issue's utility with `selection:`, `backdrop:` and `hover:`. 3. Existing tests still pass. 4. Built the issue's example and ran it through Lightning CSS: no warnings anymore.
Fixes #20571
Summary
A functional utility with a nested child rule, like
@utility bg-* { & > * { … } }, used under a pseudo-element variant (selection:,marker:,backdrop:) gets flattened inhandleNestinginto.selection\:bg-red-500::selection > *. A pseudo-element can only be followed by pseudo-classes, so that rule never matches, and Lightning CSS prints the "Pseudo-elements like '::before' or '::after' can't be followed by selectors like 'Delim('>')'" warning on every build.When a nested rule is flattened, each selector that ends up with a combinator right after a pseudo-element (
::x, functional ones like::part(x), and the legacy:before/:after/:first-line/:first-letter) is dropped. If none of the selectors are left, the rule is dropped. Pseudo-classes after a pseudo-element (.a::part(x):hover) and combinators before one (.a ::selection) are untouched, and user-authored top-level selectors are never rewritten.One case is not covered here: for
before:/after:the parent rule has declarations, so the child stays nested as& > *and Lightning CSS already drops it without a warning.Test plan
combinators after a pseudo-elementcases inast.test.ts, and aselection:/marker:/hover:case for the issue's utility inutilities.test.ts. 4 of them fail without the change; the two "keeps" cases pass on both and guard against over-matching.npx vitest runinpackages/tailwindcss: 5108 passed, 1 skipped.prettier --checkon the changed files.