]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-core): support v-bind shorthand for key and value with the same name...
authorzhiyuanzmj <32807958+zhiyuanzmj@users.noreply.github.com>
Thu, 2 Nov 2023 09:48:11 +0000 (17:48 +0800)
committerGitHub <noreply@github.com>
Thu, 2 Nov 2023 09:48:11 +0000 (17:48 +0800)
packages/compiler-core/__tests__/transforms/vBind.spec.ts
packages/compiler-core/src/transforms/vBind.ts
packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap
packages/compiler-sfc/__tests__/compileTemplate.spec.ts

index 322cf9d1bdeb45378da061ca91933ed09ca01e67..2e94dc1f7de2a07c482afe8c220d06548cc9e567 100644 (file)
@@ -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
+      }
+    })
+  })
 })
index ffaf903b9e803736663f1ea3c9ce5250bbe50840..9f85d6b3d635ec8aebb694ea7f3764d51cc00edc 100644 (file)
@@ -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())
index 35b87c4ec714111de99a8646beee49bf81f85def..f97eef6094ea1b3ef6b3361d7b68abe32680eebc 100644 (file)
@@ -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.],
 ]
 `;
index b471b67c9ca1bb235804c84b0d56a633829f1a87..5e1867f26ea1c68eb2955df29b5e8181c0534723 100644 (file)
@@ -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()