Repository: sveltejs/svelte (JavaScript)
Date: 2018-12-29 03:24:37+00:00
Task type: Bug Fix

Issue:
`stats.warnings` does not include all warnings
`stats.warnings` does not include any unused CSS selector warnings. There may be other warnings it's missing. We need to call `stats.warn` instead of `options.onwarn` whenever we have a warning.

This is present in v2 and v3.


Issue discussion:

Solution patch:
diff --git a/src/compile/Component.ts b/src/compile/Component.ts
--- a/src/compile/Component.ts
+++ b/src/compile/Component.ts
@@ -164,7 +164,7 @@ export default class Component {
 		this.fragment = new Fragment(this, ast.html);
 		if (!options.customElement) this.stylesheet.reify();
 
-		this.stylesheet.warnOnUnusedSelectors(options.onwarn);
+		this.stylesheet.warnOnUnusedSelectors(stats);
 
 		if (!this.instance_script) {
 			const props = [...this.template_references];
@@ -229,6 +229,7 @@ export default class Component {
 			format,
 			name,
 			options,
+			this.stats,
 			banner,
 			options.sveltePath,
 			importedHelpers,
diff --git a/src/compile/css/Stylesheet.ts b/src/compile/css/Stylesheet.ts
--- a/src/compile/css/Stylesheet.ts
+++ b/src/compile/css/Stylesheet.ts
@@ -8,6 +8,7 @@ import removeCSSPrefix from '../../utils/removeCSSPrefix';
 import Element from '../nodes/Element';
 import { Node, Ast, Warning } from '../../interfaces';
 import Component from '../Component';
+import Stats from '../../Stats';
 
 const isKeyframesNode = (node: Node) => removeCSSPrefix(node.name) === 'keyframes'
 
@@ -391,7 +392,7 @@ export default class Stylesheet {
 		});
 	}
 
-	warnOnUnusedSelectors(onwarn: (warning: Warning) => void) {
+	warnOnUnusedSelectors(stats: Stats) {
 		let locator;
 
 		const handler = (selector: Selector) => {
@@ -404,7 +405,7 @@ export default class Stylesheet {
 			const frame = getCodeFrame(this.source, start.line - 1, start.column);
 			const message = `Unused CSS selector`;
 
-			onwarn({
+			stats.warn({
 				code: `css-unused-selector`,
 				message,
 				frame,
diff --git a/src/compile/index.ts b/src/compile/index.ts
--- a/src/compile/index.ts
+++ b/src/compile/index.ts
@@ -6,17 +6,6 @@ import renderSSR from './render-ssr/index';
 import { CompileOptions, Warning, Ast } from '../interfaces';
 import Component from './Component';
 
-function normalize_options(options: CompileOptions): CompileOptions {
-	let normalized = assign({ generate: 'dom', dev: false }, options);
-	const { onwarn } = normalized;
-
-	normalized.onwarn = onwarn
-		? (warning: Warning) => onwarn(warning, default_onwarn)
-		: default_onwarn;
-
-	return normalized;
-}
-
 function default_onwarn({ start, message }: Warning) {
 	if (start) {
 		console.warn(`(${start.line}:${start.column}) – ${message}`);
@@ -45,10 +34,12 @@ function validate_options(options: CompileOptions, stats: Stats) {
 }
 
 export default function compile(source: string, options: CompileOptions = {}) {
-	options = normalize_options(options);
+	options = assign({ generate: 'dom', dev: false }, options);
 
 	const stats = new Stats({
 		onwarn: options.onwarn
+			? (warning: Warning) => options.onwarn(warning, default_onwarn)
+			: default_onwarn
 	});
 
 	let ast: Ast;
diff --git a/src/compile/wrapModule.ts b/src/compile/wrapModule.ts
--- a/src/compile/wrapModule.ts
+++ b/src/compile/wrapModule.ts
@@ -1,6 +1,7 @@
 import deindent from '../utils/deindent';
 import list from '../utils/list';
 import { CompileOptions, ModuleFormat, Node } from '../interfaces';
+import Stats from '../Stats';
 
 interface Dependency {
 	name: string;
@@ -20,6 +21,7 @@ export default function wrapModule(
 	format: ModuleFormat,
 	name: string,
 	options: CompileOptions,
+	stats: Stats,
 	banner: string,
 	sveltePath = 'svelte',
 	helpers: { name: string, alias: string }[],
@@ -34,7 +36,7 @@ export default function wrapModule(
 	}
 
 	if (format === 'cjs') return cjs(code, name, banner, sveltePath, internalPath, helpers, imports, module_exports);
-	if (format === 'eval') return expr(code, name, options, banner, imports);
+	if (format === 'eval') return expr(code, name, options, stats, banner, imports);
 
 	throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
 }
@@ -142,6 +144,7 @@ function expr(
 	code: string,
 	name: string,
 	options: CompileOptions,
+	stats: Stats,
 	banner: string,
 	imports: Node[]
 ) {
@@ -182,7 +185,7 @@ function expr(
 		return { name, statements, source: declaration.source.value };
 	});
 
-	const globals = getGlobals(dependencies, options);
+	const globals = getGlobals(dependencies, options, stats);
 
 	return deindent`
 		(function (${paramString(dependencies)}) { "use strict";
@@ -212,8 +215,8 @@ function getCompatibilityStatements(dependencies: Dependency[]) {
 	return statements.join('\n');
 }
 
-function getGlobals(dependencies: Dependency[], options: CompileOptions) {
-	const { globals, onwarn } = options;
+function getGlobals(dependencies: Dependency[], options: CompileOptions, stats: Stats) {
+	const { globals } = options;
 	const globalFn = getGlobalFn(globals);
 
 	return dependencies.map(d => {
@@ -225,12 +228,10 @@ function getGlobals(dependencies: Dependency[], options: CompileOptions) {
 					`Could not determine name for imported module '${d.source}' – use options.globals`
 				);
 			} else {
-				const warning = {
+				stats.warn({
 					code: `options-missing-globals`,
 					message: `No name was supplied for imported module '${d.source}'. Guessing '${d.name}', but you should use options.globals`,
-				};
-
-				onwarn(warning);
+				});
 			}
 
 			name = d.name;
diff --git a/src/interfaces.ts b/src/interfaces.ts
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -60,7 +60,7 @@ export interface CompileOptions {
 
 	preserveComments?: boolean | false;
 
-	onwarn?: (warning: Warning) => void;
+	onwarn?: (warning: Warning, default_onwarn?: (warning: Warning) => void) => void;
 }
 
 export interface Visitor {


Test patch:
diff --git a/test/css/index.js b/test/css/index.js
--- a/test/css/index.js
+++ b/test/css/index.js
@@ -69,6 +69,8 @@ describe('css', () => {
 				})
 			);
 
+			assert.deepEqual(dom.stats.warnings, domWarnings);
+
 			const ssr = svelte.compile(
 				input,
 				Object.assign(config, {
@@ -81,6 +83,8 @@ describe('css', () => {
 				})
 			);
 
+			assert.deepEqual(dom.stats.warnings, domWarnings);
+
 			assert.equal(dom.css.code, ssr.css.code);
 
 			assert.deepEqual(
diff --git a/test/validator/index.js b/test/validator/index.js
--- a/test/validator/index.js
+++ b/test/validator/index.js
@@ -83,43 +83,26 @@ describe("validate", () => {
 	});
 
 	it("warns if options.name is not capitalised", () => {
-		const warnings = [];
-		svelte.compile("<div></div>", {
+		const { stats } = svelte.compile("<div></div>", {
 			name: "lowercase",
-			onwarn(warning) {
-				warnings.push({
-					code: warning.code,
-					message: warning.message,
-					pos: warning.pos,
-					start: warning.start
-				});
-			},
 			generate: false
 		});
-		assert.deepEqual(warnings, [
-			{
-				code: `options-lowercase-name`,
-				message: "options.name should be capitalised",
-				pos: undefined,
-				start: undefined
-			}
-		]);
+
+		assert.deepEqual(stats.warnings.map(w => ({
+			code: w.code,
+			message: w.message
+		})), [{
+			code: `options-lowercase-name`,
+			message: "options.name should be capitalised"
+		}]);
 	});
 
 	it("does not warn if options.name begins with non-alphabetic character", () => {
-		const warnings = [];
-		svelte.compile("<div></div>", {
+		const { stats } = svelte.compile("<div></div>", {
 			name: "_",
-			onwarn(warning) {
-				warnings.push({
-					code: warning.code,
-					message: warning.message,
-					pos: warning.pos,
-					start: warning.start
-				});
-			},
 			generate: false
 		});
-		assert.deepEqual(warnings, []);
+		
+		assert.deepEqual(stats.warnings, []);
 	});
 });