Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
Conditional slot errors at runtime
Slot content added inside an if currently fails at runtime:
https://svelte.technology/repl?version=1.39.2&gist=d4a50532f6a783ada53f35a2b9a7f9e4

This can be easily fixed by moving the condition inside the slot:
https://svelte.technology/repl?version=1.39.2&gist=3bf75a560243feecdeaca013a0e7ad26

I don't know if it makes sense to allow slots inside conditionals, but if not, Svelte could at least error on their usage.


Discussion:

Resolving patch:
diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts
--- a/src/validate/html/index.ts
+++ b/src/validate/html/index.ts
@@ -11,6 +11,7 @@ const meta = new Map([[':Window', validateWindow]]);
 export default function validateHtml(validator: Validator, html: Node) {
 	const refs = new Map();
 	const refCallees: Node[] = [];
+	const stack: Node[] = [];
 	const elementStack: Node[] = [];
 
 	function visit(node: Node) {
@@ -21,7 +22,7 @@ export default function validateHtml(validator: Validator, html: Node) {
 				return meta.get(node.name)(validator, node, refs, refCallees);
 			}
 
-			validateElement(validator, node, refs, refCallees, elementStack);
+			validateElement(validator, node, refs, refCallees, stack, elementStack);
 		} else if (node.type === 'EachBlock') {
 			if (validator.helpers.has(node.context)) {
 				let c = node.expression.end;
@@ -40,7 +41,9 @@ export default function validateHtml(validator: Validator, html: Node) {
 
 		if (node.children) {
 			if (node.type === 'Element') elementStack.push(node);
+			stack.push(node);
 			node.children.forEach(visit);
+			stack.pop();
 			if (node.type === 'Element') elementStack.pop();
 		}
 
diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts
--- a/src/validate/html/validateElement.ts
+++ b/src/validate/html/validateElement.ts
@@ -10,6 +10,7 @@ export default function validateElement(
 	node: Node,
 	refs: Map<string, Node[]>,
 	refCallees: Node[],
+	stack: Node[],
 	elementStack: Node[]
 ) {
 	const isComponent =
@@ -189,11 +190,23 @@ export default function validateElement(
 				}
 			}
 
-			if (attribute.name === 'slot' && !isComponent && isDynamic(attribute)) {
-				validator.error(
-					`slot attribute cannot have a dynamic value`,
-					attribute.start
-				);
+			if (attribute.name === 'slot' && !isComponent) {
+				let i = stack.length;
+				while (i--) {
+					const parent = stack[i];
+					if (parent.type === 'Element' && validator.components.has(parent.name)) break;
+					if (parent.type === 'IfBlock' || parent.type === 'EachBlock') {
+						const message = `Cannot place slotted elements inside an ${parent.type === 'IfBlock' ? 'if' : 'each'}-block`;
+						validator.error(message, attribute.start);
+					}
+				}
+
+				if (isDynamic(attribute)) {
+					validator.error(
+						`slot attribute cannot have a dynamic value`,
+						attribute.start
+					);
+				}
 			}
 		}
 	});


Test patch:
diff --git a/test/validator/samples/component-slotted-each-block/errors.json b/test/validator/samples/component-slotted-each-block/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/component-slotted-each-block/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "Cannot place slotted elements inside an each-block",
+	"loc": {
+		"line": 3,
+		"column": 7
+	},
+	"pos": 43
+}]
\ No newline at end of file
diff --git a/test/validator/samples/component-slotted-each-block/input.html b/test/validator/samples/component-slotted-each-block/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/component-slotted-each-block/input.html
@@ -0,0 +1,15 @@
+<Nested>
+	{{#each things as thing}}
+		<div slot='foo'>{{thing}}</div>
+	{{/each}}
+</Nested>
+
+<script>
+	import Nested from './Nested.html';
+
+	export default {
+		components: {
+			Nested
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/component-slotted-if-block/errors.json b/test/validator/samples/component-slotted-if-block/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/component-slotted-if-block/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "Cannot place slotted elements inside an if-block",
+	"loc": {
+		"line": 3,
+		"column": 7
+	},
+	"pos": 31
+}]
\ No newline at end of file
diff --git a/test/validator/samples/component-slotted-if-block/input.html b/test/validator/samples/component-slotted-if-block/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/component-slotted-if-block/input.html
@@ -0,0 +1,15 @@
+<Nested>
+	{{#if thing}}
+		<div slot='foo'>{{thing}}</div>
+	{{/if}}
+</Nested>
+
+<script>
+	import Nested from './Nested.html';
+
+	export default {
+		components: {
+			Nested
+		}
+	};
+</script>
\ No newline at end of file