Skip to content

Commit

Permalink
feat(no-duplicate-attr-inheritance): ignore multi root (#2598)
Browse files Browse the repository at this point in the history
Co-authored-by: Flo Edelmann <git@flo-edelmann.de>
  • Loading branch information
waynzh and FloEdelmann authored Nov 27, 2024
1 parent 4500389 commit 86a8138
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 12 deletions.
38 changes: 34 additions & 4 deletions docs/rules/no-duplicate-attr-inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ since: v7.0.0
## :book: Rule Details

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

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

```vue
<template>
Expand All @@ -26,11 +26,12 @@ export default {
/* ✓ GOOD */
inheritAttrs: false
}
</script>
```

</eslint-code-block>

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

```vue
<template>
Expand All @@ -41,17 +42,46 @@ export default {
/* ✗ BAD */
// inheritAttrs: true (default)
}
</script>
```

</eslint-code-block>

## :wrench: Options

Nothing.
```json
{
"vue/no-duplicate-attr-inheritance": ["error", {
"checkMultiRootNodes": false,
}]
}
```

- `"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.

### `"checkMultiRootNodes": true`

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

```vue
<template>
<div v-bind="$attrs" />
<div />
</template>
<script>
export default {
/* ✗ BAD */
// inheritAttrs: true (default)
}
</script>
```

</eslint-code-block>

## :books: Further Reading

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

## :rocket: Version

Expand Down
76 changes: 68 additions & 8 deletions lib/rules/no-duplicate-attr-inheritance.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@

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

/** @param {VElement[]} elements */
function isConditionalGroup(elements) {
if (elements.length < 2) {
return false
}

const firstElement = elements[0]
const lastElement = elements[elements.length - 1]
const inBetweenElements = elements.slice(1, -1)

return (
utils.hasDirective(firstElement, 'if') &&
(utils.hasDirective(lastElement, 'else-if') ||
utils.hasDirective(lastElement, 'else')) &&
inBetweenElements.every((element) => utils.hasDirective(element, 'else-if'))
)
}

/** @param {VElement[]} elements */
function isMultiRootNodes(elements) {
if (elements.length > 1 && !isConditionalGroup(elements)) {
return true
}

return false
}

module.exports = {
meta: {
type: 'suggestion',
Expand All @@ -17,15 +44,30 @@ module.exports = {
url: 'https://eslint.vuejs.org/rules/no-duplicate-attr-inheritance.html'
},
fixable: null,
schema: [],
schema: [
{
type: 'object',
properties: {
checkMultiRootNodes: {
type: 'boolean'
}
},
additionalProperties: false
}
],
messages: {
noDuplicateAttrInheritance: 'Set "inheritAttrs" to false.'
}
},
/** @param {RuleContext} context */
create(context) {
const options = context.options[0] || {}
const checkMultiRootNodes = options.checkMultiRootNodes === true

/** @type {string | number | boolean | RegExp | BigInt | null} */
let inheritsAttrs = true
/** @type {VReference[]} */
const attrsRefs = []

/** @param {ObjectExpression} node */
function processOptions(node) {
Expand Down Expand Up @@ -54,22 +96,40 @@ module.exports = {
if (!inheritsAttrs) {
return
}
const attrsRef = node.references.find((reference) => {
const reference = node.references.find((reference) => {
if (reference.variable != null) {
// Not vm reference
return false
}
return reference.id.name === '$attrs'
})

if (attrsRef) {
context.report({
node: attrsRef.id,
messageId: 'noDuplicateAttrInheritance'
})
if (reference) {
attrsRefs.push(reference)
}
}
})
}),
{
'Program:exit'(program) {
const element = program.templateBody
if (element == null) {
return
}

const rootElements = element.children.filter(utils.isVElement)

if (!checkMultiRootNodes && isMultiRootNodes(rootElements)) return

if (attrsRefs.length > 0) {
for (const attrsRef of attrsRefs) {
context.report({
node: attrsRef.id,
messageId: 'noDuplicateAttrInheritance'
})
}
}
}
}
)
}
}
112 changes: 112 additions & 0 deletions tests/lib/rules/no-duplicate-attr-inheritance.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,57 @@ ruleTester.run('no-duplicate-attr-inheritance', rule, {
</script>
`
},
// ignore multi root by default
{
filename: 'test.vue',
code: `
<script setup>
defineOptions({ inheritAttrs: true })
</script>
<template><div v-bind="$attrs"/><div/></template>
`
},
{
filename: 'test.vue',
code: `
<template>
<div v-if="condition1"></div>
<div v-if="condition2" v-bind="$attrs"></div>
<div v-else></div>
</template>
`
},
{
filename: 'test.vue',
code: `
<template>
<div v-if="condition1"></div>
<div v-else-if="condition2"></div>
<div v-bind="$attrs"></div>
</template>
`
},
{
filename: 'test.vue',
code: `
<template>
<div v-bind="$attrs"></div>
<div v-if="condition1"></div>
<div v-else></div>
</template>
`
},
{
filename: 'test.vue',
code: `
<template>
<div v-if="condition1"></div>
<div v-else-if="condition2"></div>
<div v-if="condition3" v-bind="$attrs"></div>
</template>
`,
options: [{ checkMultiRootNodes: false }]
},
{
filename: 'test.vue',
code: `
Expand Down Expand Up @@ -151,6 +202,67 @@ ruleTester.run('no-duplicate-attr-inheritance', rule, {
line: 5
}
]
},
{
filename: 'test.vue',
code: `<template><div v-bind="$attrs"></div><div></div></template>`,
options: [{ checkMultiRootNodes: true }],
errors: [{ message: 'Set "inheritAttrs" to false.' }]
},
{
filename: 'test.vue',
code: `
<template>
<div v-if="condition1" v-bind="$attrs"></div>
<div v-else></div>
<div v-if="condition2"></div>
</template>
`,
options: [{ checkMultiRootNodes: true }],
errors: [{ message: 'Set "inheritAttrs" to false.' }]
},
// condition group as a single root node
{
filename: 'test.vue',
code: `
<template>
<div v-if="condition1" v-bind="$attrs"></div>
<div v-else-if="condition2"></div>
<div v-else></div>
</template>
`,
errors: [{ message: 'Set "inheritAttrs" to false.' }]
},
{
filename: 'test.vue',
code: `
<template>
<div v-if="condition1" v-bind="$attrs"></div>
<div v-else-if="condition2"></div>
<div v-else-if="condition3"></div>
<div v-else></div>
</template>
`,
errors: [{ message: 'Set "inheritAttrs" to false.' }]
},
{
filename: 'test.vue',
code: `
<template>
<div v-if="condition1" v-bind="$attrs"></div>
<div v-else></div>
</template>
`,
errors: [{ message: 'Set "inheritAttrs" to false.' }]
},
{
filename: 'test.vue',
code: `
<template>
<div v-if="condition1" v-bind="$attrs"></div>
</template>
`,
errors: [{ message: 'Set "inheritAttrs" to false.' }]
}
]
})

0 comments on commit 86a8138

Please sign in to comment.