dx(compiler-core): warn on <template v-for> key misplacement

Note: the behavior is different from Vue 2. `<template v-for>` are compiled
into an array of Fragment vnodes so the key should be placed the `<template>`
for v-for to use it for diffing.
This commit is contained in:
Evan You 2020-08-04 12:20:32 -04:00
parent de0c8a7e3e
commit b0d01e9db9
3 changed files with 50 additions and 2 deletions

View File

@ -263,6 +263,34 @@ describe('compiler: v-for', () => {
}) })
) )
}) })
test('<template v-for> key placement', () => {
const onError = jest.fn()
parseWithForTransform(
`
<template v-for="item in items">
<div :key="item.id"/>
</template>`,
{ onError }
)
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT
})
)
// should not warn on nested v-for keys
parseWithForTransform(
`
<template v-for="item in items">
<div v-for="c in item.children" :key="c.id"/>
</template>`,
{ onError }
)
expect(onError).toHaveBeenCalledTimes(1)
})
}) })
describe('source location', () => { describe('source location', () => {

View File

@ -67,6 +67,7 @@ export const enum ErrorCodes {
X_V_ELSE_NO_ADJACENT_IF, X_V_ELSE_NO_ADJACENT_IF,
X_V_FOR_NO_EXPRESSION, X_V_FOR_NO_EXPRESSION,
X_V_FOR_MALFORMED_EXPRESSION, X_V_FOR_MALFORMED_EXPRESSION,
X_V_FOR_TEMPLATE_KEY_PLACEMENT,
X_V_BIND_NO_EXPRESSION, X_V_BIND_NO_EXPRESSION,
X_V_ON_NO_EXPRESSION, X_V_ON_NO_EXPRESSION,
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET, X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
@ -140,6 +141,7 @@ export const errorMessages: { [code: number]: string } = {
[ErrorCodes.X_V_ELSE_NO_ADJACENT_IF]: `v-else/v-else-if has no adjacent v-if.`, [ErrorCodes.X_V_ELSE_NO_ADJACENT_IF]: `v-else/v-else-if has no adjacent v-if.`,
[ErrorCodes.X_V_FOR_NO_EXPRESSION]: `v-for is missing expression.`, [ErrorCodes.X_V_FOR_NO_EXPRESSION]: `v-for is missing expression.`,
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`, [ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: `<template v-for> key should be placed on the <template> tag.`,
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`, [ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`, [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`, [ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,

View File

@ -10,7 +10,6 @@ import {
SimpleExpressionNode, SimpleExpressionNode,
createCallExpression, createCallExpression,
createFunctionExpression, createFunctionExpression,
ElementTypes,
createObjectExpression, createObjectExpression,
createObjectProperty, createObjectProperty,
ForCodegenNode, ForCodegenNode,
@ -81,6 +80,25 @@ export const transformFor = createStructuralDirectiveTransform(
let childBlock: BlockCodegenNode let childBlock: BlockCodegenNode
const isTemplate = isTemplateNode(node) const isTemplate = isTemplateNode(node)
const { children } = forNode const { children } = forNode
// check <template v-for> key placement
if ((__DEV__ || !__BROWSER__) && isTemplate) {
node.children.some(c => {
if (c.type === NodeTypes.ELEMENT) {
const key = findProp(c, 'key')
if (key) {
context.onError(
createCompilerError(
ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
key.loc
)
)
return true
}
}
})
}
const needFragmentWrapper = const needFragmentWrapper =
children.length !== 1 || children[0].type !== NodeTypes.ELEMENT children.length !== 1 || children[0].type !== NodeTypes.ELEMENT
const slotOutlet = isSlotOutlet(node) const slotOutlet = isSlotOutlet(node)
@ -183,7 +201,7 @@ export function processFor(
keyAlias: key, keyAlias: key,
objectIndexAlias: index, objectIndexAlias: index,
parseResult, parseResult,
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node] children: isTemplateNode(node) ? node.children : [node]
} }
context.replaceNode(forNode) context.replaceNode(forNode)