Merge branch 'gh-678' into gh-679

pull/680/head
Rich Harris 7 years ago
commit 12907594cd

@ -1,5 +1,7 @@
import MagicString, { Bundle } from 'magic-string';
import { walk } from 'estree-walker';
import { getLocator } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame';
import isReference from '../utils/isReference';
import flattenReference from '../utils/flattenReference';
import globalWhitelist from '../utils/globalWhitelist';
@ -619,4 +621,31 @@ export default class Generator {
this.namespace = namespace;
this.templateProperties = templateProperties;
}
warnOnUnusedSelectors() {
if (this.cascade) return;
let locator;
this.selectors.forEach((selector: Selector) => {
if (!selector.used) {
const pos = selector.node.start;
if (!locator) locator = getLocator(this.source);
const { line, column } = locator(pos);
const frame = getCodeFrame(this.source, line, column);
const message = `Unused CSS selector`;
this.options.onwarn({
message,
frame,
loc: { line: line + 1, column },
pos,
filename: this.options.filename,
toString: () => `${message} (${line + 1}:${column})\n${frame}`,
});
}
});
}
}

@ -1,14 +1,17 @@
import { walkRules } from '../utils/css';
import { groupSelectors, isGlobalSelector, walkRules } from '../utils/css';
import { Node } from '../interfaces';
export default class Selector {
node: Node;
blocks: Node[][];
parts: Node[];
used: boolean;
constructor(node: Node) {
this.node = node;
this.blocks = groupSelectors(this.node);
// take trailing :global(...) selectors out of consideration
let i = node.children.length;
while (i > 2) {
@ -24,13 +27,15 @@ export default class Selector {
this.parts = node.children.slice(0, i);
this.used = false; // TODO use this! warn on unused selectors
this.used = isGlobalSelector(this.blocks[0]);
}
apply(node: Node, stack: Node[]) {
const applies = selectorAppliesTo(this.parts, node, stack.slice());
if (applies) {
this.used = true;
// add svelte-123xyz attribute to outermost and innermost
// elements — no need to add it to intermediate elements
node._needsCssAttribute = true;

@ -61,6 +61,8 @@ export default function dom(
const { block, state } = preprocess(generator, namespace, parsed.html);
generator.warnOnUnusedSelectors();
parsed.html.children.forEach((node: Node) => {
visit(generator, block, state, node, []);
});

@ -27,6 +27,8 @@ export class SsrGenerator extends Generator {
preprocess(this, parsed.html);
this.warnOnUnusedSelectors();
if (templateProperties.oncreate)
removeNode(
this.code,

@ -26,9 +26,11 @@ export interface Parsed {
}
export interface Warning {
loc?: { line: number; column: number; pos: number };
loc?: { line: number; column: number; pos?: number };
pos?: number;
message: string;
filename?: string;
frame?: string;
toString: () => string;
}

@ -35,9 +35,9 @@ export class Validator {
constructor(parsed: Parsed, source: string, options: CompileOptions) {
this.source = source;
this.filename = options !== undefined ? options.filename : undefined;
this.filename = options.filename;
this.onwarn = options !== undefined ? options.onwarn : undefined;
this.onwarn = options.onwarn;
this.namespace = null;
this.defaultExport = null;

@ -4,13 +4,21 @@ import { env, normalizeHtml, svelte } from "../helpers.js";
function tryRequire(file) {
try {
return require(file).default;
const mod = require(file);
return mod.default || mod;
} catch (err) {
if (err.code !== "MODULE_NOT_FOUND") throw err;
return null;
}
}
function normalizeWarning(warning) {
warning.frame = warning.frame.replace(/^\n/, '').replace(/^\t+/gm, '');
delete warning.filename;
delete warning.toString;
return warning;
}
describe("css", () => {
fs.readdirSync("test/css/samples").forEach(dir => {
if (dir[0] === ".") return;
@ -28,19 +36,32 @@ describe("css", () => {
.readFileSync(`test/css/samples/${dir}/input.html`, "utf-8")
.replace(/\s+$/, "");
const expectedWarnings = (config.warnings || []).map(normalizeWarning);
const domWarnings = [];
const ssrWarnings = [];
const dom = svelte.compile(input, Object.assign(config, {
format: 'iife',
name: 'SvelteComponent'
name: 'SvelteComponent',
onwarn: warning => {
domWarnings.push(warning);
}
}));
const ssr = svelte.compile(input, Object.assign(config, {
format: 'iife',
generate: 'ssr',
name: 'SvelteComponent'
name: 'SvelteComponent',
onwarn: warning => {
ssrWarnings.push(warning);
}
}));
assert.equal(dom.css, ssr.css);
assert.deepEqual(domWarnings.map(normalizeWarning), ssrWarnings.map(normalizeWarning));
assert.deepEqual(domWarnings.map(normalizeWarning), expectedWarnings);
fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css);
const expected = {
html: read(`test/css/samples/${dir}/expected.html`),

@ -1,3 +1,19 @@
export default {
cascade: false
cascade: false,
warnings: [{
message: 'Unused CSS selector',
loc: {
line: 8,
column: 1
},
pos: 74,
frame: `
6:
7: <style>
8: div > p {
^
9: color: red;
10: }`
}]
};

@ -0,0 +1,20 @@
export default {
cascade: false,
warnings: [{
filename: "SvelteComponent.html",
message: "Unused CSS selector",
loc: {
line: 8,
column: 1
},
pos: 60,
frame: `
6: }
7:
8: .bar {
^
9: color: blue;
10: }`
}]
};

@ -0,0 +1,8 @@
.foo[svelte-698347462] {
color: red;
}
.bar[svelte-698347462] {
color: blue;
}

@ -0,0 +1 @@
<div svelte-698347462="" class="foo"></div>

@ -0,0 +1,11 @@
<div class='foo'></div>
<style>
.foo {
color: red;
}
.bar {
color: blue;
}
</style>

@ -0,0 +1,10 @@
[{
"filename": "SvelteComponent.html",
"message": "Unused CSS selector",
"loc": {
"line": 8,
"column": 1
},
"pos": 61,
"frame": " 6: }\n 7: \n 8: .bar {\n ^\n 9: color: blue;\n10: }"
}]
Loading…
Cancel
Save