test('error on user key', () => {
const onError = jest.fn()
- parseWithIfTransform(`<div v-if="ok" :key="1" />`, { onError })
+ // dynamic
+ parseWithIfTransform(
+ `<div v-if="ok" :key="a + 1" /><div v-else :key="a + 1" />`,
+ { onError }
+ )
expect(onError.mock.calls[0]).toMatchObject([
{
- code: ErrorCodes.X_V_IF_KEY
+ code: ErrorCodes.X_V_IF_SAME_KEY
+ }
+ ])
+ // static
+ parseWithIfTransform(`<div v-if="ok" key="1" /><div v-else key="1" />`, {
+ onError
+ })
+ expect(onError.mock.calls[1]).toMatchObject([
+ {
+ code: ErrorCodes.X_V_IF_SAME_KEY
}
])
})
// transform errors
X_V_IF_NO_EXPRESSION,
- X_V_IF_KEY,
+ X_V_IF_SAME_KEY,
X_V_ELSE_NO_ADJACENT_IF,
X_V_FOR_NO_EXPRESSION,
X_V_FOR_MALFORMED_EXPRESSION,
// transform errors
[ErrorCodes.X_V_IF_NO_EXPRESSION]: `v-if/v-else-if is missing expression.`,
- [ErrorCodes.X_V_IF_KEY]:
- `v-if branches must use compiler generated keys. ` +
- `In many cases, you can simply remove this key. ` +
- `If this tag is inside of a <template v-for="...">, then you can move the key up to the parent <template>.`,
+ [ErrorCodes.X_V_IF_SAME_KEY]: `v-if/else branches must use unique keys.`,
[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_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
IfConditionalExpression,
BlockCodegenNode,
IfNode,
- createVNodeCall
+ createVNodeCall,
+ AttributeNode
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
validateBrowserExpression(dir.exp as SimpleExpressionNode, context)
}
- const userKey = /*#__PURE__*/ findProp(node, 'key')
- if (userKey) {
- context.onError(createCompilerError(ErrorCodes.X_V_IF_KEY, userKey.loc))
- }
-
if (dir.name === 'if') {
const branch = createIfBranch(node, dir)
const ifNode: IfNode = {
if (__DEV__ && comments.length) {
branch.children = [...comments, ...branch.children]
}
+
+ // check if user is forcing same key on different branches
+ if (__DEV__ || !__BROWSER__) {
+ const key = branch.userKey
+ if (key) {
+ sibling.branches.forEach(({ userKey }) => {
+ if (isSameKey(userKey, key)) {
+ context.onError(
+ createCompilerError(
+ ErrorCodes.X_V_IF_SAME_KEY,
+ branch.userKey!.loc
+ )
+ )
+ }
+ })
+ }
+ }
+
sibling.branches.push(branch)
const onExit = processCodegen && processCodegen(sibling, branch, false)
// since the branch was removed, it will not be traversed.
children:
node.tagType === ElementTypes.TEMPLATE && !findDir(node, 'for')
? node.children
- : [node]
+ : [node],
+ userKey: findProp(node, `key`)
}
}
return vnodeCall
}
}
+
+function isSameKey(
+ a: AttributeNode | DirectiveNode | undefined,
+ b: AttributeNode | DirectiveNode
+): boolean {
+ if (!a || a.type !== b.type) {
+ return false
+ }
+ if (a.type === NodeTypes.ATTRIBUTE) {
+ if (a.value!.content !== (b as AttributeNode).value!.content) {
+ return false
+ }
+ } else {
+ // directive
+ const exp = a.exp!
+ const branchExp = (b as DirectiveNode).exp!
+ if (exp.type !== branchExp.type) {
+ return false
+ }
+ if (
+ exp.type !== NodeTypes.SIMPLE_EXPRESSION ||
+ (exp.isStatic !== (branchExp as SimpleExpressionNode).isStatic ||
+ exp.content !== (branchExp as SimpleExpressionNode).content)
+ ) {
+ return false
+ }
+ }
+ return true
+}