chore: css unused selector warnings

pull/11098/head
Simon Holthausen 2 years ago
parent 8e31c4e7b6
commit 116cdafdb9

@ -0,0 +1,30 @@
import { walk } from 'zimmerframe';
import { warn } from '../../../warnings.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 = {
ComplexSelector(node, context) {
if (!node.metadata.used) {
for (let i = context.path.length - 1; i >= 0; i--) {
const parent = context.path[i];
if (parent.type === 'RelativeSelector' && parent.metadata.is_global) {
return; // no need to recurse; everything below is global, too
}
}
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) {

@ -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} */

@ -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
}
}
]
});

@ -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: 479,
column: 19,
line: 22
},
message: 'Unused CSS selector ":global(.x) + .bar"',
start: {
character: 461,
column: 1,
line: 22
}
}
]
});

@ -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'));
}
}
}
}

Loading…
Cancel
Save