Skip to content

Commit

Permalink
fix: mark :has selectors with multiple preceding selectors as used
Browse files Browse the repository at this point in the history
Fixes #13717

There are two parts to this:

1. the parent selectors weren't passed along for the check inside `:has`, which in case of a leading combinator would mean it would always count as unused
2. In case if a selector like `x > y:has(z)`, the prior logic would correctly determine that for element `z` there's a match for the `:has` selector, by first checking its contents and then walking up the tree. But after it did that, it would try to walk up the tree once more, which is a) wasteful b) buggy because the tree walking mechanism would no longer be adjusted for the `:has` special case, resulting in false negatives. To fix that, the `:has` will return a new value from the function, signaling that it already fully checked the upper selectors, and so the function calling it will skip doing that.
  • Loading branch information
dummdidumm committed Oct 21, 2024
1 parent 28c8d2b commit ab471da
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-zoos-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: mark `:has` selectors with multiple preceding selectors as used
14 changes: 11 additions & 3 deletions packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,17 @@ function apply_selector(relative_selectors, rule, element, stylesheet, check_has

const possible_match = relative_selector_might_apply_to_node(
relative_selector,
parent_selectors,
rule,
element,
stylesheet,
check_has
);

if (possible_match === 'definite_match') {
return true;
}

if (!possible_match) {
return false;
}
Expand Down Expand Up @@ -388,14 +393,16 @@ const regex_backslash_and_following_character = /\\(.)/g;
* Ensure that `element` satisfies each simple selector in `relative_selector`
*
* @param {Compiler.Css.RelativeSelector} relative_selector
* @param {Compiler.Css.RelativeSelector[]} parent_selectors
* @param {Compiler.Css.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
* @param {Compiler.Css.StyleSheet} stylesheet
* @param {boolean} check_has Whether or not to check the `:has(...)` selectors
* @returns {boolean}
* @returns {boolean | 'definite_match'}
*/
function relative_selector_might_apply_to_node(
relative_selector,
parent_selectors,
rule,
element,
stylesheet,
Expand Down Expand Up @@ -446,7 +453,7 @@ function relative_selector_might_apply_to_node(
apply_combinator(
left_most_combinator,
selectors[0] ?? [],
[relative_selector],
[...parent_selectors, relative_selector],
rule,
element,
stylesheet,
Expand Down Expand Up @@ -478,7 +485,8 @@ function relative_selector_might_apply_to_node(
}
}

return true;
// We return this to signal the parent "don't bother checking the rest of the selectors, I already did that"
return 'definite_match';
}

for (const selector of other_selectors) {
Expand Down
3 changes: 3 additions & 0 deletions packages/svelte/tests/css/samples/has/expected.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,6 @@
/* (unused) x:has(> z) {
color: red;
}*/
x.svelte-xyz > y:where(.svelte-xyz):has(z:where(.svelte-xyz)) {
color: green;
}
3 changes: 3 additions & 0 deletions packages/svelte/tests/css/samples/has/input.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,7 @@
x:has(> z) {
color: red;
}
x > y:has(z) {
color: green;
}
</style>

0 comments on commit ab471da

Please sign in to comment.