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>} */
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

@ -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
}
}
]

@ -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;
}

@ -1,6 +1,9 @@
<x>
<y>
<z></z>
{#if foo}
<d></d>
{/if}
</y>
</x>
<c></c>
@ -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;
}

Loading…
Cancel
Save