]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler): v-on inline statement handling
authorEvan You <yyx990803@gmail.com>
Thu, 3 Oct 2019 21:47:00 +0000 (17:47 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 3 Oct 2019 21:47:00 +0000 (17:47 -0400)
packages/compiler-core/__tests__/transforms/vOn.spec.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/vOn.ts

index 8e92123daeac7e2784f0051f7b6ec2bad65dc86c..9a7ba45000d962707d0df9212e7654b67a2cbbff 100644 (file)
@@ -121,6 +121,73 @@ describe('compiler: transform v-on', () => {
     })
   })
 
+  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 })
index 370d4f461df66e78d432bc330eb4a0ea9a77be1c..588d3e4985bc5c07dc10165dc974ad3a6283b1bc 100644 (file)
@@ -72,8 +72,8 @@ export interface TransformContext extends Required<TransformOptions> {
   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
 }
 
@@ -152,7 +152,9 @@ function createTransformContext(
     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)
@@ -161,7 +163,9 @@ function createTransformContext(
     },
     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)
index 36c37833997217e45cbdb287ace7ec38f0ffbd17..a88b28a9c29811697f9e4f8865733d7c2a523404 100644 (file)
@@ -35,11 +35,13 @@ export const transformExpression: NodeTransform = (node, context) => {
     // 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,
index 2c89038f132eed0a0c43389d4863b721e2968fd7..a28ea544392e7da6a0b545429b46b126ec990f45 100644 (file)
@@ -4,18 +4,23 @@ import {
   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
@@ -37,10 +42,36 @@ export const transformOn: DirectiveTransform = (dir, context) => {
   }
   // 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
   }