Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 79e13d1

Browse files
Fix siblings and interleaving issues reported in #2342 (#2348)
1 parent 0c6f87a commit 79e13d1

File tree

2 files changed

+199
-15
lines changed

2 files changed

+199
-15
lines changed

‎lib/rules/v-if-else-key.js

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ const casing = require('../utils/casing')
2525
* @property {VElement | null} else - The node associated with the 'v-else' directive, or null if there isn't one.
2626
*/
2727

28+
/**
29+
* Checks if a given node has sibling nodes of the same type that are also conditionally rendered.
30+
* This is used to determine if multiple instances of the same component are being conditionally
31+
* rendered within the same parent scope.
32+
*
33+
* @param {VElement} node - The Vue component node to check for conditional rendering siblings.
34+
* @param {string} componentName - The name of the component to check for sibling instances.
35+
* @returns {boolean} True if there are sibling nodes of the same type and conditionally rendered, false otherwise.
36+
*/
37+
const hasConditionalRenderedSiblings = (node, componentName) => {
38+
if (!node.parent || node.parent.type !== 'VElement') {
39+
return false
40+
}
41+
return node.parent.children.some(
42+
(sibling) =>
43+
sibling !== node &&
44+
sibling.type === 'VElement' &&
45+
sibling.rawName === componentName &&
46+
hasConditionalDirective(sibling)
47+
)
48+
}
49+
2850
/**
2951
* Checks for the presence of a 'key' attribute in the given node. If the 'key' attribute is missing
3052
* and the node is part of a conditional family a report is generated.
@@ -44,26 +66,31 @@ const checkForKey = (
4466
uniqueKey,
4567
conditionalFamilies
4668
) => {
47-
if (node.parent && node.parent.type === 'VElement') {
48-
const conditionalFamily = conditionalFamilies.get(node.parent)
69+
if (
70+
!node.parent ||
71+
node.parent.type !== 'VElement' ||
72+
!hasConditionalRenderedSiblings(node, componentName)
73+
) {
74+
return
75+
}
4976

50-
if (
51-
conditionalFamily &&
52-
(utils.hasDirective(node, 'bind', 'key') ||
53-
utils.hasAttribute(node, 'key') ||
54-
!hasConditionalDirective(node) ||
55-
!(conditionalFamily.else || conditionalFamily.elseIf.length > 0))
56-
) {
57-
return
58-
}
77+
const conditionalFamily = conditionalFamilies.get(node.parent)
78+
79+
if (!conditionalFamily || utils.hasAttribute(node, 'key')) {
80+
return
81+
}
82+
83+
const needsKey =
84+
conditionalFamily.if === node ||
85+
conditionalFamily.else === node ||
86+
conditionalFamily.elseIf.includes(node)
5987

88+
if (needsKey) {
6089
context.report({
6190
node: node.startTag,
6291
loc: node.startTag.loc,
6392
messageId: 'requireKey',
64-
data: {
65-
componentName
66-
},
93+
data: { componentName },
6794
fix(fixer) {
6895
const afterComponentNamePosition =
6996
node.startTag.range[0] + componentName.length + 1
@@ -190,13 +217,18 @@ module.exports = {
190217
if (node.parent && node.parent.type === 'VElement') {
191218
let conditionalFamily = conditionalFamilies.get(node.parent)
192219

193-
if (conditionType==='if'&&!conditionalFamily) {
220+
if (!conditionalFamily) {
194221
conditionalFamily = createConditionalFamily(node)
195222
conditionalFamilies.set(node.parent, conditionalFamily)
196223
}
197224

198225
if (conditionalFamily) {
199226
switch (conditionType) {
227+
case 'if': {
228+
conditionalFamily = createConditionalFamily(node)
229+
conditionalFamilies.set(node.parent, conditionalFamily)
230+
break
231+
}
200232
case 'else-if': {
201233
conditionalFamily.elseIf.push(node)
202234
break

‎tests/lib/rules/v-if-else-key.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,90 @@ tester.run('v-if-else-key', rule, {
127127
}
128128
</script>
129129
`
130+
},
131+
{
132+
filename: 'test.vue',
133+
code: `
134+
<template>
135+
<div>
136+
<div>
137+
<ComponentA v-if="foo" />
138+
<ComponentB v-else-if="bar" />
139+
</div>
140+
<div>
141+
<div>
142+
<ComponentA v-if="foo" />
143+
<ComponentB v-else-if="baz" />
144+
</div>
145+
<div>
146+
<ComponentA v-if="foo" />
147+
<ComponentB v-else-if="baz" />
148+
</div>
149+
</div>
150+
<div>
151+
<ComponentA v-if="foo" />
152+
<ComponentB v-else />
153+
</div>
154+
<div>
155+
<div v-if="foo" />
156+
<ComponentB v-else />
157+
</div>
158+
</div>
159+
</template>
160+
<script>
161+
export default {
162+
components: {
163+
ComponentA,
164+
ComponentB
165+
}
166+
}
167+
</script>
168+
`
169+
},
170+
{
171+
filename: 'test.vue',
172+
code: `
173+
<template>
174+
<div>
175+
<div>
176+
<CustomComponent v-if="foo" />
177+
<div v-else />
178+
</div>
179+
180+
<CustomComponent v-if="bar" />
181+
</div>
182+
</template>
183+
<script>
184+
export default {
185+
components: {
186+
CustomComponent
187+
}
188+
}
189+
</script>
190+
`
191+
},
192+
{
193+
filename: 'test.vue',
194+
code: `
195+
<template>
196+
<tile>
197+
<template v-if="state === 'foo'">
198+
<ComponentA>...</ComponentA>
199+
<ComponentB>...</ComponentB>
200+
</template>
201+
<ComponentA v-else-if="state === 'bar'" key="a">...</ComponentA>
202+
<ComponentA v-else-if="state === 'bar'" key="b">...</ComponentA>
203+
</tile>
204+
</template>
205+
<script>
206+
export default {
207+
components: {
208+
ComponentA,
209+
ComponentB
210+
}
211+
}
212+
</script>
213+
`
130214
}
131215
],
132216
invalid: [
@@ -424,6 +508,74 @@ tester.run('v-if-else-key', rule, {
424508
line: 6
425509
}
426510
]
511+
},
512+
{
513+
filename: 'test.vue',
514+
code: `
515+
<template>
516+
<div>
517+
<ComponentA v-if="foo" />
518+
<ComponentA v-else />
519+
520+
<ComponentA v-if="bar" />
521+
<ComponentA v-else-if="baz" />
522+
<ComponentA v-else />
523+
</div>
524+
</template>
525+
<script>
526+
export default {
527+
components: {
528+
ComponentA
529+
}
530+
}
531+
</script>
532+
`,
533+
output: `
534+
<template>
535+
<div>
536+
<ComponentA key="component-a-1" v-if="foo" />
537+
<ComponentA key="component-a-2" v-else />
538+
539+
<ComponentA key="component-a-3" v-if="bar" />
540+
<ComponentA key="component-a-4" v-else-if="baz" />
541+
<ComponentA key="component-a-5" v-else />
542+
</div>
543+
</template>
544+
<script>
545+
export default {
546+
components: {
547+
ComponentA
548+
}
549+
}
550+
</script>
551+
`,
552+
errors: [
553+
{
554+
message:
555+
"Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
556+
line: 4
557+
},
558+
{
559+
message:
560+
"Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
561+
line: 5
562+
},
563+
{
564+
message:
565+
"Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
566+
line: 7
567+
},
568+
{
569+
message:
570+
"Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
571+
line: 8
572+
},
573+
{
574+
message:
575+
"Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
576+
line: 9
577+
}
578+
]
427579
}
428580
]
429581
})

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /