From 137893a4fdd3d2b901adca31e30d916df925b108 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 6 Jan 2020 11:45:48 -0500 Subject: [PATCH] fix(compiler/v-on): handle multiple statements in v-on handler (close #572) --- .../__tests__/transforms/vOn.spec.ts | 41 +++++++++++++++++++ .../src/transforms/transformExpression.ts | 15 +++++-- packages/compiler-core/src/transforms/vOn.ts | 7 ++-- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/vOn.spec.ts b/packages/compiler-core/__tests__/transforms/vOn.spec.ts index 7dfe92a0026..596d7352d09 100644 --- a/packages/compiler-core/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vOn.spec.ts @@ -140,6 +140,22 @@ describe('compiler: transform v-on', () => { }) }) + test('should handle multiple inline statement', () => { + const { node } = parseWithVOn(`
`) + const props = (node.codegenNode as CallExpression) + .arguments[1] as ObjectExpression + expect(props.properties[0]).toMatchObject({ + key: { content: `onClick` }, + value: { + type: NodeTypes.COMPOUND_EXPRESSION, + // should wrap with `{` for multiple statements + // in this case the return value is discarded and the behavior is + // consistent with 2.x + children: [`$event => {`, { content: `foo();bar()` }, `}`] + } + }) + }) + test('inline statement w/ prefixIdentifiers: true', () => { const { node } = parseWithVOn(`
`, { prefixIdentifiers: true @@ -163,6 +179,31 @@ describe('compiler: transform v-on', () => { }) }) + test('multiple inline statements w/ prefixIdentifiers: true', () => { + const { node } = parseWithVOn(`
`, { + prefixIdentifiers: true + }) + const props = (node.codegenNode as CallExpression) + .arguments[1] as ObjectExpression + expect(props.properties[0]).toMatchObject({ + key: { content: `onClick` }, + value: { + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + `$event => {`, + { content: `_ctx.foo` }, + `(`, + // should NOT prefix $event + { content: `$event` }, + `);`, + { content: `_ctx.bar` }, + `()`, + `}` + ] + } + }) + }) + test('should NOT wrap as function if expression is already function expression', () => { const { node } = parseWithVOn(`
`) const props = (node.codegenNode as CallExpression) diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 6e10782e627..1dde9258fe0 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -76,7 +76,9 @@ export function processExpression( context: TransformContext, // some expressions like v-slot props & v-for aliases should be parsed as // function params - asParams: boolean = false + asParams = false, + // v-on handler values may contain multiple statements + asRawStatements = false ): ExpressionNode { if (!context.prefixIdentifiers || !node.content.trim()) { return node @@ -100,9 +102,14 @@ export function processExpression( } let ast: any - // if the expression is supposed to be used in a function params position - // we need to parse it differently. - const source = `(${rawExp})${asParams ? `=>{}` : ``}` + // exp needs to be parsed differently: + // 1. Multiple inline statements (v-on, with presence of `;`): parse as raw + // exp, but make sure to pad with spaces for consistent ranges + // 2. Expressions: wrap with parens (for e.g. object expressions) + // 3. Function arguments (v-for, v-slot): place in a function argument position + const source = asRawStatements + ? ` ${rawExp} ` + : `(${rawExp})${asParams ? `=>{}` : ``}` try { ast = parseJS(source, { ranges: true }) } catch (e) { diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index ae56e0b9825..a43f5cbe858 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -59,11 +59,12 @@ export const transformOn: DirectiveTransform = ( if (exp) { const isMemberExp = isMemberExpression(exp.content) const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content)) + const hasMultipleStatements = exp.content.includes(`;`) // process the expression since it's been skipped if (!__BROWSER__ && context.prefixIdentifiers) { context.addIdentifiers(`$event`) - exp = processExpression(exp, context) + exp = processExpression(exp, context, false, hasMultipleStatements) context.removeIdentifiers(`$event`) // with scope analysis, the function is hoistable if it has no reference // to scope variables. @@ -85,9 +86,9 @@ export const transformOn: DirectiveTransform = ( if (isInlineStatement || (isCacheable && isMemberExp)) { // wrap inline statement in a function expression exp = createCompoundExpression([ - `$event => (`, + `$event => ${hasMultipleStatements ? `{` : `(`}`, ...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children), - `)` + hasMultipleStatements ? `}` : `)` ]) } }