fix: properly traverse children when checking matches for `:has` (#13866)

The previous algorithm didn't take control flow blocks etc into account, this fixes that. Fixes #13860
pull/13863/head
Simon H 11 months ago committed by GitHub
parent 04c38b089f
commit b979c291e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: properly traverse children when checking matches for `:has`

@ -405,19 +405,28 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */ /** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */
const descendant_elements = []; const descendant_elements = [];
/** walk(
* @param {Compiler.SvelteNode} node /** @type {Compiler.SvelteNode} */ (element.fragment),
* @param {boolean} is_recursing { is_child: true },
*/ {
function collect_child_elements(node, is_recursing) { _(node, context) {
if (node.type === 'RegularElement' || node.type === 'SvelteElement') { if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
descendant_elements.push(node); descendant_elements.push(node);
if (!is_recursing) child_elements.push(node);
node.fragment.nodes.forEach((node) => collect_child_elements(node, true)); 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 // :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 // upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the

@ -6,112 +6,126 @@ export default test({
code: 'css_unused_selector', code: 'css_unused_selector',
message: 'Unused CSS selector ".unused:has(y)"', message: 'Unused CSS selector ".unused:has(y)"',
start: { start: {
line: 28, line: 31,
column: 1, column: 1,
character: 277 character: 308
}, },
end: { end: {
line: 28, line: 31,
column: 15, column: 15,
character: 291 character: 322
} }
}, },
{ {
code: 'css_unused_selector', code: 'css_unused_selector',
message: 'Unused CSS selector ".unused:has(:global(y))"', message: 'Unused CSS selector ".unused:has(:global(y))"',
start: { start: {
line: 31, line: 34,
column: 1, column: 1,
character: 312 character: 343
}, },
end: { end: {
line: 31, line: 34,
column: 24, column: 24,
character: 335 character: 366
} }
}, },
{ {
code: 'css_unused_selector', code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(.unused)"', message: 'Unused CSS selector "x:has(.unused)"',
start: { start: {
line: 34, line: 37,
column: 1, column: 1,
character: 356 character: 387
}, },
end: { end: {
line: 34, line: 37,
column: 15, column: 15,
character: 370 character: 401
} }
}, },
{ {
code: 'css_unused_selector', code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(y):has(.unused)"', message: 'Unused CSS selector "x:has(y):has(.unused)"',
start: { start: {
line: 47, line: 50,
column: 1, column: 1,
character: 525 character: 556
}, },
end: { end: {
line: 47, line: 50,
column: 22, column: 22,
character: 546 character: 577
} }
}, },
{ {
code: 'css_unused_selector', code: 'css_unused_selector',
message: 'Unused CSS selector ".unused"', message: 'Unused CSS selector ".unused"',
start: { start: {
line: 66, line: 69,
column: 2, column: 2,
character: 751 character: 782
}, },
end: { end: {
line: 66, line: 69,
column: 9, column: 9,
character: 758 character: 789
} }
}, },
{ {
code: 'css_unused_selector', code: 'css_unused_selector',
message: 'Unused CSS selector ".unused x:has(y)"', message: 'Unused CSS selector ".unused x:has(y)"',
start: { start: {
line: 82, line: 85,
column: 1, column: 1,
character: 905 character: 936
}, },
end: { end: {
line: 82, line: 85,
column: 17, column: 17,
character: 921 character: 952
} }
}, },
{ {
code: 'css_unused_selector', code: 'css_unused_selector',
message: 'Unused CSS selector ".unused:has(.unused)"', message: 'Unused CSS selector ".unused:has(.unused)"',
start: { start: {
line: 85, line: 88,
column: 1, column: 1,
character: 942 character: 973
}, },
end: { end: {
line: 85, line: 88,
column: 21, column: 21,
character: 962 character: 993
} }
}, },
{ {
code: 'css_unused_selector', code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(> z)"', message: 'Unused CSS selector "x:has(> z)"',
start: { 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, column: 1,
character: 1029 character: 1124
}, },
end: { end: {
line: 92, line: 101,
column: 11, column: 11,
character: 1039 character: 1134
} }
} }
] ]

@ -82,9 +82,15 @@
x.svelte-xyz:has(> y:where(.svelte-xyz)) { x.svelte-xyz:has(> y:where(.svelte-xyz)) {
color: green; color: green;
} }
y.svelte-xyz:has(> d:where(.svelte-xyz)) {
color: green;
}
/* (unused) x:has(> z) { /* (unused) x:has(> z) {
color: red; color: red;
}*/ }*/
/* (unused) x:has(> d) {
color: red;
}*/
x.svelte-xyz > y:where(.svelte-xyz):has(z:where(.svelte-xyz)) { x.svelte-xyz > y:where(.svelte-xyz):has(z:where(.svelte-xyz)) {
color: green; color: green;
} }

@ -1,6 +1,9 @@
<x> <x>
<y> <y>
<z></z> <z></z>
{#if foo}
<d></d>
{/if}
</y> </y>
</x> </x>
<c></c> <c></c>
@ -89,9 +92,15 @@
x:has(> y) { x:has(> y) {
color: green; color: green;
} }
y:has(> d) {
color: green;
}
x:has(> z) { x:has(> z) {
color: red; color: red;
} }
x:has(> d) {
color: red;
}
x > y:has(z) { x > y:has(z) {
color: green; color: green;
} }

Loading…
Cancel
Save