]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-core): fix :key shorthand on v-for (#10942)
authorVadim Kruglov <49036220+quiteeasy@users.noreply.github.com>
Tue, 4 Jun 2024 12:18:24 +0000 (19:18 +0700)
committerGitHub <noreply@github.com>
Tue, 4 Jun 2024 12:18:24 +0000 (20:18 +0800)
close #10882
close #10939

packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/src/transforms/vBind.ts
packages/compiler-core/src/transforms/vFor.ts

index e434b8888a698c559ab2330b7f1649b4d027a806..94f75f2a63b0f0be17169cd53f1a95a50c8bbf1f 100644 (file)
@@ -18,7 +18,7 @@ import {
 import { ErrorCodes } from '../../src/errors'
 import { type CompilerOptions, generate } from '../../src'
 import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
-import { PatchFlags } from '@vue/shared'
+import { PatchFlagNames, PatchFlags } from '@vue/shared'
 import { createObjectMatcher, genFlagText } from '../testUtils'
 
 export function parseWithForTransform(
@@ -1043,5 +1043,33 @@ describe('compiler: v-for', () => {
       })
       expect(generate(root).code).toMatchSnapshot()
     })
+
+    test('template v-for key w/ :key shorthand on div', () => {
+      const {
+        node: { codegenNode },
+      } = parseWithForTransform('<div v-for="key in keys" :key>test</div>')
+      expect(codegenNode.patchFlag).toBe(
+        `${PatchFlags.KEYED_FRAGMENT} /* ${PatchFlagNames[PatchFlags.KEYED_FRAGMENT]} */`,
+      )
+    })
+
+    test('template v-for key w/ :key shorthand on template injected to the child', () => {
+      const {
+        node: { codegenNode },
+      } = parseWithForTransform(
+        '<template v-for="key in keys" :key><div>test</div></template>',
+      )
+      expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
+        source: { content: `keys` },
+        params: [{ content: `key` }],
+        innerVNodeCall: {
+          type: NodeTypes.VNODE_CALL,
+          tag: `"div"`,
+          props: createObjectMatcher({
+            key: '[key]',
+          }),
+        },
+      })
+    })
   })
 })
index 234cf1fbc3060dec73648b881e9da0f43e33f4f1..cec444a5a1a4b90f506dfdf12ef2effec1e4e1ce 100644 (file)
@@ -1,5 +1,6 @@
-import type { DirectiveTransform } from '../transform'
+import type { DirectiveTransform, TransformContext } from '../transform'
 import {
+  type DirectiveNode,
   type ExpressionNode,
   NodeTypes,
   type SimpleExpressionNode,
@@ -56,11 +57,8 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
       }
     }
 
-    const propName = camelize((arg as SimpleExpressionNode).content)
-    exp = dir.exp = createSimpleExpression(propName, false, arg.loc)
-    if (!__BROWSER__) {
-      exp = dir.exp = processExpression(exp, context)
-    }
+    transformBindShorthand(dir, context)
+    exp = dir.exp!
   }
 
   if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
@@ -98,6 +96,19 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
   }
 }
 
+export const transformBindShorthand = (
+  dir: DirectiveNode,
+  context: TransformContext,
+) => {
+  const arg = dir.arg!
+
+  const propName = camelize((arg as SimpleExpressionNode).content)
+  dir.exp = createSimpleExpression(propName, false, arg.loc)
+  if (!__BROWSER__) {
+    dir.exp = processExpression(dir.exp, context)
+  }
+}
+
 const injectPrefix = (arg: ExpressionNode, prefix: string) => {
   if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
     if (arg.isStatic) {
index 5d423ee2429e96ffd353ed1d265536a5b61a1bb5..16c48ede06745a622259d2eec044c591e4a03979 100644 (file)
@@ -47,6 +47,7 @@ import {
 import { processExpression } from './transformExpression'
 import { validateBrowserExpression } from '../validateExpression'
 import { PatchFlagNames, PatchFlags } from '@vue/shared'
+import { transformBindShorthand } from './vBind'
 
 export const transformFor = createStructuralDirectiveTransform(
   'for',
@@ -60,13 +61,20 @@ export const transformFor = createStructuralDirectiveTransform(
       ]) as ForRenderListExpression
       const isTemplate = isTemplateNode(node)
       const memo = findDir(node, 'memo')
-      const keyProp = findProp(node, `key`)
+      const keyProp = findProp(node, `key`, false, true)
+      if (keyProp && keyProp.type === NodeTypes.DIRECTIVE && !keyProp.exp) {
+        // resolve :key shorthand #10882
+        transformBindShorthand(keyProp, context)
+      }
       const keyExp =
         keyProp &&
         (keyProp.type === NodeTypes.ATTRIBUTE
-          ? createSimpleExpression(keyProp.value!.content, true)
-          : keyProp.exp!)
-      const keyProperty = keyProp ? createObjectProperty(`key`, keyExp!) : null
+          ? keyProp.value
+            ? createSimpleExpression(keyProp.value.content, true)
+            : undefined
+          : keyProp.exp)
+      const keyProperty =
+        keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
 
       if (!__BROWSER__ && isTemplate) {
         // #2085 / #5288 process :key and v-memo expressions need to be