Skip to content

fix: correctly prune key/each blocks #14403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fuzzy-lies-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: correctly prune each blocks
5 changes: 5 additions & 0 deletions .changeset/wicked-buses-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: correctly prune key blocks
128 changes: 57 additions & 71 deletions packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ function get_possible_element_siblings(node, adjacent_only) {
if (adjacent_only) {
break;
}
} else if (prev.type === 'EachBlock' || prev.type === 'IfBlock' || prev.type === 'AwaitBlock') {
} else if (is_block(prev)) {
const possible_last_child = get_possible_last_child(prev, adjacent_only);
add_to_map(possible_last_child, result);
if (adjacent_only && has_definite_elements(possible_last_child)) {
Expand All @@ -979,7 +979,7 @@ function get_possible_element_siblings(node, adjacent_only) {
while (
// @ts-expect-error TODO
(parent = parent?.parent) &&
(parent.type === 'EachBlock' || parent.type === 'IfBlock' || parent.type === 'AwaitBlock')
is_block(parent)
) {
const possible_siblings = get_possible_element_siblings(parent, adjacent_only);
add_to_map(possible_siblings, result);
Expand All @@ -1000,73 +1000,57 @@ function get_possible_element_siblings(node, adjacent_only) {
}

/**
* @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock} relative_selector
* @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock} node
* @param {boolean} adjacent_only
* @returns {Map<Compiler.AST.RegularElement, NodeExistsValue>}
*/
function get_possible_last_child(relative_selector, adjacent_only) {
function get_possible_last_child(node, adjacent_only) {
/** @typedef {Map<Compiler.AST.RegularElement, NodeExistsValue>} NodeMap */

/** @type {Array<Compiler.AST.Fragment | undefined | null>} */
let fragments = [];

switch (node.type) {
case 'EachBlock':
fragments.push(node.body, node.fallback);
break;

case 'IfBlock':
fragments.push(node.consequent, node.alternate);
break;

case 'AwaitBlock':
fragments.push(node.pending, node.then, node.catch);
break;

case 'KeyBlock':
fragments.push(node.fragment);
break;
}

/** @type {NodeMap} */
const result = new Map();
if (relative_selector.type === 'EachBlock') {
/** @type {NodeMap} */
const each_result = loop_child(relative_selector.body.nodes, adjacent_only);

/** @type {NodeMap} */
const else_result = relative_selector.fallback
? loop_child(relative_selector.fallback.nodes, adjacent_only)
: new Map();
const not_exhaustive = !has_definite_elements(else_result);
if (not_exhaustive) {
mark_as_probably(each_result);
mark_as_probably(else_result);
}
add_to_map(each_result, result);
add_to_map(else_result, result);
} else if (relative_selector.type === 'IfBlock') {
/** @type {NodeMap} */
const if_result = loop_child(relative_selector.consequent.nodes, adjacent_only);

/** @type {NodeMap} */
const else_result = relative_selector.alternate
? loop_child(relative_selector.alternate.nodes, adjacent_only)
: new Map();
const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result);
if (not_exhaustive) {
mark_as_probably(if_result);
mark_as_probably(else_result);

let exhaustive = true;

for (const fragment of fragments) {
if (fragment == null) {
exhaustive = false;
continue;
}
add_to_map(if_result, result);
add_to_map(else_result, result);
} else if (relative_selector.type === 'AwaitBlock') {
/** @type {NodeMap} */
const pending_result = relative_selector.pending
? loop_child(relative_selector.pending.nodes, adjacent_only)
: new Map();

/** @type {NodeMap} */
const then_result = relative_selector.then
? loop_child(relative_selector.then.nodes, adjacent_only)
: new Map();

/** @type {NodeMap} */
const catch_result = relative_selector.catch
? loop_child(relative_selector.catch.nodes, adjacent_only)
: new Map();
const not_exhaustive =
!has_definite_elements(pending_result) ||
!has_definite_elements(then_result) ||
!has_definite_elements(catch_result);
if (not_exhaustive) {
mark_as_probably(pending_result);
mark_as_probably(then_result);
mark_as_probably(catch_result);

const map = loop_child(fragment.nodes, adjacent_only);
exhaustive &&= has_definite_elements(map);

add_to_map(map, result);
}

if (!exhaustive) {
for (const key of result.keys()) {
result.set(key, NODE_PROBABLY_EXISTS);
}
add_to_map(pending_result, result);
add_to_map(then_result, result);
add_to_map(catch_result, result);
}

return result;
}

Expand Down Expand Up @@ -1107,13 +1091,6 @@ function higher_existence(exist1, exist2) {
return exist1 > exist2 ? exist1 : exist2;
}

/** @param {Map<Compiler.AST.RegularElement, NodeExistsValue>} result */
function mark_as_probably(result) {
for (const key of result.keys()) {
result.set(key, NODE_PROBABLY_EXISTS);
}
}

/**
* @param {Compiler.SvelteNode[]} children
* @param {boolean} adjacent_only
Expand All @@ -1132,11 +1109,7 @@ function loop_child(children, adjacent_only) {
if (adjacent_only) {
break;
}
} else if (
child.type === 'EachBlock' ||
child.type === 'IfBlock' ||
child.type === 'AwaitBlock'
) {
} else if (is_block(child)) {
const child_result = get_possible_last_child(child, adjacent_only);
add_to_map(child_result, result);
if (adjacent_only && has_definite_elements(child_result)) {
Expand All @@ -1147,3 +1120,16 @@ function loop_child(children, adjacent_only) {

return result;
}

/**
* @param {Compiler.SvelteNode} node
* @returns {node is Compiler.AST.IfBlock | Compiler.AST.EachBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock}
*/
function is_block(node) {
return (
node.type === 'IfBlock' ||
node.type === 'EachBlock' ||
node.type === 'AwaitBlock' ||
node.type === 'KeyBlock'
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { test } from '../../test';

export default test({
warnings: []
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="a"></div>

{#key x}
<div class="b"></div>
{/key}

<div class="c"></div>

<style>
.a ~ .b { color: green; }
.a ~ .c { color: green; }
.b ~ .c { color: green; }
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,38 @@ export default test({
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".a + .c"',
start: { character: 478, column: 1, line: 23 },
end: { character: 485, column: 8, line: 23 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".a + .g"',
start: { character: 505, column: 1, line: 24 },
end: { character: 512, column: 8, line: 24 }
start: { character: 586, column: 1, line: 27 },
end: { character: 593, column: 8, line: 27 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b + .e"',
start: { character: 532, column: 1, line: 25 },
end: { character: 539, column: 8, line: 25 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".c + .g"',
start: { character: 559, column: 1, line: 26 },
end: { character: 566, column: 8, line: 26 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".c + .k"',
start: { character: 586, column: 1, line: 27 },
end: { character: 593, column: 8, line: 27 }
start: { character: 611, column: 1, line: 28 },
end: { character: 618, column: 8, line: 28 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".d + .d"',
start: { character: 613, column: 1, line: 28 },
end: { character: 620, column: 8, line: 28 }
start: { character: 636, column: 1, line: 29 },
end: { character: 643, column: 8, line: 29 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".e + .f"',
start: { character: 640, column: 1, line: 29 },
end: { character: 647, column: 8, line: 29 }
start: { character: 661, column: 1, line: 30 },
end: { character: 668, column: 8, line: 30 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".f + .f"',
start: { character: 667, column: 1, line: 30 },
end: { character: 674, column: 8, line: 30 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".g + .j"',
start: { character: 694, column: 1, line: 31 },
end: { character: 701, column: 8, line: 31 }
start: { character: 686, column: 1, line: 31 },
end: { character: 693, column: 8, line: 31 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".g + .h + .i + .j"',
start: { character: 721, column: 1, line: 32 },
end: { character: 738, column: 18, line: 32 }
start: { character: 711, column: 1, line: 32 },
end: { character: 728, column: 18, line: 32 }
}
]
});
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@

.a.svelte-xyz + .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz + .f:where(.svelte-xyz) { color: green; }
.a.svelte-xyz + .g:where(.svelte-xyz) { color: green; }
.b.svelte-xyz + .c:where(.svelte-xyz) { color: green; }
.b.svelte-xyz + .d:where(.svelte-xyz) { color: green; }
.c.svelte-xyz + .e:where(.svelte-xyz) { color: green; }
.c.svelte-xyz + .f:where(.svelte-xyz) { color: green; }
.c.svelte-xyz + .g:where(.svelte-xyz) { color: green; }
.c.svelte-xyz + .k:where(.svelte-xyz) { color: green; }
.d.svelte-xyz + .e:where(.svelte-xyz) { color: green; }
.d.svelte-xyz + .f:where(.svelte-xyz) { color: green; }
.e.svelte-xyz + .e:where(.svelte-xyz) { color: green; }
.i.svelte-xyz + .j:where(.svelte-xyz) { color: green; }
.g.svelte-xyz + .h:where(.svelte-xyz) + .j:where(.svelte-xyz) { color: green; }
.g.svelte-xyz + .i:where(.svelte-xyz) + .j:where(.svelte-xyz) { color: green; }
.g.svelte-xyz + .j:where(.svelte-xyz) { color: green; }
.m.svelte-xyz + .m:where(.svelte-xyz) { color: green; }
.m.svelte-xyz + .l:where(.svelte-xyz) { color: green; }
.l.svelte-xyz + .m:where(.svelte-xyz) { color: green; }

/* no match */
/* (unused) .a + .c { color: green; }*/
/* (unused) .a + .g { color: green; }*/
/* (unused) .b + .e { color: green; }*/
/* (unused) .c + .g { color: green; }*/
/* (unused) .c + .k { color: green; }*/
/* (unused) .d + .d { color: green; }*/
/* (unused) .e + .f { color: green; }*/
/* (unused) .f + .f { color: green; }*/
/* (unused) .g + .j { color: green; }*/
/* (unused) .g + .h + .i + .j { color: green; }*/
/* (unused) .a + .c { color: red; }*/
/* (unused) .b + .e { color: red; }*/
/* (unused) .d + .d { color: red; }*/
/* (unused) .e + .f { color: red; }*/
/* (unused) .f + .f { color: red; }*/
/* (unused) .g + .h + .i + .j { color: red; }*/
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div class="a svelte-xyz"></div>
<div class="f svelte-xyz"></div>
<div class="k"></div>
<div class="k svelte-xyz"></div>
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,31 @@
<style>
.a + .e { color: green; }
.a + .f { color: green; }
.a + .g { color: green; }
.b + .c { color: green; }
.b + .d { color: green; }
.c + .e { color: green; }
.c + .f { color: green; }
.c + .g { color: green; }
.c + .k { color: green; }
.d + .e { color: green; }
.d + .f { color: green; }
.e + .e { color: green; }
.i + .j { color: green; }
.g + .h + .j { color: green; }
.g + .i + .j { color: green; }
.g + .j { color: green; }
.m + .m { color: green; }
.m + .l { color: green; }
.l + .m { color: green; }

/* no match */
.a + .c { color: green; }
.a + .g { color: green; }
.b + .e { color: green; }
.c + .g { color: green; }
.c + .k { color: green; }
.d + .d { color: green; }
.e + .f { color: green; }
.f + .f { color: green; }
.g + .j { color: green; }
.g + .h + .i + .j { color: green; }
.a + .c { color: red; }
.b + .e { color: red; }
.d + .d { color: red; }
.e + .f { color: red; }
.f + .f { color: red; }
.g + .h + .i + .j { color: red; }
</style>

<div class="a"></div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { test } from '../../test';

export default test({
warnings: [
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".a + .c"',
start: { character: 166, column: 1, line: 14 },
end: { character: 173, column: 8, line: 14 }
}
]
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

.a.svelte-xyz + .b:where(.svelte-xyz) { color: green; }
.b.svelte-xyz + .c:where(.svelte-xyz) { color: green; }

/* no match */
/* (unused) .a + .c { color: red; }*/
Loading
Loading