diff --git a/src/generators/dom/visitors/Element/EventHandler.ts b/src/generators/dom/visitors/Element/EventHandler.ts index eb96c4e63674..56fdb42a8710 100644 --- a/src/generators/dom/visitors/Element/EventHandler.ts +++ b/src/generators/dom/visitors/Element/EventHandler.ts @@ -16,30 +16,33 @@ export default function visitEventHandler( const isCustomEvent = generator.events.has(name); const shouldHoist = !isCustomEvent && state.inEachBlock; - generator.addSourcemapLocations(attribute.expression); - - const flattened = flattenReference(attribute.expression.callee); - if (flattened.name !== 'event' && flattened.name !== 'this') { - // allow event.stopPropagation(), this.select() etc - // TODO verify that it's a valid callee (i.e. built-in or declared method) - generator.code.prependRight( - attribute.expression.start, - `${block.alias('component')}.` - ); - if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works! - } - const context = shouldHoist ? null : state.parentNode; const usedContexts: string[] = []; - attribute.expression.arguments.forEach((arg: Node) => { - const { contexts } = block.contextualise(arg, context, true); - contexts.forEach(context => { - if (!~usedContexts.indexOf(context)) usedContexts.push(context); - if (!~state.allUsedContexts.indexOf(context)) - state.allUsedContexts.push(context); + if (attribute.expression) { + generator.addSourcemapLocations(attribute.expression); + + const flattened = flattenReference(attribute.expression.callee); + if (flattened.name !== 'event' && flattened.name !== 'this') { + // allow event.stopPropagation(), this.select() etc + // TODO verify that it's a valid callee (i.e. built-in or declared method) + generator.code.prependRight( + attribute.expression.start, + `${block.alias('component')}.` + ); + if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works! + } + + attribute.expression.arguments.forEach((arg: Node) => { + const { contexts } = block.contextualise(arg, context, true); + + contexts.forEach(context => { + if (!~usedContexts.indexOf(context)) usedContexts.push(context); + if (!~state.allUsedContexts.indexOf(context)) + state.allUsedContexts.push(context); + }); }); - }); + } const _this = context || 'this'; const declarations = usedContexts.map(name => { @@ -66,7 +69,9 @@ export default function visitEventHandler( ${state.usesComponent && `var ${block.alias('component')} = ${_this}._svelte.component;`} ${declarations} - [✂${attribute.expression.start}-${attribute.expression.end}✂]; + ${attribute.expression ? + `[✂${attribute.expression.start}-${attribute.expression.end}✂];` : + `${block.alias('component')}.fire('${attribute.name}', event);`} `; if (isCustomEvent) { diff --git a/src/parse/index.ts b/src/parse/index.ts index 514e074fd4ae..b9748515e5a9 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -111,6 +111,8 @@ export class Parser { if (required) { this.error(`Expected ${str}`); } + + return false; } match(str: string) { diff --git a/src/parse/read/directives.ts b/src/parse/read/directives.ts index 6064a61fcbce..0b61ad635025 100644 --- a/src/parse/read/directives.ts +++ b/src/parse/read/directives.ts @@ -43,16 +43,21 @@ function readExpression(parser: Parser, start: number, quoteMark) { export function readEventHandlerDirective( parser: Parser, start: number, - name: string + name: string, + hasValue: boolean ) { - const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null; + let expression; - const expressionStart = parser.index; + if (hasValue) { + const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null; - const expression = readExpression(parser, expressionStart, quoteMark); + const expressionStart = parser.index; - if (expression.type !== 'CallExpression') { - parser.error(`Expected call expression`, expressionStart); + expression = readExpression(parser, expressionStart, quoteMark); + + if (expression.type !== 'CallExpression') { + parser.error(`Expected call expression`, expressionStart); + } } return { diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index 6fee187dd926..cfa056136578 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -256,8 +256,7 @@ function readAttribute(parser: Parser, uniqueNames) { parser.allowWhitespace(); if (/^on:/.test(name)) { - parser.eat('=', true); - return readEventHandlerDirective(parser, start, name.slice(3)); + return readEventHandlerDirective(parser, start, name.slice(3), parser.eat('=')); } if (/^bind:/.test(name)) { diff --git a/src/validate/html/validateEventHandler.ts b/src/validate/html/validateEventHandler.ts index 85fdad08e656..ec4476d3fbd6 100644 --- a/src/validate/html/validateEventHandler.ts +++ b/src/validate/html/validateEventHandler.ts @@ -9,6 +9,8 @@ export default function validateEventHandlerCallee( validator: Validator, attribute: Node ) { + if (!attribute.expression) return; + const { callee, start, type } = attribute.expression; if (type !== 'CallExpression') { diff --git a/test/runtime/samples/event-handler-shorthand/_config.js b/test/runtime/samples/event-handler-shorthand/_config.js new file mode 100644 index 000000000000..ceded0bf0809 --- /dev/null +++ b/test/runtime/samples/event-handler-shorthand/_config.js @@ -0,0 +1,13 @@ +export default { + html: ` + + `, + + test (assert, component, target, window) { + const button = target.querySelector('button'); + const event = new window.MouseEvent('click'); + + button.dispatchEvent(event); + assert.ok(component.clicked); + } +}; diff --git a/test/runtime/samples/event-handler-shorthand/main.html b/test/runtime/samples/event-handler-shorthand/main.html new file mode 100644 index 000000000000..5b283cbb5998 --- /dev/null +++ b/test/runtime/samples/event-handler-shorthand/main.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file