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

Appearance settings

Commit a4e807c

Browse filesBrowse files
authored
feat: add no-required-prop-with-default rule (#1943)
* feat: add optional-props-using-with-defaults * feat: improve rule name * feat: change to problem * feat: fix comments * feat: add suggest to rule
1 parent e9964e1 commit a4e807c
Copy full SHA for a4e807c

File tree

5 files changed

+1175
-0
lines changed
Filter options

5 files changed

+1175
-0
lines changed

‎docs/rules/README.md

Copy file name to clipboardExpand all lines: docs/rules/README.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ For example:
230230
| [vue/no-multiple-objects-in-class](./no-multiple-objects-in-class.md) | disallow to pass multiple objects into array to class | | :hammer: |
231231
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | :bulb: | :hammer: |
232232
| [vue/no-ref-object-destructure](./no-ref-object-destructure.md) | disallow destructuring of ref objects that can lead to loss of reactivity | | :warning: |
233+
| [vue/no-required-prop-with-default](./no-required-prop-with-default.md) | enforce props with default values ​​to be optional | :wrench::bulb: | :warning: |
233234
| [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block | | :hammer: |
234235
| [vue/no-restricted-call-after-await](./no-restricted-call-after-await.md) | disallow asynchronously called restricted methods | | :hammer: |
235236
| [vue/no-restricted-class](./no-restricted-class.md) | disallow specific classes in Vue components | | :warning: |
+96Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-required-prop-with-default
5+
description: enforce props with default values ​​to be optional
6+
---
7+
# vue/no-required-prop-with-default
8+
9+
> enforce props with default values ​​to be optional
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
13+
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
14+
15+
## :book: Rule Details
16+
17+
If a prop is declared with a default value, whether it is required or not, we can always skip it in actual use. In that situation, the default value would be applied.
18+
So, a required prop with a default value is essentially the same as an optional prop.
19+
This rule enforces all props with default values to be optional.
20+
21+
<eslint-code-block fix :rules="{'vue/no-required-prop-with-default': ['error', { autoFix: true }]}">
22+
23+
```vue
24+
<script setup lang="ts">
25+
/* ✓ GOOD */
26+
const props = withDefaults(
27+
defineProps<{
28+
name?: string | number
29+
age?: number
30+
}>(),
31+
{
32+
name: "Foo",
33+
}
34+
);
35+
36+
/* ✗ BAD */
37+
const props = withDefaults(
38+
defineProps<{
39+
name: string | number
40+
age?: number
41+
}>(),
42+
{
43+
name: "Foo",
44+
}
45+
);
46+
</script>
47+
```
48+
49+
</eslint-code-block>
50+
51+
<eslint-code-block fix :rules="{'vue/no-required-prop-with-default': ['error', { autoFix: true }]}">
52+
53+
```vue
54+
<script>
55+
export default {
56+
/* ✓ GOOD */
57+
props: {
58+
name: {
59+
required: true,
60+
default: 'Hello'
61+
}
62+
}
63+
64+
/* ✗ BAD */
65+
props: {
66+
name: {
67+
required: true,
68+
default: 'Hello'
69+
}
70+
}
71+
}
72+
</script>
73+
```
74+
75+
</eslint-code-block>
76+
77+
## :wrench: Options
78+
79+
```json
80+
{
81+
"vue/no-required-prop-with-default": ["error", {
82+
"autofix": false,
83+
}]
84+
}
85+
```
86+
87+
- `"autofix"` ... If `true`, enable autofix. (Default: `false`)
88+
89+
## :couple: Related Rules
90+
91+
- [vue/require-default-prop](./require-default-prop.md)
92+
93+
## :mag: Implementation
94+
95+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-required-prop-with-default.js)
96+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-required-prop-with-default.js)

‎lib/index.js

