Skip to content

Commit ce92931

Browse files
waynzhFloEdelmann
andauthored
feat: add define-props-destructuring rule (#2736)
Co-authored-by: Flo Edelmann <[email protected]>
1 parent c68cb1a commit ce92931

File tree

5 files changed

+389
-0
lines changed

5 files changed

+389
-0
lines changed
+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/define-props-destructuring
5+
description: enforce consistent style for props destructuring
6+
---
7+
8+
# vue/define-props-destructuring
9+
10+
> enforce consistent style for props destructuring
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>
13+
14+
## :book: Rule Details
15+
16+
This rule enforces a consistent style for handling Vue 3 Composition API props, allowing you to choose between requiring destructuring or prohibiting it.
17+
18+
By default, the rule requires you to use destructuring syntax when using `defineProps` instead of storing props in a variable and warns against combining `withDefaults` with destructuring.
19+
20+
<eslint-code-block :rules="{'vue/define-props-destructuring': ['error']}">
21+
22+
```vue
23+
<script setup>
24+
// ✓ GOOD
25+
const { foo } = defineProps(['foo'])
26+
const { bar = 'default' } = defineProps(['bar'])
27+
28+
// ✗ BAD
29+
const props = defineProps(['foo'])
30+
const propsWithDefaults = withDefaults(defineProps(['foo']), { foo: 'default' })
31+
32+
// ✗ BAD
33+
const { baz } = withDefaults(defineProps(['baz']), { baz: 'default' })
34+
</script>
35+
```
36+
37+
</eslint-code-block>
38+
39+
The rule applies to both JavaScript and TypeScript props:
40+
41+
<eslint-code-block :rules="{'vue/define-props-destructuring': ['error']}">
42+
43+
```vue
44+
<script setup lang="ts">
45+
// ✓ GOOD
46+
const { foo } = defineProps<{ foo?: string }>()
47+
const { bar = 'default' } = defineProps<{ bar?: string }>()
48+
49+
// ✗ BAD
50+
const props = defineProps<{ foo?: string }>()
51+
const propsWithDefaults = withDefaults(defineProps<{ foo?: string }>(), { foo: 'default' })
52+
</script>
53+
```
54+
55+
</eslint-code-block>
56+
57+
## :wrench: Options
58+
59+
```js
60+
{
61+
"vue/define-props-destructuring": ["error", {
62+
"destructure": "always" | "never"
63+
}]
64+
}
65+
```
66+
67+
- `destructure` - Sets the destructuring preference for props
68+
- `"always"` (default) - Requires destructuring when using `defineProps` and warns against using `withDefaults` with destructuring
69+
- `"never"` - Requires using a variable to store props and prohibits destructuring
70+
71+
### `"destructure": "never"`
72+
73+
<eslint-code-block :rules="{'vue/define-props-destructuring': ['error', { destructure: 'never' }]}">
74+
75+
```vue
76+
<script setup>
77+
// ✓ GOOD
78+
const props = defineProps(['foo'])
79+
const propsWithDefaults = withDefaults(defineProps(['foo']), { foo: 'default' })
80+
81+
// ✗ BAD
82+
const { foo } = defineProps(['foo'])
83+
</script>
84+
```
85+
86+
</eslint-code-block>
87+
88+
## :books: Further Reading
89+
90+
- [Reactive Props Destructure](https://vuejs.org/guide/components/props.html#reactive-props-destructure)
91+
92+
## :mag: Implementation
93+
94+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-props-destructuring.js)
95+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-props-destructuring.js)

docs/rules/index.md

+2
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ For example:
218218
| [vue/define-emits-declaration] | enforce declaration style of `defineEmits` | | :hammer: |
219219
| [vue/define-macros-order] | enforce order of compiler macros (`defineProps`, `defineEmits`, etc.) | :wrench::bulb: | :lipstick: |
220220
| [vue/define-props-declaration] | enforce declaration style of `defineProps` | | :hammer: |
221+
| [vue/define-props-destructuring] | enforce consistent style for props destructuring | | :hammer: |
221222
| [vue/enforce-style-attribute] | enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags | | :hammer: |
222223
| [vue/html-button-has-type] | disallow usage of button without an explicit type attribute | | :hammer: |
223224
| [vue/html-comment-content-newline] | enforce unified line break in HTML comments | :wrench: | :lipstick: |
@@ -398,6 +399,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
398399
[vue/define-emits-declaration]: ./define-emits-declaration.md
399400
[vue/define-macros-order]: ./define-macros-order.md
400401
[vue/define-props-declaration]: ./define-props-declaration.md
402+
[vue/define-props-destructuring]: ./define-props-destructuring.md
401403
[vue/dot-location]: ./dot-location.md
402404
[vue/dot-notation]: ./dot-notation.md
403405
[vue/enforce-style-attribute]: ./enforce-style-attribute.md

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const plugin = {
5858
'define-emits-declaration': require('./rules/define-emits-declaration'),
5959
'define-macros-order': require('./rules/define-macros-order'),
6060
'define-props-declaration': require('./rules/define-props-declaration'),
61+
'define-props-destructuring': require('./rules/define-props-destructuring'),
6162
'dot-location': require('./rules/dot-location'),
6263
'dot-notation': require('./rules/dot-notation'),
6364
'enforce-style-attribute': require('./rules/enforce-style-attribute'),
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* @author Wayne Zhang
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
module.exports = {
10+
meta: {
11+
type: 'suggestion',
12+
docs: {
13+
description: 'enforce consistent style for props destructuring',
14+
categories: undefined,
15+
url: 'https://eslint.vuejs.org/rules/define-props-destructuring.html'
16+
},
17+
fixable: null,
18+
schema: [
19+
{
20+
type: 'object',
21+
properties: {
22+
destructure: {
23+
enum: ['always', 'never']
24+
}
25+
},
26+
additionalProperties: false
27+
}
28+
],
29+
messages: {
30+
preferDestructuring: 'Prefer destructuring from `defineProps` directly.',
31+
avoidDestructuring: 'Avoid destructuring from `defineProps`.',
32+
avoidWithDefaults: 'Avoid using `withDefaults` with destructuring.'
33+
}
34+
},
35+
/** @param {RuleContext} context */
36+
create(context) {
37+
const options = context.options[0] || {}
38+
const destructurePreference = options.destructure || 'always'
39+
40+
return utils.compositingVisitors(
41+
utils.defineScriptSetupVisitor(context, {
42+
onDefinePropsEnter(node, props) {
43+
const hasNoArgs = props.filter((prop) => prop.propName).length === 0
44+
if (hasNoArgs) {
45+
return
46+
}
47+
48+
const hasDestructure = utils.isUsingPropsDestructure(node)
49+
const hasWithDefaults = utils.hasWithDefaults(node)
50+
51+
if (destructurePreference === 'never') {
52+
if (hasDestructure) {
53+
context.report({
54+
node,
55+
messageId: 'avoidDestructuring'
56+
})
57+
}
58+
return
59+
}
60+
61+
if (!hasDestructure) {
62+
context.report({
63+
node,
64+
messageId: 'preferDestructuring'
65+
})
66+
return
67+
}
68+
69+
if (hasWithDefaults) {
70+
context.report({
71+
node: node.parent.callee,
72+
messageId: 'avoidWithDefaults'
73+
})
74+
}
75+
}
76+
})
77+
)
78+
}
79+
}

0 commit comments

Comments
 (0)