Skip to content

Commit

Permalink
feat: add optional-props-using-with-defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
neferqiqi committed Aug 8, 2022
1 parent f358817 commit a3dfc07
Show file tree
Hide file tree
Showing 5 changed files with 818 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -247,6 +247,7 @@ For example:
| [vue/no-useless-mustaches](./no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: | :hammer: |
| [vue/no-useless-v-bind](./no-useless-v-bind.md) | disallow unnecessary `v-bind` directives | :wrench: | :hammer: |
| [vue/no-v-text](./no-v-text.md) | disallow use of v-text | | :hammer: |
| [vue/optional-props-using-with-defaults](./optional-props-using-with-defaults.md) | enforce props with default values ​​to be optional | :wrench: | :hammer: |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: | :lipstick: |
| [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md) | enforce `Boolean` comes first in component prop types | :bulb: | :warning: |
| [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: | :hammer: |
Expand Down
55 changes: 55 additions & 0 deletions docs/rules/optional-props-using-with-defaults.md
@@ -0,0 +1,55 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/optional-props-using-with-defaults
description: enforce props with default values ​​to be optional
---
# vue/optional-props-using-with-defaults

> enforce props with default values ​​to be optional
- :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.

## :book: Rule Details

This rule enforce props with default values ​​to be optional.
Because when a required prop declared with a default value, but it doesn't be passed value when using it, it will be assigned the default value. So a required prop with default value is same as a optional prop.

<eslint-code-block fix :rules="{'vue/optional-props-using-with-defaults': ['error']}">

```vue
<script setup lang="ts">
/* ✓ GOOD */
const props = withDefaults(
defineProps<{
name?: string | number
age?: number
}>(),
{
name: "Foo",
}
);
/* ✗ BAD */
const props = withDefaults(
defineProps<{
name: string | number
age?: number
}>(),
{
name: "Foo",
}
);
</script>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/optional-props-using-with-defaults.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/optional-props-using-with-defaults.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -154,6 +154,7 @@ module.exports = {
'object-shorthand': require('./rules/object-shorthand'),
'one-component-per-file': require('./rules/one-component-per-file'),
'operator-linebreak': require('./rules/operator-linebreak'),
'optional-props-using-with-defaults': require('./rules/optional-props-using-with-defaults'),
'order-in-components': require('./rules/order-in-components'),
'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
'prefer-import-from-vue': require('./rules/prefer-import-from-vue'),
Expand Down
96 changes: 96 additions & 0 deletions lib/rules/optional-props-using-with-defaults.js
@@ -0,0 +1,96 @@
/**
* @author @neferqiqi
* See LICENSE file in root directory for full license.
*/
'use strict'
// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const utils = require('../utils')
/**
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
*/

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

// ...

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce props with default values ​​to be optional',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/optional-props-using-with-defaults.html'
},
fixable: 'code',
schema: [],
messages: {
// ...
}
},
/** @param {RuleContext} context */
create(context) {
/**
* @param {ComponentTypeProp} prop
* @param {Token[]} tokens
* */
const findKeyToken = (prop, tokens) =>
tokens.find((token) => {
const isKeyIdentifierEqual =
prop.key.type === 'Identifier' && token.value === prop.key.name
const isKeyLiteralEqual =
prop.key.type === 'Literal' && token.value === prop.key.raw
return isKeyIdentifierEqual || isKeyLiteralEqual
})

return utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(node, props) {
if (!utils.hasWithDefaults(node)) {
return
}
const withDefaultsProps = Object.keys(
utils.getWithDefaultsPropExpressions(node)
)
const requiredProps = props.flatMap((item) =>
item.type === 'type' && item.required ? [item] : []
)

for (const prop of requiredProps) {
if (withDefaultsProps.includes(prop.propName)) {
const firstToken = context.getSourceCode().getFirstToken(prop.node)
// skip setter & getter case
if (firstToken.value === 'get' || firstToken.value === 'set') {
return
}
// skip computed
if (prop.node.computed) {
return
}
const keyToken = findKeyToken(
prop,
context.getSourceCode().getTokens(prop.node)
)
if (!keyToken) return
context.report({
node: prop.node,
loc: prop.node.loc,
data: {
key: prop.propName
},
message: `Prop "{{ key }}" should be optional.`,
fix: (fixer) => fixer.insertTextAfter(keyToken, '?')
})
}
}
}
})
}
}

0 comments on commit a3dfc07

Please sign in to comment.