From 81f28ccf5d398647377efc8ef5435d09d217ddf9 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:35:37 +0200 Subject: [PATCH] fix: handle leading combinators within `:has(...)` (#13606) We need to ignore the leading combinator within a `:has(...)`, else it would not stop matching, assuming there are more parent selectors (which there aren't) and always fail. https://github.com/sveltejs/svelte/pull/13567#issuecomment-2412405131 --- .../src/compiler/phases/2-analyze/css/css-prune.js | 12 +++++++++++- packages/svelte/tests/css/samples/has/_config.js | 14 ++++++++++++++ packages/svelte/tests/css/samples/has/expected.css | 7 +++++++ packages/svelte/tests/css/samples/has/input.svelte | 7 +++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index c51de75fd6..d1bf7783e0 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -371,6 +371,16 @@ function relative_selector_might_apply_to_node( for (const complex_selector of complex_selectors) { const selectors = truncate(complex_selector); + const left_most_combinator = selectors[0]?.combinator ?? descendant_combinator; + // In .x:has(> y), we want to search for y, ignoring the left-most combinator + // (else it would try to walk further up and fail because there are no selectors left) + if (selectors.length > 0) { + selectors[0] = { + ...selectors[0], + combinator: null + }; + } + if ( selectors.length === 0 /* is :global(...) */ || apply_selector(selectors, rule, element, stylesheet, check_has) @@ -379,7 +389,7 @@ function relative_selector_might_apply_to_node( // and now looking upwards for the .x part. if ( apply_combinator( - selectors[0]?.combinator ?? descendant_combinator, + left_most_combinator, selectors[0] ?? [], [relative_selector], rule, diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js index 403a7a46c9..b590b2e50a 100644 --- a/packages/svelte/tests/css/samples/has/_config.js +++ b/packages/svelte/tests/css/samples/has/_config.js @@ -85,6 +85,20 @@ export default test({ column: 17, line: 81 } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "x:has(> z)"', + start: { + line: 88, + column: 1, + character: 968 + }, + end: { + line: 88, + column: 11, + character: 978 + } } ] }); diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css index 51824840f1..41f1ca7628 100644 --- a/packages/svelte/tests/css/samples/has/expected.css +++ b/packages/svelte/tests/css/samples/has/expected.css @@ -75,3 +75,10 @@ /* (unused) .unused x:has(y) { color: red; }*/ + + x.svelte-xyz:has(> y:where(.svelte-xyz)) { + color: green; + } + /* (unused) x:has(> z) { + color: red; + }*/ diff --git a/packages/svelte/tests/css/samples/has/input.svelte b/packages/svelte/tests/css/samples/has/input.svelte index 05064c6508..87bc9bb34c 100644 --- a/packages/svelte/tests/css/samples/has/input.svelte +++ b/packages/svelte/tests/css/samples/has/input.svelte @@ -81,4 +81,11 @@ .unused x:has(y) { color: red; } + + x:has(> y) { + color: green; + } + x:has(> z) { + color: red; + }