Repository navigation
Drop nested rules that put a combinator after a pseudo-element - #20575
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughThe selector parser now provides shared pseudo-element detection. Nesting handling uses selector analysis to remove selectors that cannot match after nesting substitution and skips declarations under dead selector entries. It continues to process matchable nested rules. Tests cover invalid and valid selector combinations, including pseudo-element variants applied to utilities with nested rules. Priority: ⬇️ Low Severity of issue fixed: Low Merge Risk: ⚪ Minimal · up to
|
[Medium impact] The PR appears safe to merge; no new actionable issues were found. Reviews (4) · Last reviewed commit: "Keep empty `@layer` rules inside a rule ..." · Reviewed by Greptile |
So that it can be used outside of canonicalization as well.
A utility with a nested rule like `& > *`, used with a pseudo-element variant like `selection:`, was flattened to `.x::selection > *`. A pseudo-element can only be followed by pseudo-classes, so this selector never matches anything, and Lightning CSS warns about it on every build. When flattening nested rules, we now drop the selectors that put a combinator after a pseudo-element. This also covers selectors wrapped in `:is(…)` or `:where(…)` when all of their arguments are dead (e.g. `selection:space-x-4`). If no selectors are left, the rule is dropped. Fixes: #20571
The initial fix dropped every flattened selector that puts a combinator after a pseudo-element, together with all of its nested rules. That is too aggressive in two cases: - Unknown pseudo-elements. Some of them are not real pseudo-elements, such as the framework specific `::ng-deep` (Angular) and `::v-deep` (Vue 2), which are rewritten by another compiler later on. Now only standard pseudo-elements are dead before a combinator, including the legacy single-colon ones and vendor-prefixed ones (e.g. `::-webkit-details-marker`). This way we don't have to track third party frameworks. - Nested rules of a rule that can never match. Its declarations are dropped, but nested rules can still match something, e.g. `:is(&, .fallback)` still matches `.fallback`, so keep walking them.
cb828e7 to
20ba773
Compare
…e can still match A user-authored `<scope-start>` like `:is(&, .fallback)` and a variant `@scope` with a live selector inside can still match, even when the parent rule can't. Only drop the `@scope` when its resolved `<scope-start>` can never match.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
45c7ae22-4a6d-441a-ab08-1c34b0f524ea
📒 Files selected for processing: 2
packages/tailwindcss/src/ast.test.tspackages/tailwindcss/src/ast.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.
An empty `@layer` can establish the layer order, so dropping it can change the cascade of other rules. Emit it without the selector of the dead rule. The `@layer foo;` line moves out of the "drop at-rules" test on purpose, because that behavior changes: it now has its own test.
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.
With
selection:bg-red-500, the nested rule was flattened to: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:
The same happens for
backdrop:andmarker:. Formarker: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-lineand:first-letter). This also covers:is(…)and:where(…)when all of their arguments are dead, which means that built-in utilities likeselection:space-x-4no 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@mediaand@scope. An@scopeis only dropped when its<scope-start>selector can never match. Empty@layerrules are kept (without the dead selector), because they can establish the layer order.Things that are intentionally left alone:
.a::before:hover)..a > ::before)..a::selection > * { … }).::ng-deep(Angular) and::v-deep(Vue 2). Another compiler rewrites them later on.before:andafter:. 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
isPseudoElementhelper fromcanonicalize-candidates.tstoselector-parser.tsso both places can use it.Fixes: #20571
Closes #20573
Test plan
ast.test.tsfor dropped selectors, partially dropped selector lists, selectors that must be kept, unknown pseudo-elements, and nested rules,@scopeand@layerrules inside a dropped rule.utilities.test.tsfor the issue's utility withselection:,backdrop:andhover:.