fix(compiler/v-on): handle multiple statements in v-on handler (close #572)

This commit is contained in:
Evan You 2020-01-06 11:45:48 -05:00
parent 46a793717a
commit 137893a4fd
3 changed files with 56 additions and 7 deletions

View File

@ -140,6 +140,22 @@ describe('compiler: transform v-on', () => {
}) })
}) })
test('should handle multiple inline statement', () => {
const { node } = parseWithVOn(`<div @click="foo();bar()"/>`)
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', () => { test('inline statement w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="foo($event)"/>`, { const { node } = parseWithVOn(`<div @click="foo($event)"/>`, {
prefixIdentifiers: true prefixIdentifiers: true
@ -163,6 +179,31 @@ describe('compiler: transform v-on', () => {
}) })
}) })
test('multiple inline statements w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="foo($event);bar()"/>`, {
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', () => { test('should NOT wrap as function if expression is already function expression', () => {
const { node } = parseWithVOn(`<div @click="$event => foo($event)"/>`) const { node } = parseWithVOn(`<div @click="$event => foo($event)"/>`)
const props = (node.codegenNode as CallExpression) const props = (node.codegenNode as CallExpression)

View File

@ -76,7 +76,9 @@ export function processExpression(
context: TransformContext, context: TransformContext,
// some expressions like v-slot props & v-for aliases should be parsed as // some expressions like v-slot props & v-for aliases should be parsed as
// function params // function params
asParams: boolean = false asParams = false,
// v-on handler values may contain multiple statements
asRawStatements = false
): ExpressionNode { ): ExpressionNode {
if (!context.prefixIdentifiers || !node.content.trim()) { if (!context.prefixIdentifiers || !node.content.trim()) {
return node return node
@ -100,9 +102,14 @@ export function processExpression(
} }
let ast: any let ast: any
// if the expression is supposed to be used in a function params position // exp needs to be parsed differently:
// we need to parse it differently. // 1. Multiple inline statements (v-on, with presence of `;`): parse as raw
const source = `(${rawExp})${asParams ? `=>{}` : ``}` // 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 { try {
ast = parseJS(source, { ranges: true }) ast = parseJS(source, { ranges: true })
} catch (e) { } catch (e) {

View File

@ -59,11 +59,12 @@ export const transformOn: DirectiveTransform = (
if (exp) { if (exp) {
const isMemberExp = isMemberExpression(exp.content) const isMemberExp = isMemberExpression(exp.content)
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content)) const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
const hasMultipleStatements = exp.content.includes(`;`)
// process the expression since it's been skipped // process the expression since it's been skipped
if (!__BROWSER__ && context.prefixIdentifiers) { if (!__BROWSER__ && context.prefixIdentifiers) {
context.addIdentifiers(`$event`) context.addIdentifiers(`$event`)
exp = processExpression(exp, context) exp = processExpression(exp, context, false, hasMultipleStatements)
context.removeIdentifiers(`$event`) context.removeIdentifiers(`$event`)
// with scope analysis, the function is hoistable if it has no reference // with scope analysis, the function is hoistable if it has no reference
// to scope variables. // to scope variables.
@ -85,9 +86,9 @@ export const transformOn: DirectiveTransform = (
if (isInlineStatement || (isCacheable && isMemberExp)) { if (isInlineStatement || (isCacheable && isMemberExp)) {
// wrap inline statement in a function expression // wrap inline statement in a function expression
exp = createCompoundExpression([ exp = createCompoundExpression([
`$event => (`, `$event => ${hasMultipleStatements ? `{` : `(`}`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children), ...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
`)` hasMultipleStatements ? `}` : `)`
]) ])
} }
} }