chore: css unused selector warnings (#11098)

The character adjustments in the existing warnings are because we remove some tabs from empty lines when initializing the Svelte 5 repo; the warnings were just not checked at that time yet.
pull/11093/head
Simon H 1 year ago committed by GitHub
parent ed9bab9200
commit d2b6159d2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -36,7 +36,8 @@ export default function read_style(parser, start, attributes) {
content: {
start: content_start,
end: content_end,
styles: parser.template.slice(content_start, content_end)
styles: parser.template.slice(content_start, content_end),
comment: null
}
};
}

@ -283,25 +283,26 @@ export default function tag(parser) {
if (is_top_level_script_or_style) {
parser.eat('>', true);
if (name === 'script') {
const content = read_script(parser, start, element.attributes);
/** @type {import('#compiler').Comment | null} */
let prev_comment = null;
for (let i = current.fragment.nodes.length - 1; i >= 0; i--) {
const node = current.fragment.nodes[i];
/** @type {import('#compiler').Comment | null} */
let prev_comment = null;
for (let i = current.fragment.nodes.length - 1; i >= 0; i--) {
const node = current.fragment.nodes[i];
if (i === current.fragment.nodes.length - 1 && node.end !== start) {
break;
}
if (i === current.fragment.nodes.length - 1 && node.end !== start) {
break;
}
if (node.type === 'Comment') {
prev_comment = node;
break;
} else if (node.type !== 'Text' || node.data.trim()) {
break;
}
if (node.type === 'Comment') {
prev_comment = node;
break;
} else if (node.type !== 'Text' || node.data.trim()) {
break;
}
}
if (name === 'script') {
const content = read_script(parser, start, element.attributes);
if (prev_comment) {
// We take advantage of the fact that the root will never have leadingComments set,
// and set the previous comment to it so that the warning mechanism can later
@ -318,6 +319,7 @@ export default function tag(parser) {
}
} else {
const content = read_style(parser, start, element.attributes);
content.content.comment = prev_comment;
if (current.css) error(start, 'duplicate-style-element');
current.css = content;

@ -0,0 +1,34 @@
import { walk } from 'zimmerframe';
import { warn } from '../../../warnings.js';
import { is_keyframes_node } from '../../css.js';
/**
* @param {import('#compiler').Css.StyleSheet} stylesheet
* @param {import('../../types.js').RawWarning[]} warnings
*/
export function warn_unused(stylesheet, warnings) {
walk(stylesheet, { warnings, stylesheet }, visitors);
}
/** @type {import('zimmerframe').Visitors<import('#compiler').Css.Node, { warnings: import('../../types.js').RawWarning[], stylesheet: import('#compiler').Css.StyleSheet }>} */
const visitors = {
Atrule(node, context) {
if (!is_keyframes_node(node)) {
context.next();
}
},
PseudoClassSelector(node, context) {
if (node.name === 'is' || node.name === 'where') {
context.next();
}
},
ComplexSelector(node, context) {
if (!node.metadata.used) {
const content = context.state.stylesheet.content;
const text = content.styles.substring(node.start - content.start, node.end - content.start);
warn(context.state.warnings, node, context.path, 'css-unused-selector', text);
}
context.next();
}
};

@ -23,6 +23,7 @@ import { should_proxy_or_freeze } from '../3-transform/client/utils.js';
import { analyze_css } from './css/css-analyze.js';
import { prune } from './css/css-prune.js';
import { hash } from './utils.js';
import { warn_unused } from './css/css-warn.js';
/**
* @param {import('#compiler').Script | null} script
@ -548,6 +549,7 @@ export function analyze_component(root, source, options) {
for (const element of analysis.elements) {
prune(analysis.css.ast, element);
}
warn_unused(analysis.css.ast, analysis.warnings);
outer: for (const element of analysis.elements) {
if (element.metadata.scoped) {

@ -1,3 +1,5 @@
import type { Comment } from '#compiler';
export namespace Css {
export interface BaseNode {
start: number;
@ -12,6 +14,8 @@ export namespace Css {
start: number;
end: number;
styles: string;
/** Possible comment atop the style tag */
comment: Comment | null;
};
}

@ -7,7 +7,8 @@ import {
/** @satisfies {Warnings} */
const css = {
'unused-selector': () => 'Unused CSS selector'
/** @param {string} name */
'css-unused-selector': (name) => `Unused CSS selector "${name}"`
};
/** @satisfies {Warnings} */
@ -300,6 +301,11 @@ export function warn(array, node, path, code, ...args) {
)
);
}
// Style nodes
if (current.type === 'StyleSheet' && current.content.comment) {
ignores.push(...current.content.comment.ignores);
}
}
if (ignores.includes(code)) return;

@ -0,0 +1,20 @@
import { test } from '../../test';
export default test({
warnings: [
{
code: 'css-unused-selector',
end: {
character: 44,
column: 14,
line: 4
},
message: 'Unused CSS selector "p[type=\'B\' s]"',
start: {
character: 31,
column: 1,
line: 4
}
}
]
});

@ -0,0 +1,20 @@
import { test } from '../../test';
export default test({
warnings: [
{
code: 'css-unused-selector',
end: {
character: 33,
column: 6,
line: 6
},
message: 'Unused CSS selector "x y z"',
start: {
character: 28,
column: 1,
line: 6
}
}
]
});

@ -5,8 +5,8 @@ export default test({
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".b ~ .c"',
start: { character: 199, column: 1, line: 13 },
end: { character: 206, column: 8, line: 13 }
start: { character: 198, column: 1, line: 13 },
end: { character: 205, column: 8, line: 13 }
}
]
});

