diff --git a/.changeset/tasty-students-peel.md b/.changeset/tasty-students-peel.md new file mode 100644 index 0000000000..aa7c5472c5 --- /dev/null +++ b/.changeset/tasty-students-peel.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: properly traverse children when checking matches for `:has` 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 f5f7440085..f71a860958 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 @@ -405,19 +405,28 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element, /** @type {Array} */ const descendant_elements = []; - /** - * @param {Compiler.SvelteNode} node - * @param {boolean} is_recursing - */ - function collect_child_elements(node, is_recursing) { - if (node.type === 'RegularElement' || node.type === 'SvelteElement') { - descendant_elements.push(node); - if (!is_recursing) child_elements.push(node); - node.fragment.nodes.forEach((node) => collect_child_elements(node, true)); + walk( + /** @type {Compiler.SvelteNode} */ (element.fragment), + { is_child: true }, + { + _(node, context) { + if (node.type === 'RegularElement' || node.type === 'SvelteElement') { + descendant_elements.push(node); + + if (context.state.is_child) { + child_elements.push(node); + context.state.is_child = false; + context.next(); + context.state.is_child = true; + } else { + context.next(); + } + } else { + context.next(); + } + } } - } - - element.fragment.nodes.forEach((node) => collect_child_elements(node, false)); + ); // :has(...) is special in that it means "look downwards in the CSS tree". Since our matching algorithm goes // upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js index 815e48e096..675254c5b7 100644 --- a/packages/svelte/tests/css/samples/has/_config.js +++ b/packages/svelte/tests/css/samples/has/_config.js @@ -6,112 +6,126 @@ export default test({ code: 'css_unused_selector', message: 'Unused CSS selector ".unused:has(y)"', start: { - line: 28, + line: 31, column: 1, - character: 277 + character: 308 }, end: { - line: 28, + line: 31, column: 15, - character: 291 + character: 322 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused:has(:global(y))"', start: { - line: 31, + line: 34, column: 1, - character: 312 + character: 343 }, end: { - line: 31, + line: 34, column: 24, - character: 335 + character: 366 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(.unused)"', start: { - line: 34, + line: 37, column: 1, - character: 356 + character: 387 }, end: { - line: 34, + line: 37, column: 15, - character: 370 + character: 401 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(y):has(.unused)"', start: { - line: 47, + line: 50, column: 1, - character: 525 + character: 556 }, end: { - line: 47, + line: 50, column: 22, - character: 546 + character: 577 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused"', start: { - line: 66, + line: 69, column: 2, - character: 751 + character: 782 }, end: { - line: 66, + line: 69, column: 9, - character: 758 + character: 789 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused x:has(y)"', start: { - line: 82, + line: 85, column: 1, - character: 905 + character: 936 }, end: { - line: 82, + line: 85, column: 17, - character: 921 + character: 952 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused:has(.unused)"', start: { - line: 85, + line: 88, column: 1, - character: 942 + character: 973 }, end: { - line: 85, + line: 88, column: 21, - character: 962 + character: 993 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(> z)"', start: { - line: 92, + line: 98, + column: 1, + character: 1093 + }, + end: { + line: 98, + column: 11, + character: 1103 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "x:has(> d)"', + start: { + line: 101, column: 1, - character: 1029 + character: 1124 }, end: { - line: 92, + line: 101, column: 11, - character: 1039 + character: 1134 } } ] diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css index 215f7d667e..251a6952d5 100644 --- a/packages/svelte/tests/css/samples/has/expected.css +++ b/packages/svelte/tests/css/samples/has/expected.css @@ -82,9 +82,15 @@ x.svelte-xyz:has(> y:where(.svelte-xyz)) { color: green; } + y.svelte-xyz:has(> d:where(.svelte-xyz)) { + color: green; + } /* (unused) x:has(> z) { color: red; }*/ + /* (unused) x:has(> d) { + color: red; + }*/ x.svelte-xyz > y:where(.svelte-xyz):has(z:where(.svelte-xyz)) { color: green; } diff --git a/packages/svelte/tests/css/samples/has/input.svelte b/packages/svelte/tests/css/samples/has/input.svelte index 7cbebe27ec..7a62b69c0b 100644 --- a/packages/svelte/tests/css/samples/has/input.svelte +++ b/packages/svelte/tests/css/samples/has/input.svelte @@ -1,6 +1,9 @@ + {#if foo} + + {/if} @@ -89,9 +92,15 @@ x:has(> y) { color: green; } + y:has(> d) { + color: green; + } x:has(> z) { color: red; } + x:has(> d) { + color: red; + } x > y:has(z) { color: green; }