Skip to content

Commit de0c8a7

Browse files
committed
fix(compiler-core): v-if key error should only be checking same key on different branches
1 parent c3f8c78 commit de0c8a7

File tree

5 files changed

+74
-15
lines changed

5 files changed

+74
-15
lines changed

packages/compiler-core/__tests__/transforms/vIf.spec.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,23 @@ describe('compiler: v-if', () => {
285285

286286
test('error on user key', () => {
287287
const onError = jest.fn()
288-
parseWithIfTransform(`<div v-if="ok" :key="1" />`, { onError })
288+
// dynamic
289+
parseWithIfTransform(
290+
`<div v-if="ok" :key="a + 1" /><div v-else :key="a + 1" />`,
291+
{ onError }
292+
)
289293
expect(onError.mock.calls[0]).toMatchObject([
290294
{
291-
code: ErrorCodes.X_V_IF_KEY
295+
code: ErrorCodes.X_V_IF_SAME_KEY
296+
}
297+
])
298+
// static
299+
parseWithIfTransform(`<div v-if="ok" key="1" /><div v-else key="1" />`, {
300+
onError
301+
})
302+
expect(onError.mock.calls[1]).toMatchObject([
303+
{
304+
code: ErrorCodes.X_V_IF_SAME_KEY
292305
}
293306
])
294307
})

packages/compiler-core/src/ast.ts

+1
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ export interface IfBranchNode extends Node {
243243
type: NodeTypes.IF_BRANCH
244244
condition: ExpressionNode | undefined // else
245245
children: TemplateChildNode[]
246+
userKey?: AttributeNode | DirectiveNode
246247
}
247248

248249
export interface ForNode extends Node {

packages/compiler-core/src/errors.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const enum ErrorCodes {
6363

6464
// transform errors
6565
X_V_IF_NO_EXPRESSION,
66-
X_V_IF_KEY,
66+
X_V_IF_SAME_KEY,
6767
X_V_ELSE_NO_ADJACENT_IF,
6868
X_V_FOR_NO_EXPRESSION,
6969
X_V_FOR_MALFORMED_EXPRESSION,
@@ -136,10 +136,7 @@ export const errorMessages: { [code: number]: string } = {
136136

137137
// transform errors
138138
[ErrorCodes.X_V_IF_NO_EXPRESSION]: `v-if/v-else-if is missing expression.`,
139-
[ErrorCodes.X_V_IF_KEY]:
140-
`v-if branches must use compiler generated keys. ` +
141-
`In many cases, you can simply remove this key. ` +
142-
`If this tag is inside of a <template v-for="...">, then you can move the key up to the parent <template>.`,
139+
[ErrorCodes.X_V_IF_SAME_KEY]: `v-if/else branches must use unique keys.`,
143140
[ErrorCodes.X_V_ELSE_NO_ADJACENT_IF]: `v-else/v-else-if has no adjacent v-if.`,
144141
[ErrorCodes.X_V_FOR_NO_EXPRESSION]: `v-for is missing expression.`,
145142
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,

packages/compiler-core/src/transforms/vIf.ts

+51-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
IfConditionalExpression,
1919
BlockCodegenNode,
2020
IfNode,
21-
createVNodeCall
21+
createVNodeCall,
22+
AttributeNode
2223
} from '../ast'
2324
import { createCompilerError, ErrorCodes } from '../errors'
2425
import { processExpression } from './transformExpression'
@@ -111,11 +112,6 @@ export function processIf(
111112
validateBrowserExpression(dir.exp as SimpleExpressionNode, context)
112113
}
113114

114-
const userKey = /*#__PURE__*/ findProp(node, 'key')
115-
if (userKey) {
116-
context.onError(createCompilerError(ErrorCodes.X_V_IF_KEY, userKey.loc))
117-
}
118-
119115
if (dir.name === 'if') {
120116
const branch = createIfBranch(node, dir)
121117
const ifNode: IfNode = {
@@ -146,6 +142,24 @@ export function processIf(
146142
if (__DEV__ && comments.length) {
147143
branch.children = [...comments, ...branch.children]
148144
}
145+
146+
// check if user is forcing same key on different branches
147+
if (__DEV__ || !__BROWSER__) {
148+
const key = branch.userKey
149+
if (key) {
150+
sibling.branches.forEach(({ userKey }) => {
151+
if (isSameKey(userKey, key)) {
152+
context.onError(
153+
createCompilerError(
154+
ErrorCodes.X_V_IF_SAME_KEY,
155+
branch.userKey!.loc
156+
)
157+
)
158+
}
159+
})
160+
}
161+
}
162+
149163
sibling.branches.push(branch)
150164
const onExit = processCodegen && processCodegen(sibling, branch, false)
151165
// since the branch was removed, it will not be traversed.
@@ -174,7 +188,8 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
174188
children:
175189
node.tagType === ElementTypes.TEMPLATE && !findDir(node, 'for')
176190
? node.children
177-
: [node]
191+
: [node],
192+
userKey: findProp(node, `key`)
178193
}
179194
}
180195

@@ -256,3 +271,32 @@ function createChildrenCodegenNode(
256271
return vnodeCall
257272
}
258273
}
274+
275+
function isSameKey(
276+
a: AttributeNode | DirectiveNode | undefined,
277+
b: AttributeNode | DirectiveNode
278+
): boolean {
279+
if (!a || a.type !== b.type) {
280+
return false
281+
}
282+
if (a.type === NodeTypes.ATTRIBUTE) {
283+
if (a.value!.content !== (b as AttributeNode).value!.content) {
284+
return false
285+
}
286+
} else {
287+
// directive
288+
const exp = a.exp!
289+
const branchExp = (b as DirectiveNode).exp!
290+
if (exp.type !== branchExp.type) {
291+
return false
292+
}
293+
if (
294+
exp.type !== NodeTypes.SIMPLE_EXPRESSION ||
295+
(exp.isStatic !== (branchExp as SimpleExpressionNode).isStatic ||
296+
exp.content !== (branchExp as SimpleExpressionNode).content)
297+
) {
298+
return false
299+
}
300+
}
301+
return true
302+
}

packages/compiler-core/src/utils.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,11 @@ export function findProp(
161161
if (p.name === name && (p.value || allowEmpty)) {
162162
return p
163163
}
164-
} else if (p.name === 'bind' && p.exp && isBindKey(p.arg, name)) {
164+
} else if (
165+
p.name === 'bind' &&
166+
(p.exp || allowEmpty) &&
167+
isBindKey(p.arg, name)
168+
) {
165169
return p
166170
}
167171
}

0 commit comments

Comments
 (0)