diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts index d36b00b833..9a3b573505 100644 --- 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 index 72ca2a6698..606b0399d8 100644 --- a/src/validate/html/validateElement.ts +++ b/src/validate/html/validateElement.ts @@ -10,6 +10,7 @@ export default function validateElement( node: Node, refs: Map, 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 + ); + } } } }); 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 index 0000000000..eca404b1e2 --- /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 index 0000000000..81166f3e54 --- /dev/null +++ b/test/validator/samples/component-slotted-each-block/input.html @@ -0,0 +1,15 @@ + + {{#each things as thing}} +
{{thing}}
+ {{/each}} +
+ + \ 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 index 0000000000..7a3a34b9f7 --- /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 index 0000000000..d063ab03aa --- /dev/null +++ b/test/validator/samples/component-slotted-if-block/input.html @@ -0,0 +1,15 @@ + + {{#if thing}} +
{{thing}}
+ {{/if}} +
+ + \ No newline at end of file