import { DirectiveTransform, DirectiveTransformResult } from '../transform' import { createCompoundExpression, createObjectProperty, createSimpleExpression, DirectiveNode, ElementTypes, ExpressionNode, NodeTypes, SimpleExpressionNode } from '../ast' import { camelize, toHandlerKey } from '@vue/shared' import { createCompilerError, ErrorCodes } from '../errors' import { processExpression } from './transformExpression' import { validateBrowserExpression } from '../validateExpression' import { hasScopeRef, isMemberExpression } from '../utils' import { TO_HANDLER_KEY } from '../runtimeHelpers' const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^\s*function(?:\s+[\w$]+)?\s*\(/ export interface VOnDirectiveNode extends DirectiveNode { // v-on without arg is handled directly in ./transformElements.ts due to it affecting // codegen for the entire props object. This transform here is only for v-on // *with* args. arg: ExpressionNode // exp is guaranteed to be a simple expression here because v-on w/ arg is // skipped by transformExpression as a special case. exp: SimpleExpressionNode | undefined } export const transformOn: DirectiveTransform = ( dir, node, context, augmentor ) => { const { loc, modifiers, arg } = dir as VOnDirectiveNode if (!dir.exp && !modifiers.length) { context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc)) } let eventName: ExpressionNode if (arg.type === NodeTypes.SIMPLE_EXPRESSION) { if (arg.isStatic) { const rawName = arg.content // for all event listeners, auto convert it to camelCase. See issue #2249 eventName = createSimpleExpression( toHandlerKey(camelize(rawName)), true, arg.loc ) } else { // #2388 eventName = createCompoundExpression([ `${context.helperString(TO_HANDLER_KEY)}(`, arg, `)` ]) } } else { // already a compound expression. eventName = arg eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`) eventName.children.push(`)`) } // handler processing let exp: ExpressionNode | undefined = dir.exp as | SimpleExpressionNode | undefined if (exp && !exp.content.trim()) { exp = undefined } let shouldCache: boolean = context.cacheHandlers && !exp 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) { isInlineStatement && context.addIdentifiers(`$event`) exp = dir.exp = processExpression( exp, context, false, hasMultipleStatements ) isInlineStatement && context.removeIdentifiers(`$event`) // with scope analysis, the function is hoistable if it has no reference // to scope variables. shouldCache = context.cacheHandlers && // runtime constants don't need to be cached // (this is analyzed by compileScript in SFC