diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index c77127b53f..543822ebe1 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -143,6 +143,26 @@ export default function tag(parser: Parser) { } } + if (name === 'slot') { + let i = parser.stack.length; + while (i--) { + const item = parser.stack[i]; + if (item.type === 'EachBlock') { + parser.error( + ` cannot be a child of an each-block`, + start + ); + } + + if (item.type === 'Element' && item.name === 'slot') { + parser.error( + ` elements cannot be nested`, + start + ); + } + } + } + const attributes = []; const uniqueNames = new Set(); diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts index 6658895c58..ce412382c6 100644 --- a/src/validate/html/validateElement.ts +++ b/src/validate/html/validateElement.ts @@ -11,6 +11,35 @@ export default function validateElement(validator: Validator, node: Node, refs: validator.warn(`${node.name} component is not defined`, node.start); } + if (node.name === 'slot') { + const nameAttribute = node.attributes.find((attribute: Node) => attribute.name === 'name'); + if (nameAttribute) { + if (nameAttribute.value.length !== 1 || nameAttribute.value[0].type !== 'Text') { + validator.error(` name cannot be dynamic`, nameAttribute.start); + } + + const slotName = nameAttribute.value[0].data; + if (slotName === 'default') { + validator.error(`default is a reserved word — it cannot be used as a slot name`, nameAttribute.start); + } + + // TODO should duplicate slots be disallowed? Feels like it's more likely to be a + // bug than anything. Perhaps it should be a warning + + // if (validator.slots.has(slotName)) { + // validator.error(`duplicate '${slotName}' element`, nameAttribute.start); + // } + + // validator.slots.add(slotName); + } else { + // if (validator.slots.has('default')) { + // validator.error(`duplicate default element`, node.start); + // } + + // validator.slots.add('default'); + } + } + let hasIntro: boolean; let hasOutro: boolean; let hasTransition: boolean; @@ -136,6 +165,13 @@ export default function validateElement(validator: Validator, node: Node, refs: ); } } + + if (attribute.name === 'slot' && !isComponent && isDynamic(attribute)) { + validator.error( + `slot attribute cannot have a dynamic value`, + attribute.start + ); + } } }); } @@ -150,7 +186,7 @@ function checkTypeAttribute(validator: Validator, node: Node) { validator.error(`'type' attribute must be specified`, attribute.start); } - if (attribute.value.length > 1 || attribute.value[0].type !== 'Text') { + if (isDynamic(attribute)) { validator.error( `'type' attribute cannot be dynamic if input uses two-way binding`, attribute.start @@ -159,3 +195,7 @@ function checkTypeAttribute(validator: Validator, node: Node) { return attribute.value[0].data; } + +function isDynamic(attribute: Node) { + return attribute.value.length > 1 || attribute.value[0].type !== 'Text'; +} \ No newline at end of file diff --git a/src/validate/index.ts b/src/validate/index.ts index d29a054c9a..17c3e83be9 100644 --- a/src/validate/index.ts +++ b/src/validate/index.ts @@ -32,6 +32,7 @@ export class Validator { methods: Map; helpers: Map; transitions: Map; + slots: Set; constructor(parsed: Parsed, source: string, options: CompileOptions) { this.source = source; @@ -47,6 +48,7 @@ export class Validator { this.methods = new Map(); this.helpers = new Map(); this.transitions = new Map(); + this.slots = new Set(); } error(message: string, pos: number) { diff --git a/test/validator/index.js b/test/validator/index.js index 9b4f4900b7..176d060faf 100644 --- a/test/validator/index.js +++ b/test/validator/index.js @@ -8,12 +8,13 @@ describe("validate", () => { // 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 : it)(dir, () => { + (solo ? it.only : skip ? it.skip : it)(dir, () => { const filename = `test/validator/samples/${dir}/input.html`; const input = fs.readFileSync(filename, "utf-8").replace(/\s+$/, ""); diff --git a/test/validator/samples/component-slot-default-duplicate.skip/errors.json b/test/validator/samples/component-slot-default-duplicate.skip/errors.json new file mode 100644 index 0000000000..10a7789168 --- /dev/null +++ b/test/validator/samples/component-slot-default-duplicate.skip/errors.json @@ -0,0 +1,8 @@ +[{ + "message": "duplicate default element", + "loc": { + "line": 2, + "column": 0 + }, + "pos": 14 +}] \ No newline at end of file diff --git a/test/validator/samples/component-slot-default-duplicate.skip/input.html b/test/validator/samples/component-slot-default-duplicate.skip/input.html new file mode 100644 index 0000000000..9dffac4144 --- /dev/null +++ b/test/validator/samples/component-slot-default-duplicate.skip/input.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/test/validator/samples/component-slot-default-reserved/errors.json b/test/validator/samples/component-slot-default-reserved/errors.json new file mode 100644 index 0000000000..f951f61a5d --- /dev/null +++ b/test/validator/samples/component-slot-default-reserved/errors.json @@ -0,0 +1,8 @@ +[{ + "message": "default is a reserved word — it cannot be used as a slot name", + "loc": { + "line": 1, + "column": 6 + }, + "pos": 6 +}] \ No newline at end of file diff --git a/test/validator/samples/component-slot-default-reserved/input.html b/test/validator/samples/component-slot-default-reserved/input.html new file mode 100644 index 0000000000..397ae0aaf5 --- /dev/null +++ b/test/validator/samples/component-slot-default-reserved/input.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/validator/samples/component-slot-dynamic-attribute/errors.json b/test/validator/samples/component-slot-dynamic-attribute/errors.json new file mode 100644 index 0000000000..3d88aa3367 --- /dev/null +++ b/test/validator/samples/component-slot-dynamic-attribute/errors.json @@ -0,0 +1,8 @@ +[{ + "message": "slot attribute cannot have a dynamic value", + "loc": { + "line": 2, + "column": 9 + }, + "pos": 18 +}] \ No newline at end of file diff --git a/test/validator/samples/component-slot-dynamic-attribute/input.html b/test/validator/samples/component-slot-dynamic-attribute/input.html new file mode 100644 index 0000000000..1827eb2dd3 --- /dev/null +++ b/test/validator/samples/component-slot-dynamic-attribute/input.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/test/validator/samples/component-slot-dynamic/errors.json b/test/validator/samples/component-slot-dynamic/errors.json new file mode 100644 index 0000000000..38b45bb364 --- /dev/null +++ b/test/validator/samples/component-slot-dynamic/errors.json @@ -0,0 +1,8 @@ +[{ + "message": " name cannot be dynamic", + "loc": { + "line": 1, + "column": 6 + }, + "pos": 6 +}] \ No newline at end of file diff --git a/test/validator/samples/component-slot-dynamic/input.html b/test/validator/samples/component-slot-dynamic/input.html new file mode 100644 index 0000000000..4636cd4973 --- /dev/null +++ b/test/validator/samples/component-slot-dynamic/input.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/validator/samples/component-slot-each-block/errors.json b/test/validator/samples/component-slot-each-block/errors.json new file mode 100644 index 0000000000..c85d7fee58 --- /dev/null +++ b/test/validator/samples/component-slot-each-block/errors.json @@ -0,0 +1,8 @@ +[{ + "message": " cannot be a child of an each-block", + "loc": { + "line": 2, + "column": 1 + }, + "pos": 27 +}] \ No newline at end of file diff --git a/test/validator/samples/component-slot-each-block/input.html b/test/validator/samples/component-slot-each-block/input.html new file mode 100644 index 0000000000..2e4f7cb812 --- /dev/null +++ b/test/validator/samples/component-slot-each-block/input.html @@ -0,0 +1,3 @@ +{{#each things as thing}} + +{{/each}} \ No newline at end of file diff --git a/test/validator/samples/component-slot-named-duplicate.skip/errors.json b/test/validator/samples/component-slot-named-duplicate.skip/errors.json new file mode 100644 index 0000000000..535a049c0d --- /dev/null +++ b/test/validator/samples/component-slot-named-duplicate.skip/errors.json @@ -0,0 +1,8 @@ +[{ + "message": "duplicate 'foo' element", + "loc": { + "line": 2, + "column": 6 + }, + "pos": 31 +}] \ No newline at end of file diff --git a/test/validator/samples/component-slot-named-duplicate.skip/input.html b/test/validator/samples/component-slot-named-duplicate.skip/input.html new file mode 100644 index 0000000000..b0affc0608 --- /dev/null +++ b/test/validator/samples/component-slot-named-duplicate.skip/input.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/test/validator/samples/component-slot-nested/errors.json b/test/validator/samples/component-slot-nested/errors.json new file mode 100644 index 0000000000..9d19da7fd4 --- /dev/null +++ b/test/validator/samples/component-slot-nested/errors.json @@ -0,0 +1,8 @@ +[{ + "message": " elements cannot be nested", + "loc": { + "line": 2, + "column": 1 + }, + "pos": 8 +}] \ No newline at end of file diff --git a/test/validator/samples/component-slot-nested/input.html b/test/validator/samples/component-slot-nested/input.html new file mode 100644 index 0000000000..6365f729f4 --- /dev/null +++ b/test/validator/samples/component-slot-nested/input.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file