diff --git a/.changeset/moody-kids-brake.md b/.changeset/moody-kids-brake.md new file mode 100644 index 000000000000..664a700d8754 --- /dev/null +++ b/.changeset/moody-kids-brake.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: do not comment out unused selectors that are inside an unused selector diff --git a/packages/svelte/src/compiler/phases/3-transform/css/index.js b/packages/svelte/src/compiler/phases/3-transform/css/index.js index e1da4eca9679..decdb21ad716 100644 --- a/packages/svelte/src/compiler/phases/3-transform/css/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/css/index.js @@ -159,34 +159,37 @@ const visitors = { next(); }, SelectorList(node, { state, next, path }) { - let pruning = false; - let last = node.children[0].start; + // Only add comments if we're not inside a complex selector that itself is unused + if (!path.find((n) => n.type === 'ComplexSelector' && !n.metadata.used)) { + let pruning = false; + let last = node.children[0].start; - for (let i = 0; i < node.children.length; i += 1) { - const selector = node.children[i]; + for (let i = 0; i < node.children.length; i += 1) { + const selector = node.children[i]; - if (selector.metadata.used === pruning) { - if (pruning) { - let i = selector.start; - while (state.code.original[i] !== ',') i--; + if (selector.metadata.used === pruning) { + if (pruning) { + let i = selector.start; + while (state.code.original[i] !== ',') i--; - state.code.overwrite(i, i + 1, '*/'); - } else { - if (i === 0) { - state.code.prependRight(selector.start, '/* (unused) '); + state.code.overwrite(i, i + 1, '*/'); } else { - state.code.overwrite(last, selector.start, ' /* (unused) '); + if (i === 0) { + state.code.prependRight(selector.start, '/* (unused) '); + } else { + state.code.overwrite(last, selector.start, ' /* (unused) '); + } } + + pruning = !pruning; } - pruning = !pruning; + last = selector.end; } - last = selector.end; - } - - if (pruning) { - state.code.appendLeft(last, '*/'); + if (pruning) { + state.code.appendLeft(last, '*/'); + } } // if we're in a `:is(...)` or whatever, keep existing specificity bump state diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js index b590b2e50a44..68a1a35fe75c 100644 --- a/packages/svelte/tests/css/samples/has/_config.js +++ b/packages/svelte/tests/css/samples/has/_config.js @@ -86,18 +86,32 @@ export default test({ line: 81 } }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector ".unused:has(.unused)"', + start: { + line: 84, + column: 1, + character: 934 + }, + end: { + line: 84, + column: 21, + character: 954 + } + }, { code: 'css_unused_selector', message: 'Unused CSS selector "x:has(> z)"', start: { - line: 88, + line: 91, column: 1, - character: 968 + character: 1021 }, end: { - line: 88, + line: 91, column: 11, - character: 978 + character: 1031 } } ] diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css index 41f1ca76289b..a543916631fd 100644 --- a/packages/svelte/tests/css/samples/has/expected.css +++ b/packages/svelte/tests/css/samples/has/expected.css @@ -75,6 +75,9 @@ /* (unused) .unused x:has(y) { color: red; }*/ + /* (unused) .unused:has(.unused)*/ x.svelte-xyz:has(y:where(.svelte-xyz)) { + color: green; + } x.svelte-xyz:has(> y: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 87bc9bb34c6f..0a9644d2ae16 100644 --- a/packages/svelte/tests/css/samples/has/input.svelte +++ b/packages/svelte/tests/css/samples/has/input.svelte @@ -81,6 +81,9 @@ .unused x:has(y) { color: red; } + .unused:has(.unused), x:has(y) { + color: green; + } x:has(> y) { color: green;