Merge pull request #2102 from sveltejs/warnings-and-vars

move warnings and vars out of stats
pull/2113/head
Rich Harris 7 years ago committed by GitHub
commit d31d857794
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,3 @@
import { Warning } from './interfaces';
import Component from './compile/Component';
const now = (typeof process !== 'undefined' && process.hrtime) const now = (typeof process !== 'undefined' && process.hrtime)
? () => { ? () => {
const t = process.hrtime(); const t = process.hrtime();
@ -31,14 +28,11 @@ export default class Stats {
currentChildren: Timing[]; currentChildren: Timing[];
timings: Timing[]; timings: Timing[];
stack: Timing[]; stack: Timing[];
warnings: Warning[];
constructor() { constructor() {
this.startTime = now(); this.startTime = now();
this.stack = []; this.stack = [];
this.currentChildren = this.timings = []; this.currentChildren = this.timings = [];
this.warnings = [];
} }
start(label) { start(label) {
@ -67,46 +61,13 @@ export default class Stats {
this.currentChildren = this.currentTiming ? this.currentTiming.children : this.timings; this.currentChildren = this.currentTiming ? this.currentTiming.children : this.timings;
} }
render(component: Component) { render() {
const timings = Object.assign({ const timings = Object.assign({
total: now() - this.startTime total: now() - this.startTime
}, collapseTimings(this.timings)); }, collapseTimings(this.timings));
// TODO would be good to have this info even
// if options.generate is false
const imports = component && component.imports.map(node => {
return {
source: node.source.value,
specifiers: node.specifiers.map(specifier => {
return {
name: (
specifier.type === 'ImportDefaultSpecifier' ? 'default' :
specifier.type === 'ImportNamespaceSpecifier' ? '*' :
specifier.imported.name
),
as: specifier.local.name
};
})
}
});
return { return {
timings, timings
warnings: this.warnings,
vars: component.vars.filter(variable => !variable.global && !variable.implicit && !variable.internal).map(variable => ({
name: variable.name,
export_name: variable.export_name || null,
injected: variable.injected || false,
module: variable.module || false,
mutated: variable.mutated || false,
reassigned: variable.reassigned || false,
referenced: variable.referenced || false,
writable: variable.writable || false
}))
}; };
} }
warn(warning) {
this.warnings.push(warning);
}
} }

@ -11,7 +11,7 @@ import Stylesheet from './css/Stylesheet';
import { test } from '../config'; import { test } from '../config';
import Fragment from './nodes/Fragment'; import Fragment from './nodes/Fragment';
import internal_exports from './internal-exports'; import internal_exports from './internal-exports';
import { Node, Ast, CompileOptions, Var } from '../interfaces'; import { Node, Ast, CompileOptions, Var, Warning } from '../interfaces';
import error from '../utils/error'; import error from '../utils/error';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import flattenReference from '../utils/flattenReference'; import flattenReference from '../utils/flattenReference';
@ -40,6 +40,7 @@ childKeys.ExportNamedDeclaration = ['declaration', 'specifiers'];
export default class Component { export default class Component {
stats: Stats; stats: Stats;
warnings: Warning[];
ast: Ast; ast: Ast;
source: string; source: string;
@ -93,11 +94,13 @@ export default class Component {
source: string, source: string,
name: string, name: string,
compileOptions: CompileOptions, compileOptions: CompileOptions,
stats: Stats stats: Stats,
warnings: Warning[]
) { ) {
this.name = name; this.name = name;
this.stats = stats; this.stats = stats;
this.warnings = warnings;
this.ast = ast; this.ast = ast;
this.source = source; this.source = source;
this.compileOptions = compileOptions; this.compileOptions = compileOptions;
@ -161,7 +164,7 @@ export default class Component {
if (!compileOptions.customElement) this.stylesheet.reify(); if (!compileOptions.customElement) this.stylesheet.reify();
this.stylesheet.warnOnUnusedSelectors(stats); this.stylesheet.warnOnUnusedSelectors(this);
} }
add_var(variable: Var) { add_var(variable: Var) {
@ -214,105 +217,121 @@ export default class Component {
} }
generate(result: string) { generate(result: string) {
const { compileOptions, name } = this; let js = null;
const { format = 'esm' } = compileOptions; let css = null;
const banner = `/* ${this.file ? `${this.file} ` : ``}generated by Svelte v${"__VERSION__"} */`; if (result) {
const { compileOptions, name } = this;
const { format = 'esm' } = compileOptions;
// TODO use same regex for both const banner = `/* ${this.file ? `${this.file} ` : ``}generated by Svelte v${"__VERSION__"} */`;
result = result.replace(compileOptions.generate === 'ssr' ? /(@+|#+)(\w*(?:-\w*)?)/g : /(@+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => {
if (sigil === '@') {
if (internal_exports.has(name)) {
if (compileOptions.dev && internal_exports.has(`${name}Dev`)) name = `${name}Dev`;
this.helpers.add(name);
}
return this.alias(name); // TODO use same regex for both
} result = result.replace(compileOptions.generate === 'ssr' ? /(@+|#+)(\w*(?:-\w*)?)/g : /(@+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => {
if (sigil === '@') {
if (internal_exports.has(name)) {
if (compileOptions.dev && internal_exports.has(`${name}Dev`)) name = `${name}Dev`;
this.helpers.add(name);
}
return sigil.slice(1) + name; return this.alias(name);
}); }
const importedHelpers = Array.from(this.helpers) return sigil.slice(1) + name;
.sort()
.map(name => {
const alias = this.alias(name);
return { name, alias };
}); });
const module = wrapModule( const importedHelpers = Array.from(this.helpers)
result, .sort()
format, .map(name => {
name, const alias = this.alias(name);
compileOptions, return { name, alias };
banner, });
compileOptions.sveltePath,
importedHelpers, const module = wrapModule(
this.imports, result,
this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({ format,
name: variable.name, name,
as: variable.export_name compileOptions,
})), banner,
this.source compileOptions.sveltePath,
); importedHelpers,
this.imports,
this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({
name: variable.name,
as: variable.export_name
})),
this.source
);
const parts = module.split('✂]'); const parts = module.split('✂]');
const finalChunk = parts.pop(); const finalChunk = parts.pop();
const compiled = new Bundle({ separator: '' }); const compiled = new Bundle({ separator: '' });
function addString(str: string) { function addString(str: string) {
compiled.addSource({ compiled.addSource({
content: new MagicString(str), content: new MagicString(str),
}); });
} }
const { filename } = compileOptions; const { filename } = compileOptions;
// special case — the source file doesn't actually get used anywhere. we need // special case — the source file doesn't actually get used anywhere. we need
// to add an empty file to populate map.sources and map.sourcesContent // to add an empty file to populate map.sources and map.sourcesContent
if (!parts.length) { if (!parts.length) {
compiled.addSource({ compiled.addSource({
filename, filename,
content: new MagicString(this.source).remove(0, this.source.length), content: new MagicString(this.source).remove(0, this.source.length),
}); });
} }
const pattern = /\[✂(\d+)-(\d+)$/; const pattern = /\[✂(\d+)-(\d+)$/;
parts.forEach((str: string) => { parts.forEach((str: string) => {
const chunk = str.replace(pattern, ''); const chunk = str.replace(pattern, '');
if (chunk) addString(chunk); if (chunk) addString(chunk);
const match = pattern.exec(str); const match = pattern.exec(str);
const snippet = this.code.snip(+match[1], +match[2]); const snippet = this.code.snip(+match[1], +match[2]);
compiled.addSource({ compiled.addSource({
filename, filename,
content: snippet, content: snippet,
});
}); });
});
addString(finalChunk); addString(finalChunk);
const css = compileOptions.customElement ? css = compileOptions.customElement ?
{ code: null, map: null } : { code: null, map: null } :
this.stylesheet.render(compileOptions.cssOutputFilename, true); this.stylesheet.render(compileOptions.cssOutputFilename, true);
const js = { js = {
code: compiled.toString(), code: compiled.toString(),
map: compiled.generateMap({ map: compiled.generateMap({
includeContent: true, includeContent: true,
file: compileOptions.outputFilename, file: compileOptions.outputFilename,
}) })
}; };
}
return { return {
ast: this.ast,
js, js,
css, css,
stats: this.stats.render(this) ast: this.ast,
warnings: this.warnings,
vars: this.vars.filter(v => !v.global && !v.implicit && !v.internal).map(v => ({
name: v.name,
export_name: v.export_name || null,
injected: v.injected || false,
module: v.module || false,
mutated: v.mutated || false,
reassigned: v.reassigned || false,
referenced: v.referenced || false,
writable: v.writable || false
})),
stats: this.stats.render()
}; };
} }
@ -393,7 +412,7 @@ export default class Component {
const frame = getCodeFrame(this.source, start.line - 1, start.column); const frame = getCodeFrame(this.source, start.line - 1, start.column);
this.stats.warn({ this.warnings.push({
code: warning.code, code: warning.code,
message: warning.message, message: warning.message,
frame, frame,

@ -1,14 +1,11 @@
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { getLocator } from 'locate-character';
import Selector from './Selector'; import Selector from './Selector';
import getCodeFrame from '../../utils/getCodeFrame';
import hash from '../../utils/hash'; import hash from '../../utils/hash';
import removeCSSPrefix from '../../utils/removeCSSPrefix'; import removeCSSPrefix from '../../utils/removeCSSPrefix';
import Element from '../nodes/Element'; import Element from '../nodes/Element';
import { Node, Ast, Warning } from '../../interfaces'; import { Node, Ast } from '../../interfaces';
import Component from '../Component'; import Component from '../Component';
import Stats from '../../Stats';
const isKeyframesNode = (node: Node) => removeCSSPrefix(node.name) === 'keyframes' const isKeyframesNode = (node: Node) => removeCSSPrefix(node.name) === 'keyframes'
@ -392,33 +389,14 @@ export default class Stylesheet {
}); });
} }
warnOnUnusedSelectors(stats: Stats) { warnOnUnusedSelectors(component: Component) {
let locator;
const handler = (selector: Selector) => {
const pos = selector.node.start;
if (!locator) locator = getLocator(this.source, { offsetLine: 1 });
const start = locator(pos);
const end = locator(selector.node.end);
const frame = getCodeFrame(this.source, start.line - 1, start.column);
const message = `Unused CSS selector`;
stats.warn({
code: `css-unused-selector`,
message,
frame,
start,
end,
pos,
filename: this.filename,
toString: () => `${message} (${start.line}:${start.column})\n${frame}`,
});
};
this.children.forEach(child => { this.children.forEach(child => {
child.warnOnUnusedSelector(handler); child.warnOnUnusedSelector((selector: Selector) => {
component.warn(selector.node, {
code: `css-unused-selector`,
message: `Unused CSS selector`
});
});
}); });
} }
} }

@ -3,7 +3,7 @@ import Stats from '../Stats';
import parse from '../parse/index'; import parse from '../parse/index';
import renderDOM from './render-dom/index'; import renderDOM from './render-dom/index';
import renderSSR from './render-ssr/index'; import renderSSR from './render-ssr/index';
import { CompileOptions, Ast } from '../interfaces'; import { CompileOptions, Ast, Warning } from '../interfaces';
import Component from './Component'; import Component from './Component';
import fuzzymatch from '../utils/fuzzymatch'; import fuzzymatch from '../utils/fuzzymatch';
@ -24,7 +24,7 @@ const valid_options = [
'preserveComments' 'preserveComments'
]; ];
function validate_options(options: CompileOptions, stats: Stats) { function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename } = options; const { name, filename } = options;
Object.keys(options).forEach(key => { Object.keys(options).forEach(key => {
@ -43,7 +43,7 @@ function validate_options(options: CompileOptions, stats: Stats) {
if (name && /^[a-z]/.test(name)) { if (name && /^[a-z]/.test(name)) {
const message = `options.name should be capitalised`; const message = `options.name should be capitalised`;
stats.warn({ warnings.push({
code: `options-lowercase-name`, code: `options-lowercase-name`,
message, message,
filename, filename,
@ -74,10 +74,11 @@ export default function compile(source: string, options: CompileOptions = {}) {
options = assign({ generate: 'dom', dev: false }, options); options = assign({ generate: 'dom', dev: false }, options);
const stats = new Stats(); const stats = new Stats();
const warnings = [];
let ast: Ast; let ast: Ast;
validate_options(options, stats); validate_options(options, warnings);
stats.start('parse'); stats.start('parse');
ast = parse(source, options); ast = parse(source, options);
@ -89,17 +90,16 @@ export default function compile(source: string, options: CompileOptions = {}) {
source, source,
options.name || get_name(options.filename) || 'SvelteComponent', options.name || get_name(options.filename) || 'SvelteComponent',
options, options,
stats stats,
warnings
); );
stats.stop('create component'); stats.stop('create component');
if (options.generate === false) { const js = options.generate === false
return { ast, stats: stats.render(component), js: null, css: null }; ? null
} : options.generate === 'ssr'
? renderSSR(component, options)
const js = options.generate === 'ssr' : renderDOM(component, options);
? renderSSR(component, options)
: renderDOM(component, options);
return component.generate(js); return component.generate(js);
} }

@ -68,8 +68,8 @@ describe('css', () => {
assert.equal(dom.css.code, ssr.css.code); assert.equal(dom.css.code, ssr.css.code);
const dom_warnings = dom.stats.warnings.map(normalize_warning); const dom_warnings = dom.warnings.map(normalize_warning);
const ssr_warnings = ssr.stats.warnings.map(normalize_warning); const ssr_warnings = ssr.warnings.map(normalize_warning);
assert.deepEqual(dom_warnings, ssr_warnings); assert.deepEqual(dom_warnings, ssr_warnings);
assert.deepEqual(dom_warnings.map(normalize_warning), expected_warnings); assert.deepEqual(dom_warnings.map(normalize_warning), expected_warnings);

@ -19,8 +19,6 @@ describe('stats', () => {
const filename = `test/stats/samples/${dir}/input.svelte`; const filename = `test/stats/samples/${dir}/input.svelte`;
const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, ''); const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, '');
const expectedWarnings =
tryToLoadJson(`test/stats/samples/${dir}/warnings.json`) || [];
const expectedError = tryToLoadJson( const expectedError = tryToLoadJson(
`test/stats/samples/${dir}/error.json` `test/stats/samples/${dir}/error.json`
); );
@ -31,10 +29,6 @@ describe('stats', () => {
try { try {
result = svelte.compile(input, config.options); result = svelte.compile(input, config.options);
config.test(assert, result.stats); config.test(assert, result.stats);
if (result.stats.warnings.length || expectedWarnings.length) {
// TODO check warnings are added to stats.warnings
}
} catch (e) { } catch (e) {
error = e; error = e;
} }

@ -1,5 +0,0 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, []);
},
};

@ -1,5 +0,0 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, []);
},
};

@ -1,5 +0,0 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, []);
},
};

@ -1,5 +0,0 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, []);
},
};

@ -1,5 +0,0 @@
export default {
test(assert, stats) {
assert.deepEqual(stats.vars, []);
},
};

@ -24,21 +24,19 @@ describe("validate", () => {
let error; let error;
try { try {
const { stats } = svelte.compile(input, { let { warnings } = svelte.compile(input, {
dev: config.dev, dev: config.dev,
legacy: config.legacy, legacy: config.legacy,
generate: false generate: false
}); });
const warnings = stats.warnings.map(w => ({ assert.deepEqual(warnings.map(w => ({
code: w.code, code: w.code,
message: w.message, message: w.message,
pos: w.pos, pos: w.pos,
start: w.start, start: w.start,
end: w.end end: w.end
})); })), expected_warnings);
assert.deepEqual(warnings, expected_warnings);
} catch (e) { } catch (e) {
error = e; error = e;
} }
@ -78,12 +76,12 @@ describe("validate", () => {
}); });
it("warns if options.name is not capitalised", () => { it("warns if options.name is not capitalised", () => {
const { stats } = svelte.compile("<div></div>", { const { warnings } = svelte.compile("<div></div>", {
name: "lowercase", name: "lowercase",
generate: false generate: false
}); });
assert.deepEqual(stats.warnings.map(w => ({ assert.deepEqual(warnings.map(w => ({
code: w.code, code: w.code,
message: w.message message: w.message
})), [{ })), [{
@ -93,11 +91,11 @@ describe("validate", () => {
}); });
it("does not warn if options.name begins with non-alphabetic character", () => { it("does not warn if options.name begins with non-alphabetic character", () => {
const { stats } = svelte.compile("<div></div>", { const { warnings } = svelte.compile("<div></div>", {
name: "_", name: "_",
generate: false generate: false
}); });
assert.deepEqual(stats.warnings, []); assert.deepEqual(warnings, []);
}); });
}); });

@ -0,0 +1,60 @@
import * as fs from 'fs';
import * as assert from 'assert';
import { svelte, loadConfig, tryToLoadJson } from '../helpers.js';
describe('vars', () => {
fs.readdirSync('test/vars/samples').forEach(dir => {
if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test
const solo = /\.solo/.test(dir);
const skip = /\.skip/.test(dir);
if (solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = loadConfig(`./vars/samples/${dir}/_config.js`);
const filename = `test/vars/samples/${dir}/input.svelte`;
const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, '');
const expectedError = tryToLoadJson(
`test/vars/samples/${dir}/error.json`
);
let result;
let error;
try {
result = svelte.compile(input, config.options);
config.test(assert, result.vars);
} catch (e) {
error = e;
}
if (error || expectedError) {
if (error && !expectedError) {
throw error;
}
if (expectedError && !error) {
throw new Error(`Expected an error: ${expectedError.message}`);
}
assert.equal(error.message, expectedError.message);
assert.deepEqual(error.start, expectedError.start);
assert.deepEqual(error.end, expectedError.end);
assert.equal(error.pos, expectedError.pos);
}
});
});
it('returns a vars object when options.generate is false', () => {
const { vars } = svelte.compile('', {
generate: false
});
assert.ok(Array.isArray(vars));
});
});

@ -0,0 +1,5 @@
export default {
test(assert, vars) {
assert.deepEqual(vars, []);
},
};

@ -1,6 +1,6 @@
export default { export default {
test(assert, stats) { test(assert, vars) {
assert.deepEqual(stats.vars, [ assert.deepEqual(vars, [
{ {
name: 'console', name: 'console',
injected: false, injected: false,

@ -1,6 +1,6 @@
export default { export default {
test(assert, stats) { test(assert, vars) {
assert.deepEqual(stats.vars, [ assert.deepEqual(vars, [
{ {
name: 'foo', name: 'foo',
injected: false, injected: false,

@ -0,0 +1,5 @@
export default {
test(assert, vars) {
assert.deepEqual(vars, []);
},
};

@ -1,6 +1,6 @@
export default { export default {
test(assert, stats) { test(assert, vars) {
assert.deepEqual(stats.vars, [ assert.deepEqual(vars, [
{ {
name: 'a', name: 'a',
injected: false, injected: false,

@ -0,0 +1,5 @@
export default {
test(assert, vars) {
assert.deepEqual(vars, []);
},
};

@ -1,6 +1,6 @@
export default { export default {
test(assert, stats) { test(assert, vars) {
assert.deepEqual(stats.vars, [ assert.deepEqual(vars, [
{ {
name: 'x', name: 'x',
export_name: null, export_name: null,

@ -1,6 +1,6 @@
export default { export default {
test(assert, stats) { test(assert, vars) {
assert.deepEqual(stats.vars, [ assert.deepEqual(vars, [
{ {
name: 'count', name: 'count',
export_name: null, export_name: null,

@ -1,6 +1,6 @@
export default { export default {
test(assert, stats) { test(assert, vars) {
assert.deepEqual(stats.vars, [ assert.deepEqual(vars, [
{ {
name: 'count', name: 'count',
export_name: null, export_name: null,

@ -1,6 +1,6 @@
export default { export default {
test(assert, stats) { test(assert, vars) {
assert.deepEqual(stats.vars, [ assert.deepEqual(vars, [
{ {
name: 'name', name: 'name',
export_name: 'name', export_name: 'name',

@ -1,6 +1,6 @@
export default { export default {
test(assert, stats) { test(assert, vars) {
assert.deepEqual(stats.vars, [ assert.deepEqual(vars, [
{ {
name: 'foo', name: 'foo',
export_name: null, export_name: null,

@ -1,6 +1,6 @@
export default { export default {
test(assert, stats) { test(assert, vars) {
assert.deepEqual(stats.vars, [ assert.deepEqual(vars, [
{ {
name: 'foo', name: 'foo',
export_name: null, export_name: null,

@ -0,0 +1,5 @@
export default {
test(assert, vars) {
assert.deepEqual(vars, []);
},
};

@ -0,0 +1,5 @@
export default {
test(assert, vars) {
assert.deepEqual(vars, []);
},
};
Loading…
Cancel
Save