Repository: sveltejs/svelte (JavaScript)
Date: 2018-02-03 23:00:23+00:00
Task type: Feature

Issue:
Compiler should warn on empty style block
Small thing, but when tidying up components this would be helpful

[REPL](https://svelte.technology/repl?version=1.51.1&gist=b1fe8552385dbae6a8ca57f7f1c568ce)

```html
<div class='foo'>
  no styles
</div>

<style>
  .foo {
    /* this should be warned about */	
  }
</style>
```


Issue discussion:
Maybe it should only warn when built in production? I know in development I'll leave empty blocks to fill in later, having a warning when running in development seems annoying.
I agree w/ @PaulBGD I can't tell you how many times I leave empty CSS selectors *on purpose* as a reminder to come back—so this has the potential to become noise—

—feature flag?

—production only?

—feature creep?
Yeah, that makes sense actually. I'll often put selectors in just so I can tweak them in devtools. 

Solution patch:
diff --git a/src/css/Stylesheet.ts b/src/css/Stylesheet.ts
--- a/src/css/Stylesheet.ts
+++ b/src/css/Stylesheet.ts
@@ -25,12 +25,13 @@ class Rule {
 		this.selectors.forEach(selector => selector.apply(node, stack)); // TODO move the logic in here?
 	}
 
-	isUsed() {
+	isUsed(dev: boolean) {
 		if (this.parent && this.parent.node.type === 'Atrule' && this.parent.node.name === 'keyframes') return true;
+		if (this.declarations.length === 0) return dev;
 		return this.selectors.some(s => s.used);
 	}
 
-	minify(code: MagicString, cascade: boolean) {
+	minify(code: MagicString, cascade: boolean, dev: boolean) {
 		let c = this.node.start;
 		let started = false;
 
@@ -177,11 +178,11 @@ class Atrule {
 		}
 	}
 
-	isUsed() {
+	isUsed(dev: boolean) {
 		return true; // TODO
 	}
 
-	minify(code: MagicString, cascade: boolean) {
+	minify(code: MagicString, cascade: boolean, dev: boolean) {
 		if (this.node.name === 'media') {
 			const expressionChar = code.original[this.node.expression.start];
 			let c = this.node.start + (expressionChar === '(' ? 6 : 7);
@@ -206,9 +207,9 @@ class Atrule {
 			let c = this.node.block.start + 1;
 
 			this.children.forEach(child => {
-				if (cascade || child.isUsed()) {
+				if (cascade || child.isUsed(dev)) {
 					code.remove(c, child.node.start);
-					child.minify(code, cascade);
+					child.minify(code, cascade, dev);
 					c = child.node.end;
 				}
 			});
@@ -257,6 +258,7 @@ export default class Stylesheet {
 	parsed: Parsed;
 	cascade: boolean;
 	filename: string;
+	dev: boolean;
 
 	hasStyles: boolean;
 	id: string;
@@ -264,11 +266,12 @@ export default class Stylesheet {
 	children: (Rule|Atrule)[];
 	keyframes: Map<string, string>;
 
-	constructor(source: string, parsed: Parsed, filename: string, cascade: boolean) {
+	constructor(source: string, parsed: Parsed, filename: string, cascade: boolean, dev: boolean) {
 		this.source = source;
 		this.parsed = parsed;
 		this.cascade = cascade;
 		this.filename = filename;
+		this.dev = dev;
 
 		this.children = [];
 		this.keyframes = new Map();
@@ -374,9 +377,9 @@ export default class Stylesheet {
 
 		let c = 0;
 		this.children.forEach(child => {
-			if (this.cascade || child.isUsed()) {
+			if (this.cascade || child.isUsed(this.dev)) {
 				code.remove(c, child.node.start);
-				child.minify(code, this.cascade);
+				child.minify(code, this.cascade, this.dev);
 				c = child.node.end;
 			}
 		});
diff --git a/src/index.ts b/src/index.ts
--- a/src/index.ts
+++ b/src/index.ts
@@ -116,7 +116,7 @@ export function compile(source: string, _options: CompileOptions) {
 		return;
 	}
 
-	const stylesheet = new Stylesheet(source, parsed, options.filename, options.cascade !== false);
+	const stylesheet = new Stylesheet(source, parsed, options.filename, options.cascade !== false, options.dev);
 
 	validate(parsed, source, stylesheet, options);
 


Test patch:
diff --git a/test/css/samples/cascade-false-empty-rule-dev/_config.js b/test/css/samples/cascade-false-empty-rule-dev/_config.js
new file mode 100644
--- /dev/null
+++ b/test/css/samples/cascade-false-empty-rule-dev/_config.js
@@ -0,0 +1,4 @@
+export default {
+	cascade: false,
+	dev: true
+};
\ No newline at end of file
diff --git a/test/css/samples/cascade-false-empty-rule-dev/expected.css b/test/css/samples/cascade-false-empty-rule-dev/expected.css
new file mode 100644
--- /dev/null
+++ b/test/css/samples/cascade-false-empty-rule-dev/expected.css
@@ -0,0 +1 @@
+.foo[svelte-xyz]{}
\ No newline at end of file
diff --git a/test/css/samples/cascade-false-empty-rule-dev/input.html b/test/css/samples/cascade-false-empty-rule-dev/input.html
new file mode 100644
--- /dev/null
+++ b/test/css/samples/cascade-false-empty-rule-dev/input.html
@@ -0,0 +1,7 @@
+<div class='foo'></div>
+
+<style>
+	.foo {
+		/* empty */
+	}
+</style>
\ No newline at end of file
diff --git a/test/css/samples/cascade-false-empty-rule/_config.js b/test/css/samples/cascade-false-empty-rule/_config.js
new file mode 100644
--- /dev/null
+++ b/test/css/samples/cascade-false-empty-rule/_config.js
@@ -0,0 +1,3 @@
+export default {
+	cascade: false
+};
\ No newline at end of file
diff --git a/test/css/samples/cascade-false-empty-rule/expected.css b/test/css/samples/cascade-false-empty-rule/expected.css
new file mode 100644
diff --git a/test/css/samples/cascade-false-empty-rule/input.html b/test/css/samples/cascade-false-empty-rule/input.html
new file mode 100644
--- /dev/null
+++ b/test/css/samples/cascade-false-empty-rule/input.html
@@ -0,0 +1,7 @@
+<div class='foo'></div>
+
+<style>
+	.foo {
+		/* empty */
+	}
+</style>
\ No newline at end of file