Copy file name to clipboardExpand all lines: lib/index.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ module.exports = {
107107
'no-potential-component-option-typo': require('./rules/no-potential-component-option-typo'),
108108
'no-ref-as-operand': require('./rules/no-ref-as-operand'),
109109
'no-ref-object-destructure': require('./rules/no-ref-object-destructure'),
110+
'no-required-prop-with-default': require('./rules/no-required-prop-with-default'),
110111
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
111112
'no-reserved-keys': require('./rules/no-reserved-keys'),
112113
'no-reserved-props': require('./rules/no-reserved-props'),
+155Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/**
2+
* @author @neferqiqi
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
// ------------------------------------------------------------------------------
7+
// Requirements
8+
// ------------------------------------------------------------------------------
9+
10+
const utils = require('../utils')
11+
/**
12+
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
13+
* @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
14+
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
15+
* @typedef {import('../utils').ComponentUnknownProp} ComponentUnknownProp
16+
* @typedef {import('../utils').ComponentProp} ComponentProp
17+
*/
18+
19+
// ------------------------------------------------------------------------------
20+
// Rule Definition
21+
// ------------------------------------------------------------------------------
22+
23+
module.exports = {
24+
meta: {
25+
hasSuggestions: true,
26+
type: 'problem',
27+
docs: {
28+
description: 'enforce props with default values ​​to be optional',
29+
categories: undefined,
30+
url: 'https://eslint.vuejs.org/rules/no-required-prop-with-default.html'
31+
},
32+
fixable: 'code',
33+
schema: [
34+
{
35+
type: 'object',
36+
properties: {
37+
autofix: {
38+
type: 'boolean'
39+
}
40+
},
41+
additionalProperties: false
42+
}
43+
],
44+
messages: {
45+
requireOptional: `Prop "{{ key }}" should be optional.`,
46+
fixRequiredProp: `Change this prop to be optional.`
47+
}
48+
},
49+
/** @param {RuleContext} context */
50+
create(context) {
51+
let canAutoFix = false
52+
const option = context.options[0]
53+
if (option) {
54+
canAutoFix = option.autofix
55+
}
56+
57+
/**
58+
* @param {ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp | ComponentProp} prop
59+
* */
60+
const handleObjectProp = (prop) => {
61+
if (
62+
prop.type === 'object' &&
63+
prop.propName &&
64+
prop.value.type === 'ObjectExpression' &&
65+
utils.findProperty(prop.value, 'default')
66+
) {
67+
const requiredProperty = utils.findProperty(prop.value, 'required')
68+
if (!requiredProperty) return
69+
const requiredNode = requiredProperty.value
70+
if (
71+
requiredNode &&
72+
requiredNode.type === 'Literal' &&
73+
!!requiredNode.value
74+
) {
75+
context.report({
76+
node: prop.node,
77+
loc: prop.node.loc,
78+
data: {
79+
key: prop.propName
80+
},
81+
messageId: 'requireOptional',
82+
fix: canAutoFix
83+
? (fixer) => fixer.replaceText(requiredNode, 'false')
84+
: null,
85+
suggest: canAutoFix
86+
? null
87+
: [
88+
{
89+
messageId: 'fixRequiredProp',
90+
fix: (fixer) => fixer.replaceText(requiredNode, 'false')
91+
}
92+
]
93+
})
94+
}
95+
}
96+
}
97+
98+
return utils.compositingVisitors(
99+
utils.defineVueVisitor(context, {
100+
onVueObjectEnter(node) {
101+
utils.getComponentPropsFromOptions(node).map(handleObjectProp)
102+
}
103+
}),
104+
utils.defineScriptSetupVisitor(context, {
105+
onDefinePropsEnter(node, props) {
106+
if (!utils.hasWithDefaults(node)) {
107+
props.map(handleObjectProp)
108+
return
109+
}
110+
const withDefaultsProps = Object.keys(
111+
utils.getWithDefaultsPropExpressions(node)
112+
)
113+
const requiredProps = props.flatMap((item) =>
114+
item.type === 'type' && item.required ? [item] : []
115+
)
116+
117+
for (const prop of requiredProps) {
118+
if (withDefaultsProps.includes(prop.propName)) {
119+
// skip setter & getter case
120+
if (
121+
prop.node.type === 'TSMethodSignature' &&
122+
(prop.node.kind === 'get' || prop.node.kind === 'set')
123+
) {
124+
return
125+
}
126+
// skip computed
127+
if (prop.node.computed) {
128+
return
129+
}
130+
context.report({
131+
node: prop.node,
132+
loc: prop.node.loc,
133+
data: {
134+
key: prop.propName
135+
},
136+
messageId: 'requireOptional',
137+
fix: canAutoFix
138+
? (fixer) => fixer.insertTextAfter(prop.key, '?')
139+
: null,
140+
suggest: canAutoFix
141+
? null
142+
: [
143+
{
144+
messageId: 'fixRequiredProp',
145+
fix: (fixer) => fixer.insertTextAfter(prop.key, '?')
146+
}
147+
]
148+
})
149+
}
150+
}
151+
}
152+
})
153+
)
154+
}
155+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.