From 522557559f22b47cbe2b1c782e5a363b72ea146a Mon Sep 17 00:00:00 2001 From: 7nik Date: Thu, 30 Jan 2025 16:51:25 +0200 Subject: [PATCH] fix: do not prune selectors like `:global(.foo):has(.scoped)` (#15140) Fixes #14910 The issue occurs only when :has() targets at a component's root element and because include_self is false. I came to the conclusion that this is the same case as :root:has(.scoped). --- .changeset/famous-bulldogs-tan.md | 5 +++++ .../src/compiler/phases/2-analyze/css/css-prune.js | 9 +++++++-- 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 +++++++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .changeset/famous-bulldogs-tan.md diff --git a/.changeset/famous-bulldogs-tan.md b/.changeset/famous-bulldogs-tan.md new file mode 100644 index 000000000000..a5cc14b7f2da --- /dev/null +++ b/.changeset/famous-bulldogs-tan.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: do not prune selectors like `:global(.foo):has(.scoped)` 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 109010e88c4a..e719895798e1 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 @@ -339,13 +339,18 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element) let sibling_elements; // do them lazy because it's rarely used and expensive to calculate // If this is a :has inside a global selector, we gotta include the element itself, too, - // because the global selector might be for an element that's outside the component (e.g. :root). + // because the global selector might be for an element that's outside the component, + // e.g. :root:has(.scoped), :global(.foo):has(.scoped), or :root { &:has(.scoped) {} } const rules = get_parent_rules(rule); const include_self = rules.some((r) => r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))) || rules[rules.length - 1].prelude.children.some((c) => c.children.some((r) => - r.selectors.some((s) => s.type === 'PseudoClassSelector' && s.name === 'root') + r.selectors.some( + (s) => + s.type === 'PseudoClassSelector' && + (s.name === 'root' || (s.name === 'global' && s.args)) + ) ) ); if (include_self) { diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js index 33bbe74949a3..8d89d98cbdb0 100644 --- a/packages/svelte/tests/css/samples/has/_config.js +++ b/packages/svelte/tests/css/samples/has/_config.js @@ -197,6 +197,20 @@ export default test({ column: 16, character: 1614 } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector ":global(.foo):has(.unused)"', + start: { + line: 155, + column: 1, + character: 1684 + }, + end: { + line: 155, + column: 27, + character: 1710 + } } ] }); diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css index 9627bf730cbe..b257370d61f3 100644 --- a/packages/svelte/tests/css/samples/has/expected.css +++ b/packages/svelte/tests/css/samples/has/expected.css @@ -136,3 +136,10 @@ color: red; }*/ } + + .foo:has(x.svelte-xyz) { + color: green; + } + /* (unused) :global(.foo):has(.unused) { + color: red; + }*/ diff --git a/packages/svelte/tests/css/samples/has/input.svelte b/packages/svelte/tests/css/samples/has/input.svelte index 946cf2df90a2..9b254996bf30 100644 --- a/packages/svelte/tests/css/samples/has/input.svelte +++ b/packages/svelte/tests/css/samples/has/input.svelte @@ -148,4 +148,11 @@ color: red; } } + + :global(.foo):has(x) { + color: green; + } + :global(.foo):has(.unused) { + color: red; + }