})
})
+ test('should wrap as function if expression is inline statement', () => {
+ const node = parseWithVOn(`<div @click="i++"/>`)
+ const props = node.codegenNode!.arguments[1] as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: { content: `onClick` },
+ value: {
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [`$event => (`, { content: `i++` }, `)`]
+ }
+ })
+ })
+
+ test('inline statement w/ prefixIdentifiers: true', () => {
+ const node = parseWithVOn(`<div @click="foo($event)"/>`, {
+ prefixIdentifiers: true
+ })
+ const props = node.codegenNode!.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` },
+ `)`,
+ `)`
+ ]
+ }
+ })
+ })
+
+ test('should NOT wrap as function if expression is already function expression', () => {
+ const node = parseWithVOn(`<div @click="$event => foo($event)"/>`)
+ const props = node.codegenNode!.arguments[1] as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: { content: `onClick` },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `$event => foo($event)`
+ }
+ })
+ })
+
+ test('function expression w/ prefixIdentifiers: true', () => {
+ const node = parseWithVOn(`<div @click="e => foo(e)"/>`, {
+ prefixIdentifiers: true
+ })
+ const props = node.codegenNode!.arguments[1] as ObjectExpression
+ expect(props.properties[0]).toMatchObject({
+ key: { content: `onClick` },
+ value: {
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ { content: `e` },
+ ` => `,
+ { content: `_ctx.foo` },
+ `(`,
+ { content: `e` },
+ `)`
+ ]
+ }
+ })
+ })
+
test('should error if no expression AND no modifier', () => {
const onError = jest.fn()
parseWithVOn(`<div v-on:click />`, { onError })
replaceNode(node: TemplateChildNode): void
removeNode(node?: TemplateChildNode): void
onNodeRemoved: () => void
- addIdentifiers(exp: ExpressionNode): void
- removeIdentifiers(exp: ExpressionNode): void
+ addIdentifiers(exp: ExpressionNode | string): void
+ removeIdentifiers(exp: ExpressionNode | string): void
hoist(exp: JSChildNode): SimpleExpressionNode
}
addIdentifiers(exp) {
// identifier tracking only happens in non-browser builds.
if (!__BROWSER__) {
- if (exp.identifiers) {
+ if (isString(exp)) {
+ addId(exp)
+ } else if (exp.identifiers) {
exp.identifiers.forEach(addId)
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
addId(exp.content)
},
removeIdentifiers(exp) {
if (!__BROWSER__) {
- if (exp.identifiers) {
+ if (isString(exp)) {
+ removeId(exp)
+ } else if (exp.identifiers) {
exp.identifiers.forEach(removeId)
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
removeId(exp.content)
// handle directives on element
for (let i = 0; i < node.props.length; i++) {
const dir = node.props[i]
- // do not process for v-for since it's special handled
+ // do not process for v-on & v-for since they are special handled
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
const exp = dir.exp as SimpleExpressionNode | undefined
const arg = dir.arg as SimpleExpressionNode | undefined
- if (exp) {
+ // do not process exp if this is v-on:arg - we need special handling
+ // for wrapping inline statements.
+ if (exp && !(dir.name === 'on' && arg)) {
dir.exp = processExpression(
exp,
context,
createSimpleExpression,
ExpressionNode,
NodeTypes,
- createCompoundExpression
+ createCompoundExpression,
+ SimpleExpressionNode
} from '../ast'
import { capitalize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
+import { processExpression } from './transformExpression'
+
+const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/
+const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/
// v-on without arg is handled directly in ./element.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-on
// *with* args.
export const transformOn: DirectiveTransform = (dir, context) => {
- const { exp, loc, modifiers } = dir
+ const { loc, modifiers } = dir
const arg = dir.arg!
- if (!exp && !modifiers.length) {
+ if (!dir.exp && !modifiers.length) {
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
}
let eventName: ExpressionNode
}
// TODO .once modifier handling since it is platform agnostic
// other modifiers are handled in compiler-dom
+
+ // handler processing
+ if (dir.exp) {
+ // exp is guarunteed to be a simple expression here because v-on w/ arg is
+ // skipped by transformExpression as a special case.
+ let exp: ExpressionNode = dir.exp as SimpleExpressionNode
+ const isInlineStatement = !(
+ simplePathRE.test(exp.content) || fnExpRE.test(exp.content)
+ )
+ // process the expression since it's been skipped
+ if (!__BROWSER__ && context.prefixIdentifiers) {
+ context.addIdentifiers(`$event`)
+ exp = processExpression(exp, context)
+ context.removeIdentifiers(`$event`)
+ }
+ if (isInlineStatement) {
+ // wrap inline statement in a function expression
+ exp = createCompoundExpression([
+ `$event => (`,
+ ...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
+ `)`
+ ])
+ }
+ dir.exp = exp
+ }
+
return {
props: createObjectProperty(
eventName,
- exp || createSimpleExpression(`() => {}`, false, loc)
+ dir.exp || createSimpleExpression(`() => {}`, false, loc)
),
needRuntime: false
}