Skip to content

Commit 86a8138

Browse files
waynzhFloEdelmann
andauthored
feat(no-duplicate-attr-inheritance): ignore multi root (#2598)
Co-authored-by: Flo Edelmann <[email protected]>
1 parent 4500389 commit 86a8138

File tree

3 files changed

+214
-12
lines changed

3 files changed

+214
-12
lines changed

docs/rules/no-duplicate-attr-inheritance.md

+34-4
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ since: v7.0.0
1313
## :book: Rule Details
1414

1515
This rule aims to prevent duplicate attribute inheritance.
16-
This rule to warn to apply `inheritAttrs: false` when it detects `v-bind="$attrs"` being used.
16+
This rule suggests applying `inheritAttrs: false` when it detects `v-bind="$attrs"` being used.
1717

18-
<eslint-code-block :rules="{'vue/no-duplicate-attr-inheritance': ['error']}">
18+
<eslint-code-block :rules="{'vue/no-duplicate-attr-inheritance': ['error', { checkMultiRootNodes: false }]}">
1919

2020
```vue
2121
<template>
@@ -26,11 +26,12 @@ export default {
2626
/* ✓ GOOD */
2727
inheritAttrs: false
2828
}
29+
</script>
2930
```
3031

3132
</eslint-code-block>
3233

33-
<eslint-code-block :rules="{'vue/no-duplicate-attr-inheritance': ['error']}">
34+
<eslint-code-block :rules="{'vue/no-duplicate-attr-inheritance': ['error', { checkMultiRootNodes: false }]}">
3435

3536
```vue
3637
<template>
@@ -41,17 +42,46 @@ export default {
4142
/* ✗ BAD */
4243
// inheritAttrs: true (default)
4344
}
45+
</script>
4446
```
4547

4648
</eslint-code-block>
4749

4850
## :wrench: Options
4951

50-
Nothing.
52+
```json
53+
{
54+
"vue/no-duplicate-attr-inheritance": ["error", {
55+
"checkMultiRootNodes": false,
56+
}]
57+
}
58+
```
59+
60+
- `"checkMultiRootNodes"`: If set to `true`, also suggest applying `inheritAttrs: false` to components with multiple root nodes (where `inheritAttrs: false` is the implicit default, see [attribute inheritance on multiple root nodes](https://vuejs.org/guide/components/attrs.html#attribute-inheritance-on-multiple-root-nodes)), whenever it detects `v-bind="$attrs"` being used. Default is `false`, which will ignore components with multiple root nodes.
61+
62+
### `"checkMultiRootNodes": true`
63+
64+
<eslint-code-block :rules="{'vue/no-duplicate-attr-inheritance': ['error', { checkMultiRootNodes: true }]}">
65+
66+
```vue
67+
<template>
68+
<div v-bind="$attrs" />
69+
<div />
70+
</template>
71+
<script>
72+
export default {
73+
/* ✗ BAD */
74+
// inheritAttrs: true (default)
75+
}
76+
</script>
77+
```
78+
79+
</eslint-code-block>
5180

5281
## :books: Further Reading
5382

5483
- [API - inheritAttrs](https://vuejs.org/api/options-misc.html#inheritattrs)
84+
- [Fallthrough Attributes](https://vuejs.org/guide/components/attrs.html#attribute-inheritance-on-multiple-root-nodes)
5585

5686
## :rocket: Version
5787

lib/rules/no-duplicate-attr-inheritance.js

+68-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,33 @@
66

77
const utils = require('../utils')
88

9+
/** @param {VElement[]} elements */
10+
function isConditionalGroup(elements) {
11+
if (elements.length < 2) {
12+
return false
13+
}
14+
15+
const firstElement = elements[0]
16+
const lastElement = elements[elements.length - 1]
17+
const inBetweenElements = elements.slice(1, -1)
18+
19+
return (
20+
utils.hasDirective(firstElement, 'if') &&
21+
(utils.hasDirective(lastElement, 'else-if') ||
22+
utils.hasDirective(lastElement, 'else')) &&
23+
inBetweenElements.every((element) => utils.hasDirective(element, 'else-if'))
24+
)
25+
}
26+
27+
/** @param {VElement[]} elements */
28+
function isMultiRootNodes(elements) {
29+
if (elements.length > 1 && !isConditionalGroup(elements)) {
30+
return true
31+
}
32+
33+
return false
34+
}
35+
936
module.exports = {
1037
meta: {
1138
type: 'suggestion',
@@ -17,15 +44,30 @@ module.exports = {
1744
url: 'https://eslint.vuejs.org/rules/no-duplicate-attr-inheritance.html'
1845
},
1946
fixable: null,
20-
schema: [],
47+
schema: [
48+
{
49+
type: 'object',
50+
properties: {
51+
checkMultiRootNodes: {
52+
type: 'boolean'
53+
}
54+
},
55+
additionalProperties: false
56+
}
57+
],
2158
messages: {
2259
noDuplicateAttrInheritance: 'Set "inheritAttrs" to false.'
2360
}
2461
},
2562
/** @param {RuleContext} context */
2663
create(context) {
64+
const options = context.options[0] || {}
65+
const checkMultiRootNodes = options.checkMultiRootNodes === true
66+
2767
/** @type {string | number | boolean | RegExp | BigInt | null} */
2868
let inheritsAttrs = true
69+
/** @type {VReference[]} */
70+
const attrsRefs = []
2971

3072
/** @param {ObjectExpression} node */
3173
function processOptions(node) {
@@ -54,22 +96,40 @@ module.exports = {
5496
if (!inheritsAttrs) {
5597
return
5698
}
57-
const attrsRef = node.references.find((reference) => {
99+
const reference = node.references.find((reference) => {
58100
if (reference.variable != null) {
59101
// Not vm reference
60102
return false
61103
}
62104
return reference.id.name === '$attrs'
63105
})
64106

65-
if (attrsRef) {
66-
context.report({
67-
node: attrsRef.id,
68-
messageId: 'noDuplicateAttrInheritance'
69-
})
107+
if (reference) {
108+
attrsRefs.push(reference)
70109
}
71110
}
72-
})
111+
}),
112+
{
113+
'Program:exit'(program) {
114+
const element = program.templateBody
115+
if (element == null) {
116+
return
117+
}
118+
119+
const rootElements = element.children.filter(utils.isVElement)
120+
121+
if (!checkMultiRootNodes && isMultiRootNodes(rootElements)) return
122+
123+
if (attrsRefs.length > 0) {
124+
for (const attrsRef of attrsRefs) {
125+
context.report({
126+
node: attrsRef.id,
127+
messageId: 'noDuplicateAttrInheritance'
128+
})
129+
}
130+
}
131+
}
132+
}
73133
)
74134
}
75135
}

tests/lib/rules/no-duplicate-attr-inheritance.js

+112
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,57 @@ ruleTester.run('no-duplicate-attr-inheritance', rule, {
4343
</script>
4444
`
4545
},
46+
// ignore multi root by default
47+
{
48+
filename: 'test.vue',
49+
code: `
50+
<script setup>
51+
defineOptions({ inheritAttrs: true })
52+
</script>
53+
<template><div v-bind="$attrs"/><div/></template>
54+
`
55+
},
56+
{
57+
filename: 'test.vue',
58+
code: `
59+
<template>
60+
<div v-if="condition1"></div>
61+
<div v-if="condition2" v-bind="$attrs"></div>
62+
<div v-else></div>
63+
</template>
64+
`
65+
},
66+
{
67+
filename: 'test.vue',
68+
code: `
69+
<template>
70+
<div v-if="condition1"></div>
71+
<div v-else-if="condition2"></div>
72+
<div v-bind="$attrs"></div>
73+
</template>
74+
`
75+
},
76+
{
77+
filename: 'test.vue',
78+
code: `
79+
<template>
80+
<div v-bind="$attrs"></div>
81+
<div v-if="condition1"></div>
82+
<div v-else></div>
83+
</template>
84+
`
85+
},
86+
{
87+
filename: 'test.vue',
88+
code: `
89+
<template>
90+
<div v-if="condition1"></div>
91+
<div v-else-if="condition2"></div>
92+
<div v-if="condition3" v-bind="$attrs"></div>
93+
</template>
94+
`,
95+
options: [{ checkMultiRootNodes: false }]
96+
},
4697
{
4798
filename: 'test.vue',
4899
code: `
@@ -151,6 +202,67 @@ ruleTester.run('no-duplicate-attr-inheritance', rule, {
151202
line: 5
152203
}
153204
]
205+
},
206+
{
207+
filename: 'test.vue',
208+
code: `<template><div v-bind="$attrs"></div><div></div></template>`,
209+
options: [{ checkMultiRootNodes: true }],
210+
errors: [{ message: 'Set "inheritAttrs" to false.' }]
211+
},
212+
{
213+
filename: 'test.vue',
214+
code: `
215+
<template>
216+
<div v-if="condition1" v-bind="$attrs"></div>
217+
<div v-else></div>
218+
<div v-if="condition2"></div>
219+
</template>
220+
`,
221+
options: [{ checkMultiRootNodes: true }],
222+
errors: [{ message: 'Set "inheritAttrs" to false.' }]
223+
},
224+
// condition group as a single root node
225+
{
226+
filename: 'test.vue',
227+
code: `
228+
<template>
229+
<div v-if="condition1" v-bind="$attrs"></div>
230+
<div v-else-if="condition2"></div>
231+
<div v-else></div>
232+
</template>
233+
`,
234+
errors: [{ message: 'Set "inheritAttrs" to false.' }]
235+
},
236+
{
237+
filename: 'test.vue',
238+
code: `
239+
<template>
240+
<div v-if="condition1" v-bind="$attrs"></div>
241+
<div v-else-if="condition2"></div>
242+
<div v-else-if="condition3"></div>
243+
<div v-else></div>
244+
</template>
245+
`,
246+
errors: [{ message: 'Set "inheritAttrs" to false.' }]
247+
},
248+
{
249+
filename: 'test.vue',
250+
code: `
251+
<template>
252+
<div v-if="condition1" v-bind="$attrs"></div>
253+
<div v-else></div>
254+
</template>
255+
`,
256+
errors: [{ message: 'Set "inheritAttrs" to false.' }]
257+
},
258+
{
259+
filename: 'test.vue',
260+
code: `
261+
<template>
262+
<div v-if="condition1" v-bind="$attrs"></div>
263+
</template>
264+
`,
265+
errors: [{ message: 'Set "inheritAttrs" to false.' }]
154266
}
155267
]
156268
})

0 commit comments

Comments
 (0)