Browse Source

feat(compiler-core): support v-bind shorthand for key and value with the same name (#9451)

zhiyuanzmj 2 years ago
parent
commit
26399aa6fa

+ 117 - 3
packages/compiler-core/__tests__/transforms/vBind.spec.ts

@@ -72,6 +72,60 @@ describe('compiler: transform v-bind', () => {
     })
   })
 
+  test('no expression', () => {
+    const node = parseWithVBind(`<div v-bind:id />`)
+    const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+    expect(props.properties[0]).toMatchObject({
+      key: {
+        content: `id`,
+        isStatic: true,
+        loc: {
+          start: {
+            line: 1,
+            column: 13,
+            offset: 12
+          },
+          end: {
+            line: 1,
+            column: 15,
+            offset: 14
+          }
+        }
+      },
+      value: {
+        content: `id`,
+        isStatic: false,
+        loc: {
+          start: {
+            line: 1,
+            column: 1,
+            offset: 0
+          },
+          end: {
+            line: 1,
+            column: 1,
+            offset: 0
+          }
+        }
+      }
+    })
+  })
+
+  test('no expression (shorthand)', () => {
+    const node = parseWithVBind(`<div :id />`)
+    const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+    expect(props.properties[0]).toMatchObject({
+      key: {
+        content: `id`,
+        isStatic: true
+      },
+      value: {
+        content: `id`,
+        isStatic: false
+      }
+    })
+  })
+
   test('dynamic arg', () => {
     const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
     const props = (node.codegenNode as VNodeCall).props as CallExpression
@@ -98,9 +152,9 @@ describe('compiler: transform v-bind', () => {
     })
   })
 
-  test('should error if no expression', () => {
+  test('should error if empty expression', () => {
     const onError = vi.fn()
-    const node = parseWithVBind(`<div v-bind:arg />`, { onError })
+    const node = parseWithVBind(`<div v-bind:arg="" />`, { onError })
     const props = (node.codegenNode as VNodeCall).props as ObjectExpression
     expect(onError.mock.calls[0][0]).toMatchObject({
       code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
@@ -111,7 +165,7 @@ describe('compiler: transform v-bind', () => {
         },
         end: {
           line: 1,
-          column: 16
+          column: 19
         }
       }
     })
@@ -142,6 +196,21 @@ describe('compiler: transform v-bind', () => {
     })
   })
 
+  test('.camel modifier w/ no expression', () => {
+    const node = parseWithVBind(`<div v-bind:foo-bar.camel />`)
+    const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+    expect(props.properties[0]).toMatchObject({
+      key: {
+        content: `fooBar`,
+        isStatic: true
+      },
+      value: {
+        content: `fooBar`,
+        isStatic: false
+      }
+    })
+  })
+
   test('.camel modifier w/ dynamic arg', () => {
     const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
     const props = (node.codegenNode as VNodeCall).props as CallExpression
@@ -219,6 +288,21 @@ describe('compiler: transform v-bind', () => {
     })
   })
 
+  test('.prop modifier w/ no expression', () => {
+    const node = parseWithVBind(`<div v-bind:fooBar.prop />`)
+    const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+    expect(props.properties[0]).toMatchObject({
+      key: {
+        content: `.fooBar`,
+        isStatic: true
+      },
+      value: {
+        content: `fooBar`,
+        isStatic: false
+      }
+    })
+  })
+
   test('.prop modifier w/ dynamic arg', () => {
     const node = parseWithVBind(`<div v-bind:[fooBar].prop="id"/>`)
     const props = (node.codegenNode as VNodeCall).props as CallExpression
@@ -296,6 +380,21 @@ describe('compiler: transform v-bind', () => {
     })
   })
 
+  test('.prop modifier (shortband) w/ no expression', () => {
+    const node = parseWithVBind(`<div .fooBar />`)
+    const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+    expect(props.properties[0]).toMatchObject({
+      key: {
+        content: `.fooBar`,
+        isStatic: true
+      },
+      value: {
+        content: `fooBar`,
+        isStatic: false
+      }
+    })
+  })
+
   test('.attr modifier', () => {
     const node = parseWithVBind(`<div v-bind:foo-bar.attr="id"/>`)
     const props = (node.codegenNode as VNodeCall).props as ObjectExpression
@@ -310,4 +409,19 @@ describe('compiler: transform v-bind', () => {
       }
     })
   })
+
+  test('.attr modifier w/ no expression', () => {
+    const node = parseWithVBind(`<div v-bind:foo-bar.attr />`)
+    const props = (node.codegenNode as VNodeCall).props as ObjectExpression
+    expect(props.properties[0]).toMatchObject({
+      key: {
+        content: `^foo-bar`,
+        isStatic: true
+      },
+      value: {
+        content: `fooBar`,
+        isStatic: false
+      }
+    })
+  })
 })

+ 15 - 1
packages/compiler-core/src/transforms/vBind.ts

@@ -3,17 +3,19 @@ import {
   createObjectProperty,
   createSimpleExpression,
   ExpressionNode,
+  locStub,
   NodeTypes
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { camelize } from '@vue/shared'
 import { CAMELIZE } from '../runtimeHelpers'
+import { processExpression } from './transformExpression'
 
 // v-bind 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-bind
 // *with* args.
 export const transformBind: DirectiveTransform = (dir, _node, context) => {
-  const { exp, modifiers, loc } = dir
+  const { modifiers, loc } = dir
   const arg = dir.arg!
 
   if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
@@ -46,6 +48,18 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
     }
   }
 
+  // :arg is replaced by :arg="arg"
+  let { exp } = dir
+  if (!exp && arg.type === NodeTypes.SIMPLE_EXPRESSION) {
+    const propName = camelize(arg.loc.source)
+    const simpleExpression = createSimpleExpression(propName, false, {
+      ...locStub,
+      source: propName
+    })
+
+    exp = dir.exp = processExpression(simpleExpression, context)
+  }
+
   if (
     !exp ||
     (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())

+ 0 - 1
packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap

@@ -79,7 +79,6 @@ exports[`source map 1`] = `
 exports[`template errors 1`] = `
 [
   [SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
-  [SyntaxError: v-bind is missing expression.],
   [SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.],
 ]
 `;

+ 1 - 1
packages/compiler-sfc/__tests__/compileTemplate.spec.ts

@@ -124,7 +124,7 @@ test('source map', () => {
 test('template errors', () => {
   const result = compile({
     filename: 'example.vue',
-    source: `<div :foo
+    source: `<div
       :bar="a[" v-model="baz"/>`
   })
   expect(result.errors).toMatchSnapshot()