From 19d80ad63c545bff2b84d29563cfab8e63b5bf68 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 26 Nov 2024 22:30:21 +0100 Subject: [PATCH] fix: css pruning producing invalid css (#14448) * fix: css pruning producing invalid css * Update .changeset/big-hats-wonder.md --------- Co-authored-by: Rich Harris --- .changeset/big-hats-wonder.md | 5 + .../compiler/phases/3-transform/css/index.js | 18 +- .../svelte/tests/css/samples/has/expected.css | 2 +- .../unused-selector-in-between/expected.css | 2 +- .../unused-selector-leading/expected.css | 2 +- .../unused-selector-multiple/_config.js | 160 ++++++++++++++++++ .../unused-selector-multiple/expected.css | 15 ++ .../unused-selector-multiple/input.svelte | 20 +++ 8 files changed, 211 insertions(+), 13 deletions(-) create mode 100644 .changeset/big-hats-wonder.md create mode 100644 packages/svelte/tests/css/samples/unused-selector-multiple/_config.js create mode 100644 packages/svelte/tests/css/samples/unused-selector-multiple/expected.css create mode 100644 packages/svelte/tests/css/samples/unused-selector-multiple/input.svelte diff --git a/.changeset/big-hats-wonder.md b/.changeset/big-hats-wonder.md new file mode 100644 index 0000000000..bc2e352ffb --- /dev/null +++ b/.changeset/big-hats-wonder.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly remove unused selectors in middle of selector lists 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 d1f3d3baa6..c1175e4b5b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/css/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/css/index.js @@ -200,6 +200,7 @@ const visitors = { let pruning = false; let prune_start = children[0].start; let last = prune_start; + let has_previous_used = false; for (let i = 0; i < children.length; i += 1) { const selector = children[i]; @@ -210,9 +211,9 @@ const visitors = { while (state.code.original[i] !== ',') i--; if (state.minify) { - state.code.remove(prune_start, i + 1); + state.code.remove(prune_start, has_previous_used ? i : i + 1); } else { - state.code.overwrite(i, i + 1, '*/'); + state.code.appendRight(has_previous_used ? i : i + 1, '*/'); } } else { if (i === 0) { @@ -222,17 +223,10 @@ const visitors = { state.code.prependRight(selector.start, '/* (unused) '); } } else { - // If this is not the last selector add a separator - const separator = i !== children.length - 1 ? ',' : ''; - if (state.minify) { prune_start = last; - if (separator) { - while (state.code.original[prune_start - 1] !== ',') prune_start++; - state.code.update(last, prune_start, separator); - } } else { - state.code.overwrite(last, selector.start, `${separator} /* (unused) `); + state.code.overwrite(last, selector.start, ` /* (unused) `); } } } @@ -240,6 +234,10 @@ const visitors = { pruning = !pruning; } + if (!pruning && selector.metadata.used) { + has_previous_used = true; + } + last = selector.end; } diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css index 8eda676f93..68d6aad68a 100644 --- a/packages/svelte/tests/css/samples/has/expected.css +++ b/packages/svelte/tests/css/samples/has/expected.css @@ -75,7 +75,7 @@ /* (unused) .unused x:has(y) { color: red; }*/ - /* (unused) .unused:has(.unused)*/ x.svelte-xyz:has(y:where(.svelte-xyz)) { + /* (unused) .unused:has(.unused),*/ x.svelte-xyz:has(y:where(.svelte-xyz)) { color: green; } diff --git a/packages/svelte/tests/css/samples/unused-selector-in-between/expected.css b/packages/svelte/tests/css/samples/unused-selector-in-between/expected.css index 5a93aa53b7..43db50b0fe 100644 --- a/packages/svelte/tests/css/samples/unused-selector-in-between/expected.css +++ b/packages/svelte/tests/css/samples/unused-selector-in-between/expected.css @@ -1,7 +1,7 @@ h1.svelte-xyz, h2.svelte-xyz, - h3.svelte-xyz, /* (unused) h4*/ + h3.svelte-xyz /* (unused) h4*/, p.svelte-xyz { color: red; } diff --git a/packages/svelte/tests/css/samples/unused-selector-leading/expected.css b/packages/svelte/tests/css/samples/unused-selector-leading/expected.css index 61245348fb..1a5b5d6591 100644 --- a/packages/svelte/tests/css/samples/unused-selector-leading/expected.css +++ b/packages/svelte/tests/css/samples/unused-selector-leading/expected.css @@ -1,3 +1,3 @@ - /* (unused) .foo*/ .bar.svelte-xyz /* (unused) .baz*/ { + /* (unused) .foo,*/ .bar.svelte-xyz /* (unused) .baz*/ { color: red; } diff --git a/packages/svelte/tests/css/samples/unused-selector-multiple/_config.js b/packages/svelte/tests/css/samples/unused-selector-multiple/_config.js new file mode 100644 index 0000000000..1239fe2e35 --- /dev/null +++ b/packages/svelte/tests/css/samples/unused-selector-multiple/_config.js @@ -0,0 +1,160 @@ +import { test } from '../../test'; + +export default test({ + warnings: [ + { + code: 'a11y_missing_content', + message: '`

` element should contain text', + start: { + line: 1, + column: 0, + character: 0 + }, + end: { + line: 1, + column: 9, + character: 9 + } + }, + { + code: 'a11y_missing_content', + message: '`

` element should contain text', + start: { + line: 2, + column: 0, + character: 10 + }, + end: { + line: 2, + column: 9, + character: 19 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "h2"', + start: { + line: 6, + column: 5, + character: 35 + }, + end: { + line: 6, + column: 7, + character: 37 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "h3"', + start: { + line: 6, + column: 9, + character: 39 + }, + end: { + line: 6, + column: 11, + character: 41 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "h2"', + start: { + line: 9, + column: 5, + character: 66 + }, + end: { + line: 9, + column: 7, + character: 68 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "h2"', + start: { + line: 13, + column: 5, + character: 110 + }, + end: { + line: 13, + column: 7, + character: 112 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "h3"', + start: { + line: 13, + column: 9, + character: 114 + }, + end: { + line: 13, + column: 11, + character: 116 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "h2"', + start: { + line: 17, + column: 5, + character: 161 + }, + end: { + line: 17, + column: 7, + character: 163 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "h3"', + start: { + line: 17, + column: 9, + character: 165 + }, + end: { + line: 17, + column: 11, + character: 167 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "h5"', + start: { + line: 17, + column: 17, + character: 173 + }, + end: { + line: 17, + column: 19, + character: 175 + } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector "h6"', + start: { + line: 17, + column: 21, + character: 177 + }, + end: { + line: 17, + column: 23, + character: 179 + } + } + ] +}); diff --git a/packages/svelte/tests/css/samples/unused-selector-multiple/expected.css b/packages/svelte/tests/css/samples/unused-selector-multiple/expected.css new file mode 100644 index 0000000000..78ddf12fec --- /dev/null +++ b/packages/svelte/tests/css/samples/unused-selector-multiple/expected.css @@ -0,0 +1,15 @@ + + h1.svelte-xyz /* (unused) h2, h3*/ { + color: red; + } + h1.svelte-xyz /* (unused) h2*/ { + text-decoration: underline; + } + + h1.svelte-xyz /* (unused) h2, h3*/, h4.svelte-xyz { + text-transform: uppercase; + } + + h1.svelte-xyz /* (unused) h2, h3*/, h4.svelte-xyz /* (unused) h5, h6*/ { + background-color: green; + } diff --git a/packages/svelte/tests/css/samples/unused-selector-multiple/input.svelte b/packages/svelte/tests/css/samples/unused-selector-multiple/input.svelte new file mode 100644 index 0000000000..b5bdb16420 --- /dev/null +++ b/packages/svelte/tests/css/samples/unused-selector-multiple/input.svelte @@ -0,0 +1,20 @@ +

+

+ + +