@ -2,12 +2,19 @@ import { test } from '../../test';
export default test({
warnings: [
// TODO
// {
// code: 'css-unused-selector',
// message: 'Unused CSS selector ".a ~ .b"',
// start: { character: 111, column: 1, line: 10 },
// end: { character: 118, column: 8, line: 10 }
// },
{
code: 'css-unused-selector',
end: {
character: 479,
column: 19,
line: 22
},
message: 'Unused CSS selector ":global(.x) + .bar"',
start: {
character: 461,
column: 1,
line: 22
}
}
]
});

@ -2,12 +2,19 @@ import { test } from '../../test';
export default test({
warnings: [
// TODO
// {
// code: 'css-unused-selector',
// message: 'Unused CSS selector ".a ~ .b"',
// start: { character: 111, column: 1, line: 10 },
// end: { character: 118, column: 8, line: 10 }
// },
{
code: 'css-unused-selector',
end: {
character: 472,
column: 19,
line: 22
},
message: 'Unused CSS selector ":global(.x) + .bar"',
start: {
character: 454,
column: 1,
line: 22
}
}
]
});

@ -5,38 +5,38 @@ export default test({
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".a ~ .b"',
start: { character: 111, column: 1, line: 10 },
end: { character: 118, column: 8, line: 10 }
start: { character: 110, column: 1, line: 10 },
end: { character: 117, column: 8, line: 10 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".b ~ .c"',
start: { character: 138, column: 1, line: 11 },
end: { character: 145, column: 8, line: 11 }
start: { character: 137, column: 1, line: 11 },
end: { character: 144, column: 8, line: 11 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".c ~ .f"',
start: { character: 165, column: 1, line: 12 },
end: { character: 172, column: 8, line: 12 }
start: { character: 164, column: 1, line: 12 },
end: { character: 171, column: 8, line: 12 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".f ~ .g"',
start: { character: 192, column: 1, line: 13 },
end: { character: 199, column: 8, line: 13 }
start: { character: 191, column: 1, line: 13 },
end: { character: 198, column: 8, line: 13 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".b ~ .f"',
start: { character: 219, column: 1, line: 14 },
end: { character: 226, column: 8, line: 14 }
start: { character: 218, column: 1, line: 14 },
end: { character: 225, column: 8, line: 14 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".b ~ .g"',
start: { character: 246, column: 1, line: 15 },
end: { character: 253, column: 8, line: 15 }
start: { character: 245, column: 1, line: 15 },
end: { character: 252, column: 8, line: 15 }
}
]
});

@ -2,12 +2,19 @@ import { test } from '../../test';
export default test({
warnings: [
// TODO
// {
// code: 'css-unused-selector',
// message: 'Unused CSS selector ".a ~ .b"',
// start: { character: 111, column: 1, line: 10 },
// end: { character: 118, column: 8, line: 10 }
// },
{
code: 'css-unused-selector',
end: {
character: 496,
column: 10,
line: 26
},
message: 'Unused CSS selector ".x + .bar"',
start: {
character: 487,
column: 1,
line: 26
}
}
]
});

@ -6,12 +6,12 @@ export default test({
code: 'css-unused-selector',
message: 'Unused CSS selector ":host > span"',
start: {
character: 147,
character: 145,
column: 1,
line: 18
},
end: {
character: 159,
character: 157,
column: 13,
line: 18
}

@ -0,0 +1,20 @@
import { test } from '../../test';
export default test({
warnings: [
{
code: 'css-unused-selector',
end: {
character: 38,
column: 11,
line: 6
},
message: 'Unused CSS selector "z"',
start: {
character: 37,
column: 10,
line: 6
}
}
]
});

@ -0,0 +1,104 @@
import { test } from '../../test';
export default test({
warnings: [
{
code: 'css-unused-selector',
end: {
character: 239,
column: 13,
line: 20
},
message: 'Unused CSS selector ".unused"',
start: {
character: 232,
column: 6,
line: 20
}
},
{
code: 'css-unused-selector',
end: {
character: 302,
column: 10,
line: 27
},
message: 'Unused CSS selector ".unused"',
start: {
character: 295,
column: 3,
line: 27
}
},
{
code: 'css-unused-selector',
end: {
character: 328,
column: 6,
line: 30
},
message: 'Unused CSS selector ".c"',
start: {
character: 326,
column: 4,
line: 30
}
},
{
code: 'css-unused-selector',
end: {
character: 381,
column: 10,
line: 37
},
message: 'Unused CSS selector ".unused"',
start: {
character: 374,
column: 3,
line: 37
}
},
{
code: 'css-unused-selector',
end: {
character: 471,
column: 7,
line: 47
},
message: 'Unused CSS selector "& &"',
start: {
character: 468,
column: 4,
line: 47
}
},
{
code: 'css-unused-selector',
end: {
character: 634,
column: 5,
line: 66
},
message: 'Unused CSS selector "&.b"',
start: {
character: 631,
column: 2,
line: 66
}
},
{
code: 'css-unused-selector',
end: {
character: 666,
column: 9,
line: 70
},
message: 'Unused CSS selector ".unused"',
start: {
character: 659,
column: 2,
line: 70
}
}
]
});

@ -5,62 +5,62 @@ export default test({
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".a + .c"',
start: { character: 479, column: 1, line: 23 },
end: { character: 486, column: 8, line: 23 }
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: 506, column: 1, line: 24 },
end: { character: 513, column: 8, line: 24 }
start: { character: 505, column: 1, line: 24 },
end: { character: 512, column: 8, line: 24 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".b + .e"',
start: { character: 533, column: 1, line: 25 },
end: { character: 540, column: 8, line: 25 }
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: 560, column: 1, line: 26 },
end: { character: 567, column: 8, line: 26 }
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: 587, column: 1, line: 27 },
end: { character: 594, column: 8, line: 27 }
start: { character: 586, column: 1, line: 27 },
end: { character: 593, column: 8, line: 27 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".d + .d"',
start: { character: 614, column: 1, line: 28 },
end: { character: 621, column: 8, line: 28 }
start: { character: 613, column: 1, line: 28 },
end: { character: 620, column: 8, line: 28 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".e + .f"',
start: { character: 641, column: 1, line: 29 },
end: { character: 648, column: 8, line: 29 }
start: { character: 640, column: 1, line: 29 },
end: { character: 647, column: 8, line: 29 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".f + .f"',
start: { character: 668, column: 1, line: 30 },
end: { character: 675, column: 8, line: 30 }
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: 695, column: 1, line: 31 },
end: { character: 702, column: 8, line: 31 }
start: { character: 694, column: 1, line: 31 },
end: { character: 701, column: 8, line: 31 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".g + .h + .i + .j"',
start: { character: 722, column: 1, line: 32 },
end: { character: 739, column: 18, line: 32 }
start: { character: 721, column: 1, line: 32 },
end: { character: 738, column: 18, line: 32 }
}
]
});

@ -5,14 +5,14 @@ export default test({
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".a + .d"',
start: { character: 172, column: 1, line: 12 },
end: { character: 179, column: 8, line: 12 }
start: { character: 171, column: 1, line: 12 },
end: { character: 178, column: 8, line: 12 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".b + .c"',
start: { character: 199, column: 1, line: 13 },
end: { character: 206, column: 8, line: 13 }
start: { character: 198, column: 1, line: 13 },
end: { character: 205, column: 8, line: 13 }
}
]
});

@ -5,20 +5,20 @@ export default test({
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".a + .b"',
start: { character: 84, column: 1, line: 9 },
end: { character: 91, column: 8, line: 9 }
start: { character: 83, column: 1, line: 9 },
end: { character: 90, column: 8, line: 9 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".b + .c"',
start: { character: 111, column: 1, line: 10 },
end: { character: 118, column: 8, line: 10 }
start: { character: 110, column: 1, line: 10 },
end: { character: 117, column: 8, line: 10 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".c + .f"',
start: { character: 138, column: 1, line: 11 },
end: { character: 145, column: 8, line: 11 }
start: { character: 137, column: 1, line: 11 },
end: { character: 144, column: 8, line: 11 }
}
]
});

@ -5,20 +5,20 @@ export default test({
{
code: 'css-unused-selector',
message: 'Unused CSS selector "article > *"',
start: { character: 10, column: 1, line: 2 },
end: { character: 21, column: 12, line: 2 }
start: { character: 9, column: 1, line: 2 },
end: { character: 20, column: 12, line: 2 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector "article *"',
start: { character: 49, column: 1, line: 6 },
end: { character: 58, column: 10, line: 6 }
start: { character: 47, column: 1, line: 6 },
end: { character: 56, column: 10, line: 6 }
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ".article > *"',
start: { character: 86, column: 1, line: 10 },
end: { character: 98, column: 13, line: 10 }
start: { character: 83, column: 1, line: 10 },
end: { character: 95, column: 13, line: 10 }
}
]
});

@ -1,6 +1,6 @@
/* (unused) article > * {
font-size: 36px;
}*/
}*/
/* (unused) article * {
font-size: 36px;

@ -1,7 +1,7 @@
<style>
article > * {
font-size: 36px;
}
}
article * {
font-size: 36px;

@ -0,0 +1,20 @@
import { test } from '../../test';
export default test({
warnings: [
{
code: 'css-unused-selector',
end: {
character: 32,
column: 3,
line: 5
},
message: 'Unused CSS selector "h2"',
start: {
character: 30,
column: 1,
line: 5
}
}
]
});

@ -8,12 +8,17 @@ import { mount, unmount } from 'svelte';
import { suite, type BaseTest } from '../suite.js';
import type { CompileOptions, Warning } from '#compiler';
// function normalize_warning(warning) {
// warning.frame = warning.frame.replace(/^\n/, '').replace(/^\t+/gm, '').replace(/\s+$/gm, '');
// delete warning.filename;
// delete warning.toString;
// return warning;
// }
function normalize_warning(warning: Warning) {
delete warning.filename;
return warning;
}
function load_warnings(path: string) {
if (!fs.existsSync(path)) {
return [];
}
return JSON.parse(fs.readFileSync(path, 'utf-8')).map(normalize_warning);
}
interface CssTest extends BaseTest {
compileOptions?: Partial<CompileOptions>;
@ -22,9 +27,6 @@ interface CssTest extends BaseTest {
}
const { test, run } = suite<CssTest>(async (config, cwd) => {
// TODO
// const expected_warnings = (config.warnings || []).map(normalize_warning);
await compile_directory(cwd, 'client', { cssHash: () => 'svelte-xyz', ...config.compileOptions });
await compile_directory(cwd, 'server', { cssHash: () => 'svelte-xyz', ...config.compileOptions });
@ -33,11 +35,11 @@ const { test, run } = suite<CssTest>(async (config, cwd) => {
assert.equal(dom_css, ssr_css);
// TODO reenable
// const dom_warnings = dom.warnings.map(normalize_warning);
// const ssr_warnings = ssr.warnings.map(normalize_warning);
// assert.deepEqual(dom_warnings, ssr_warnings);
// assert.deepEqual(dom_warnings.map(normalize_warning), expected_warnings);
const dom_warnings = load_warnings(`${cwd}/_output/client/input.svelte.warnings.json`);
const ssr_warnings = load_warnings(`${cwd}/_output/server/input.svelte.warnings.json`);
const expected_warnings = (config.warnings || []).map(normalize_warning);
assert.deepEqual(dom_warnings, ssr_warnings);
assert.deepEqual(dom_warnings.map(normalize_warning), expected_warnings);
const expected = {
html: try_read_file(`${cwd}/expected.html`),

@ -71,7 +71,7 @@ export async function compile_directory(
for (const file of glob('**', { cwd, filesOnly: true })) {
if (file.startsWith('_')) continue;
let text = fs.readFileSync(`${cwd}/${file}`, 'utf-8');
let text = fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r\n/g, '\n');
let opts = {
filename: path.join(cwd, file),
...compileOptions,
@ -138,6 +138,10 @@ export async function compile_directory(
write(`${output_dir}/${file}.css.map`, JSON.stringify(compiled.css.map, null, '\t'));
}
}
if (compiled.warnings.length > 0) {
write(`${output_dir}/${file}.warnings.json`, JSON.stringify(compiled.warnings, null, '\t'));
}
}
}
}

@ -78,6 +78,7 @@
"content": {
"start": 23,
"end": 48,
"comment": null,
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n"
}
}

@ -78,6 +78,7 @@
"content": {
"start": 23,
"end": 48,
"comment": null,
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n"
}
}

@ -1074,6 +1074,7 @@
"content": {
"start": 7,
"end": 798,
"comment": null,
"styles": "\n /* test that all these are parsed correctly */\n\th1:nth-of-type(2n+1){\n background: red;\n }\n h1:nth-child(-n + 3 of li.important) {\n background: red;\n }\n h1:nth-child(1) {\n background: red;\n }\n h1:nth-child(p) {\n background: red;\n }\n h1:nth-child(n+7) {\n background: red;\n }\n h1:nth-child(even) {\n background: red;\n }\n h1:nth-child(odd) {\n background: red;\n }\n h1:nth-child(\n n\n ) {\n background: red;\n }\n h1:global(nav) {\n background: red;\n }\n\t\th1:nth-of-type(10n+1){\n background: red;\n }\n\t\th1:nth-of-type(-2n+3){\n background: red;\n }\n\t\th1:nth-of-type(+12){\n background: red;\n }\n\t\th1:nth-of-type(+3n){\n background: red;\n }\n"
}
},

@ -393,6 +393,7 @@
"content": {
"start": 7,
"end": 378,
"comment": null,
"styles": "\n /* test that all these are parsed correctly */\n\t::view-transition-old(x-y) {\n\t\tcolor: red;\n }\n\t:global(::view-transition-old(x-y)) {\n\t\tcolor: red;\n }\n\t::highlight(rainbow-color-1) {\n\t\tcolor: red;\n\t}\n\tcustom-element::part(foo) {\n\t\tcolor: red;\n\t}\n\t::slotted(.content) {\n\t\tcolor: red;\n\t}\n\t:is( /*button*/\n\t\tbutton, /*p after h1*/\n\t\th1 + p\n\t\t){\n\t\tcolor: red;\n\t}\n"
}
},

@ -71,6 +71,7 @@
"content": {
"start": 43,
"end": 197,
"comment": null,
"styles": "\n\t@import url(\"https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap\");\n\th1 {\n\t\tfont-weight: bold;\n\t\tbackground: url(\"whatever\");\n\t}\n"
}
},

@ -1087,6 +1087,8 @@ declare module 'svelte/compiler' {
start: number;
end: number;
styles: string;
/** Possible comment atop the style tag */
comment: Comment | null;
};
}

Loading…
Cancel
Save