From 9410ad0318587c338c539ca7cbca1ff9d61db6a0 Mon Sep 17 00:00:00 2001 From: 7nik Date: Wed, 29 Jan 2025 13:50:07 +0200 Subject: [PATCH] fix: correctly look for sibling elements inside blocks and components (#15106) Fixes #15027 and fixes #14995 When the target element is inside a block or component, get_following_sibling_elements was looking only for sibling elements after the block/component. This PR fixes it by starting the search from the begging of the block/component and skipping nodes that go before the target element. --- .changeset/curly-balloons-relate.md | 5 + .../phases/2-analyze/css/css-prune.js | 25 ++-- .../svelte/tests/css/samples/has/_config.js | 118 ++++++++++-------- .../svelte/tests/css/samples/has/expected.css | 10 ++ .../svelte/tests/css/samples/has/input.svelte | 12 ++ 5 files changed, 109 insertions(+), 61 deletions(-) create mode 100644 .changeset/curly-balloons-relate.md diff --git a/.changeset/curly-balloons-relate.md b/.changeset/curly-balloons-relate.md new file mode 100644 index 0000000000..69d13b8692 --- /dev/null +++ b/.changeset/curly-balloons-relate.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly look for sibling elements inside blocks and components when pruning CSS 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 ca7476ef7f..109010e88c 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 @@ -638,19 +638,30 @@ function get_following_sibling_elements(element, include_self) { /** @type {Array} */ const siblings = []; - // ...then walk them, starting from the node after the one - // containing the element in question + // ...then walk them, starting from the node containing the element in question + // skipping nodes that appears before the element const seen = new Set(); + let skip = true; /** @param {Compiler.AST.SvelteNode} node */ function get_siblings(node) { walk(node, null, { RegularElement(node) { - siblings.push(node); + if (node === element) { + skip = false; + if (include_self) siblings.push(node); + } else if (!skip) { + siblings.push(node); + } }, SvelteElement(node) { - siblings.push(node); + if (node === element) { + skip = false; + if (include_self) siblings.push(node); + } else if (!skip) { + siblings.push(node); + } }, RenderTag(node) { for (const snippet of node.metadata.snippets) { @@ -663,14 +674,10 @@ function get_following_sibling_elements(element, include_self) { }); } - for (const node of nodes.slice(nodes.indexOf(start) + 1)) { + for (const node of nodes.slice(nodes.indexOf(start))) { get_siblings(node); } - if (include_self) { - siblings.push(element); - } - return siblings; } diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js index e5dc5f3459..33bbe74949 100644 --- a/packages/svelte/tests/css/samples/has/_config.js +++ b/packages/svelte/tests/css/samples/has/_config.js @@ -6,182 +6,196 @@ export default test({ code: 'css_unused_selector', message: 'Unused CSS selector ".unused:has(y)"', start: { - line: 31, + line: 33, column: 1, - character: 308 + character: 330 }, end: { - line: 31, + line: 33, column: 15, - character: 322 + character: 344 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused:has(:global(y))"', start: { - line: 34, + line: 36, column: 1, - character: 343 + character: 365 }, end: { - line: 34, + line: 36, column: 24, - character: 366 + character: 388 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(.unused)"', start: { - line: 37, + line: 39, column: 1, - character: 387 + character: 409 }, end: { - line: 37, + line: 39, column: 15, - character: 401 + character: 423 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ":global(.foo):has(.unused)"', start: { - line: 40, + line: 42, column: 1, - character: 422 + character: 444 }, end: { - line: 40, + line: 42, column: 27, - character: 448 + character: 470 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(y):has(.unused)"', start: { - line: 50, + line: 52, column: 1, - character: 556 + character: 578 }, end: { - line: 50, + line: 52, column: 22, - character: 577 + character: 599 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused"', start: { - line: 69, + line: 71, column: 2, - character: 782 + character: 804 }, end: { - line: 69, + line: 71, column: 9, - character: 789 + character: 811 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused x:has(y)"', start: { - line: 85, + line: 87, column: 1, - character: 936 + character: 958 }, end: { - line: 85, + line: 87, column: 17, - character: 952 + character: 974 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ".unused:has(.unused)"', start: { - line: 88, + line: 90, column: 1, - character: 973 + character: 995 }, end: { - line: 88, + line: 90, column: 21, - character: 993 + character: 1015 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(> z)"', start: { - line: 98, + line: 100, column: 1, - character: 1093 + character: 1115 }, end: { - line: 98, + line: 100, column: 11, - character: 1103 + character: 1125 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(> d)"', start: { - line: 101, + line: 103, column: 1, - character: 1124 + character: 1146 }, end: { - line: 101, + line: 103, column: 11, - character: 1134 + character: 1156 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(~ y)"', start: { - line: 121, + line: 123, column: 1, - character: 1326 + character: 1348 }, end: { - line: 121, + line: 123, column: 11, - character: 1336 + character: 1358 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "f:has(~ d)"', + start: { + line: 133, + column: 1, + character: 1446 + }, + end: { + line: 133, + column: 11, + character: 1456 } }, { code: 'css_unused_selector', message: 'Unused CSS selector ":has(.unused)"', start: { - line: 129, + line: 141, column: 2, - character: 1409 + character: 1529 }, end: { - line: 129, + line: 141, column: 15, - character: 1422 + character: 1542 } }, { code: 'css_unused_selector', message: 'Unused CSS selector "&:has(.unused)"', start: { - line: 135, + line: 147, column: 2, - character: 1480 + character: 1600 }, end: { - line: 135, + line: 147, column: 16, - character: 1494 + character: 1614 } } ] diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css index 68d6aad68a..9627bf730c 100644 --- a/packages/svelte/tests/css/samples/has/expected.css +++ b/packages/svelte/tests/css/samples/has/expected.css @@ -112,6 +112,16 @@ color: red; }*/ + d.svelte-xyz:has(+ e:where(.svelte-xyz)) { + color: green; + } + d.svelte-xyz:has(~ f:where(.svelte-xyz)) { + color: green; + } + /* (unused) f:has(~ d) { + color: red; + }*/ + .foo { .svelte-xyz:has(x: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 3487b64e8c..946cf2df90 100644 --- a/packages/svelte/tests/css/samples/has/input.svelte +++ b/packages/svelte/tests/css/samples/has/input.svelte @@ -3,6 +3,8 @@ {#if foo} + + {/if} @@ -122,6 +124,16 @@ color: red; } + d:has(+ e) { + color: green; + } + d:has(~ f) { + color: green; + } + f:has(~ d) { + color: red; + } + :global(.foo) { :has(x) { color: green;