Skip to content

feat: add define-props-destructuring rule #2736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions docs/rules/define-props-destructuring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/define-props-destructuring
description: enforce consistent style for props destructuring
---

# vue/define-props-destructuring

> enforce consistent style for props destructuring

- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>

## :book: Rule Details

This rule enforces a consistent style for handling Vue 3 Composition API props, allowing you to choose between requiring destructuring or prohibiting it.

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.

<eslint-code-block :rules="{'vue/define-props-destructuring': ['error']}">

```vue
<script setup>
// ✓ GOOD
const { foo } = defineProps(['foo'])
const { bar = 'default' } = defineProps(['bar'])

// ✗ BAD
const props = defineProps(['foo'])
const propsWithDefaults = withDefaults(defineProps(['foo']), { foo: 'default' })

// ✗ BAD
const { baz } = withDefaults(defineProps(['baz']), { baz: 'default' })
</script>
```

</eslint-code-block>

The rule applies to both JavaScript and TypeScript props:

<eslint-code-block :rules="{'vue/define-props-destructuring': ['error']}">

```vue
<script setup lang="ts">
// ✓ GOOD
const { foo } = defineProps<{ foo?: string }>()
const { bar = 'default' } = defineProps<{ bar?: string }>()

// ✗ BAD
const props = defineProps<{ foo?: string }>()
const propsWithDefaults = withDefaults(defineProps<{ foo?: string }>(), { foo: 'default' })
</script>
```

</eslint-code-block>

## :wrench: Options

```js
{
"vue/define-props-destructuring": ["error", {
"destructure": "always" | "never"
}]
}
```

- `destructure` - Sets the destructuring preference for props
- `"always"` (default) - Requires destructuring when using `defineProps` and warns against using `withDefaults` with destructuring
- `"never"` - Requires using a variable to store props and prohibits destructuring

### `"destructure": "never"`

<eslint-code-block :rules="{'vue/define-props-destructuring': ['error', { destructure: 'never' }]}">

```vue
<script setup>
// ✓ GOOD
const props = defineProps(['foo'])
const propsWithDefaults = withDefaults(defineProps(['foo']), { foo: 'default' })

// ✗ BAD
const { foo } = defineProps(['foo'])
</script>
```

</eslint-code-block>

## :books: Further Reading

- [Reactive Props Destructure](https://vuejs.org/guide/components/props.html#reactive-props-destructure)

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-props-destructuring.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-props-destructuring.js)
2 changes: 2 additions & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ For example:
| [vue/define-emits-declaration] | enforce declaration style of `defineEmits` | | :hammer: |
| [vue/define-macros-order] | enforce order of compiler macros (`defineProps`, `defineEmits`, etc.) | :wrench::bulb: | :lipstick: |
| [vue/define-props-declaration] | enforce declaration style of `defineProps` | | :hammer: |
| [vue/define-props-destructuring] | enforce consistent style for props destructuring | | :hammer: |
| [vue/enforce-style-attribute] | enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags | | :hammer: |
| [vue/html-button-has-type] | disallow usage of button without an explicit type attribute | | :hammer: |
| [vue/html-comment-content-newline] | enforce unified line break in HTML comments | :wrench: | :lipstick: |
Expand Down Expand Up @@ -398,6 +399,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
[vue/define-emits-declaration]: ./define-emits-declaration.md
[vue/define-macros-order]: ./define-macros-order.md
[vue/define-props-declaration]: ./define-props-declaration.md
[vue/define-props-destructuring]: ./define-props-destructuring.md
[vue/dot-location]: ./dot-location.md
[vue/dot-notation]: ./dot-notation.md
[vue/enforce-style-attribute]: ./enforce-style-attribute.md
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const plugin = {
'define-emits-declaration': require('./rules/define-emits-declaration'),
'define-macros-order': require('./rules/define-macros-order'),
'define-props-declaration': require('./rules/define-props-declaration'),
'define-props-destructuring': require('./rules/define-props-destructuring'),
'dot-location': require('./rules/dot-location'),
'dot-notation': require('./rules/dot-notation'),
'enforce-style-attribute': require('./rules/enforce-style-attribute'),
Expand Down
79 changes: 79 additions & 0 deletions lib/rules/define-props-destructuring.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* @author Wayne Zhang
* See LICENSE file in root directory for full license.
*/
'use strict'

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

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce consistent style for props destructuring',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/define-props-destructuring.html'
},
fixable: null,
schema: [
{
type: 'object',
properties: {
destructure: {
enum: ['always', 'never']
}
},
additionalProperties: false
}
],
messages: {
preferDestructuring: 'Prefer destructuring from `defineProps` directly.',
avoidDestructuring: 'Avoid destructuring from `defineProps`.',
avoidWithDefaults: 'Avoid using `withDefaults` with destructuring.'
}
},
/** @param {RuleContext} context */
create(context) {
const options = context.options[0] || {}
const destructurePreference = options.destructure || 'always'

return utils.compositingVisitors(
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(node, props) {
const hasNoArgs = props.filter((prop) => prop.propName).length === 0
if (hasNoArgs) {
return
}

const hasDestructure = utils.isUsingPropsDestructure(node)
const hasWithDefaults = utils.hasWithDefaults(node)

if (destructurePreference === 'never') {
if (hasDestructure) {
context.report({
node,
messageId: 'avoidDestructuring'
})
}
return
}

if (!hasDestructure) {
context.report({
node,
messageId: 'preferDestructuring'
})
return
}

if (hasWithDefaults) {
context.report({
node: node.parent.callee,
messageId: 'avoidWithDefaults'
})
}
}
})
)
}
}
Loading
Loading