fix: mark `:has` selectors with multiple preceding selectors as used (#13750)

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.
pull/13742/head
Simon H 2 months ago committed by GitHub
parent fd78385447
commit a6e416da8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: mark `:has` selectors with multiple preceding selectors as used

@ -202,12 +202,17 @@ function apply_selector(relative_selectors, rule, element, stylesheet, check_has
const possible_match = relative_selector_might_apply_to_node( const possible_match = relative_selector_might_apply_to_node(
relative_selector, relative_selector,
parent_selectors,
rule, rule,
element, element,
stylesheet, stylesheet,
check_has check_has
); );
if (possible_match === 'definite_match') {
return true;
}
if (!possible_match) { if (!possible_match) {
return false; return false;
} }
@ -388,14 +393,16 @@ const regex_backslash_and_following_character = /\\(.)/g;
* Ensure that `element` satisfies each simple selector in `relative_selector` * Ensure that `element` satisfies each simple selector in `relative_selector`
* *
* @param {Compiler.Css.RelativeSelector} relative_selector * @param {Compiler.Css.RelativeSelector} relative_selector
* @param {Compiler.Css.RelativeSelector[]} parent_selectors
* @param {Compiler.Css.Rule} rule * @param {Compiler.Css.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
* @param {Compiler.Css.StyleSheet} stylesheet * @param {Compiler.Css.StyleSheet} stylesheet
* @param {boolean} check_has Whether or not to check the `:has(...)` selectors * @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( function relative_selector_might_apply_to_node(
relative_selector, relative_selector,
parent_selectors,
rule, rule,
element, element,
stylesheet, stylesheet,
@ -446,7 +453,7 @@ function relative_selector_might_apply_to_node(
apply_combinator( apply_combinator(
left_most_combinator, left_most_combinator,
selectors[0] ?? [], selectors[0] ?? [],
[relative_selector], [...parent_selectors, relative_selector],
rule, rule,
element, element,
stylesheet, stylesheet,
@ -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) { for (const selector of other_selectors) {

@ -85,3 +85,6 @@
/* (unused) x:has(> z) { /* (unused) x:has(> z) {
color: red; color: red;
}*/ }*/
x.svelte-xyz > y:where(.svelte-xyz):has(z:where(.svelte-xyz)) {
color: green;
}

@ -91,4 +91,7 @@
x:has(> z) { x:has(> z) {
color: red; color: red;
} }
x > y:has(z) {
color: green;
}
</style> </style>

Loading…
Cancel
Save