Skip to content

Commit b979c29

Browse files
authored
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
1 parent 04c38b0 commit b979c29

File tree

5 files changed

+87
-44
lines changed

5 files changed

+87
-44
lines changed

.changeset/tasty-students-peel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: properly traverse children when checking matches for `:has`

packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -405,19 +405,28 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
405405
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */
406406
const descendant_elements = [];
407407

408-
/**
409-
* @param {Compiler.SvelteNode} node
410-
* @param {boolean} is_recursing
411-
*/
412-
function collect_child_elements(node, is_recursing) {
413-
if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
414-
descendant_elements.push(node);
415-
if (!is_recursing) child_elements.push(node);
416-
node.fragment.nodes.forEach((node) => collect_child_elements(node, true));
408+
walk(
409+
/** @type {Compiler.SvelteNode} */ (element.fragment),
410+
{ is_child: true },
411+
{
412+
_(node, context) {
413+
if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
414+
descendant_elements.push(node);
415+
416+
if (context.state.is_child) {
417+
child_elements.push(node);
418+
context.state.is_child = false;
419+
context.next();
420+
context.state.is_child = true;
421+
} else {
422+
context.next();
423+
}
424+
} else {
425+
context.next();
426+
}
427+
}
417428
}
418-
}
419-
420-
element.fragment.nodes.forEach((node) => collect_child_elements(node, false));
429+
);
421430

422431
// :has(...) is special in that it means "look downwards in the CSS tree". Since our matching algorithm goes
423432
// upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the

packages/svelte/tests/css/samples/has/_config.js

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,112 +6,126 @@ export default test({
66
code: 'css_unused_selector',
77
message: 'Unused CSS selector ".unused:has(y)"',
88
start: {
9-
line: 28,
9+
line: 31,
1010
column: 1,
11-
character: 277
11+
character: 308
1212
},
1313
end: {
14-
line: 28,
14+
line: 31,
1515
column: 15,
16-
character: 291
16+
character: 322
1717
}
1818
},
1919
{
2020
code: 'css_unused_selector',
2121
message: 'Unused CSS selector ".unused:has(:global(y))"',
2222
start: {
23-
line: 31,
23+
line: 34,
2424
column: 1,
25-
character: 312
25+
character: 343
2626
},
2727
end: {
28-
line: 31,
28+
line: 34,
2929
column: 24,
30-
character: 335
30+
character: 366
3131
}
3232
},
3333
{
3434
code: 'css_unused_selector',
3535
message: 'Unused CSS selector "x:has(.unused)"',
3636
start: {
37-
line: 34,
37+
line: 37,
3838
column: 1,
39-
character: 356
39+
character: 387
4040
},
4141
end: {
42-
line: 34,
42+
line: 37,
4343
column: 15,
44-
character: 370
44+
character: 401
4545
}
4646
},
4747
{
4848
code: 'css_unused_selector',
4949
message: 'Unused CSS selector "x:has(y):has(.unused)"',
5050
start: {
51-
line: 47,
51+
line: 50,
5252
column: 1,
53-
character: 525
53+
character: 556
5454
},
5555
end: {
56-
line: 47,
56+
line: 50,
5757
column: 22,
58-
character: 546
58+
character: 577
5959
}
6060
},
6161
{
6262
code: 'css_unused_selector',
6363
message: 'Unused CSS selector ".unused"',
6464
start: {
65-
line: 66,
65+
line: 69,
6666
column: 2,
67-
character: 751
67+
character: 782
6868
},
6969
end: {
70-
line: 66,
70+
line: 69,
7171
column: 9,
72-
character: 758
72+
character: 789
7373
}
7474
},
7575
{
7676
code: 'css_unused_selector',
7777
message: 'Unused CSS selector ".unused x:has(y)"',
7878
start: {
79-
line: 82,
79+
line: 85,
8080
column: 1,
81-
character: 905
81+
character: 936
8282
},
8383
end: {
84-
line: 82,
84+
line: 85,
8585
column: 17,
86-
character: 921
86+
character: 952
8787
}
8888
},
8989
{
9090
code: 'css_unused_selector',
9191
message: 'Unused CSS selector ".unused:has(.unused)"',
9292
start: {
93-
line: 85,
93+
line: 88,
9494
column: 1,
95-
character: 942
95+
character: 973
9696
},
9797
end: {
98-
line: 85,
98+
line: 88,
9999
column: 21,
100-
character: 962
100+
character: 993
101101
}
102102
},
103103
{
104104
code: 'css_unused_selector',
105105
message: 'Unused CSS selector "x:has(> z)"',
106106
start: {
107-
line: 92,
107+
line: 98,
108+
column: 1,
109+
character: 1093
110+
},
111+
end: {
112+
line: 98,
113+
column: 11,
114+
character: 1103
115+
}
116+
},
117+
{
118+
code: 'css_unused_selector',
119+
message: 'Unused CSS selector "x:has(> d)"',
120+
start: {
121+
line: 101,
108122
column: 1,
109-
character: 1029
123+
character: 1124
110124
},
111125
end: {
112-
line: 92,
126+
line: 101,
113127
column: 11,
114-
character: 1039
128+
character: 1134
115129
}
116130
}
117131
]

packages/svelte/tests/css/samples/has/expected.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,15 @@
8282
x.svelte-xyz:has(> y:where(.svelte-xyz)) {
8383
color: green;
8484
}
85+
y.svelte-xyz:has(> d:where(.svelte-xyz)) {
86+
color: green;
87+
}
8588
/* (unused) x:has(> z) {
8689
color: red;
8790
}*/
91+
/* (unused) x:has(> d) {
92+
color: red;
93+
}*/
8894
x.svelte-xyz > y:where(.svelte-xyz):has(z:where(.svelte-xyz)) {
8995
color: green;
9096
}

packages/svelte/tests/css/samples/has/input.svelte

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<x>
22
<y>
33
<z></z>
4+
{#if foo}
5+
<d></d>
6+
{/if}
47
</y>
58
</x>
69
<c></c>
@@ -89,9 +92,15 @@
8992
x:has(> y) {
9093
color: green;
9194
}
95+
y:has(> d) {
96+
color: green;
97+
}
9298
x:has(> z) {
9399
color: red;
94100
}
101+
x:has(> d) {
102+
color: red;
103+
}
95104
x > y:has(z) {
96105
color: green;
97106
}

0 commit comments

Comments
 (0)