diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts index 7fabcbb579c..046f822c45a 100644 --- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts @@ -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( @@ -1019,5 +1019,33 @@ describe('compiler: v-for', () => { }) expect(generate(root).code).toMatchSnapshot() }) + + test('template v-for key w/ :key shorthand on div', () => { + const { + node: { codegenNode }, + } = parseWithForTransform('
test
') + 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( + '', + ) + expect(assertSharedCodegen(codegenNode, true)).toMatchObject({ + source: { content: `keys` }, + params: [{ content: `key` }], + innerVNodeCall: { + type: NodeTypes.VNODE_CALL, + tag: `"div"`, + props: createObjectMatcher({ + key: '[key]', + }), + }, + }) + }) }) }) diff --git a/packages/compiler-core/src/transforms/vBind.ts b/packages/compiler-core/src/transforms/vBind.ts index 234cf1fbc30..f5c06b870ea 100644 --- a/packages/compiler-core/src/transforms/vBind.ts +++ b/packages/compiler-core/src/transforms/vBind.ts @@ -1,5 +1,6 @@ -import type { DirectiveTransform } from '../transform' +import type { DirectiveTransform, TransformContext } from '../transform' import { + type DirectiveNode, type ExpressionNode, NodeTypes, type SimpleExpressionNode, @@ -41,25 +42,11 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => { // same-name shorthand - :arg is expanded to :arg="arg" if (!exp) { - if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) { - // only simple expression is allowed for same-name shorthand - context.onError( - createCompilerError( - ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT, - arg.loc, - ), - ) - return { - props: [ - createObjectProperty(arg, createSimpleExpression('', true, loc)), - ], - } - } + if (!exp) { + const returned = transformBindShorthand(dir, context) + if (returned) return returned - const propName = camelize((arg as SimpleExpressionNode).content) - exp = dir.exp = createSimpleExpression(propName, false, arg.loc) - if (!__BROWSER__) { - exp = dir.exp = processExpression(exp, context) + exp = dir.exp! } } @@ -98,6 +85,32 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => { } } +export const transformBindShorthand = ( + dir: DirectiveNode, + context: TransformContext, +) => { + const arg = dir.arg! + const { loc } = dir + if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) { + // only simple expression is allowed for same-name shorthand + context.onError( + createCompilerError( + ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT, + arg.loc, + ), + ) + return { + props: [createObjectProperty(arg, createSimpleExpression('', true, loc))], + } + } + + 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) { diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index 5d423ee2429..35fe2048724 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -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) { + // try to 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