Skip to content

Commit

Permalink
Add prefer-true-attribute-shorthand rule (#1796)
Browse files Browse the repository at this point in the history
* Add `prefer-true-attribute-shorthand` rule

* fix typo

* accept suggestions

* accept option `"always"` or `"never"`

* ignore native HTML elements

* provide suggestions instead of auto fix

* meta update
  • Loading branch information
g-plane committed Feb 12, 2022
1 parent 7ebbd85 commit 1d1be85
Show file tree
Hide file tree
Showing 5 changed files with 505 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -353,6 +353,7 @@ For example:
| [vue/no-v-text](./no-v-text.md) | disallow use of v-text | |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
| [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: |
| [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: |
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | |
| [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | :bulb: |
| [vue/require-expose](./require-expose.md) | require declare public properties using `expose` | :bulb: |
Expand Down
110 changes: 110 additions & 0 deletions docs/rules/prefer-true-attribute-shorthand.md
@@ -0,0 +1,110 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/prefer-true-attribute-shorthand
description: require shorthand form attribute when `v-bind` value is `true`
---
# vue/prefer-true-attribute-shorthand

> require shorthand form attribute when `v-bind` value is `true`
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

## :book: Rule Details

`v-bind` attribute with `true` value usually can be written in shorthand form. This can reduce verbosity.

<eslint-code-block :rules="{'vue/prefer-true-attribute-shorthand': ['error']}">

```vue
<template>
<!-- ✗ BAD -->
<MyComponent v-bind:show="true" />
<MyComponent :show="true" />
<!-- ✓ GOOD -->
<MyComponent show />
<MyComponent another-prop="true" />
</template>
```

</eslint-code-block>

::: warning Warning
The shorthand form is not always equivalent! If a prop accepts multiple types, but Boolean is not the first one, a shorthand prop won't pass `true`.
:::

```vue
<script>
export default {
name: 'MyComponent',
props: {
bool: Boolean,
boolOrString: [Boolean, String],
stringOrBool: [String, Boolean],
}
}
</script>
```

**Shorthand form:**

```vue
<MyComponent bool bool-or-string string-or-bool />
```

```
bool: true (boolean)
boolOrString: true (boolean)
stringOrBool: "" (string)
```

**Longhand form:**

```vue
<MyComponent :bool="true" :bool-or-string="true" :string-or-bool="true" />
```

```
bool: true (boolean)
boolOrString: true (boolean)
stringOrBool: true (boolean)
```

Those two calls will introduce different render result. See [this demo](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCBNeUNvbXBvbmVudCBmcm9tICcuL015Q29tcG9uZW50LnZ1ZSdcbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIFNob3J0aGFuZCBmb3JtOlxuICA8TXlDb21wb25lbnQgYm9vbCBib29sLW9yLXN0cmluZyBzdHJpbmctb3ItYm9vbCAvPlxuICBcbiAgTG9uZ2hhbmQgZm9ybTpcbiAgPE15Q29tcG9uZW50IDpib29sPVwidHJ1ZVwiIDpib29sLW9yLXN0cmluZz1cInRydWVcIiA6c3RyaW5nLW9yLWJvb2w9XCJ0cnVlXCIgLz5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSIsIk15Q29tcG9uZW50LnZ1ZSI6IjxzY3JpcHQ+XG5leHBvcnQgZGVmYXVsdCB7XG4gIHByb3BzOiB7XG4gICAgYm9vbDogQm9vbGVhbixcbiAgICBib29sT3JTdHJpbmc6IFtCb29sZWFuLCBTdHJpbmddLFxuICAgIHN0cmluZ09yQm9vbDogW1N0cmluZywgQm9vbGVhbl0sXG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxwcmU+XG5ib29sOiB7e2Jvb2x9fSAoe3sgdHlwZW9mIGJvb2wgfX0pXG5ib29sT3JTdHJpbmc6IHt7Ym9vbE9yU3RyaW5nfX0gKHt7IHR5cGVvZiBib29sT3JTdHJpbmcgfX0pXG5zdHJpbmdPckJvb2w6IHt7c3RyaW5nT3JCb29sfX0gKHt7IHR5cGVvZiBzdHJpbmdPckJvb2wgfX0pXG4gIDwvcHJlPlxuPC90ZW1wbGF0ZT4ifQ==).

## :wrench: Options

Default options is `"always"`.

```json
{
"vue/prefer-true-attribute-shorthand": ["error", "always" | "never"]
}
```

- `"always"` (default) ... requires shorthand form.
- `"never"` ... requires long form.

### `"never"`

<eslint-code-block :rules="{'vue/prefer-true-attribute-shorthand': ['error', 'never']}">

```vue
<template>
<!-- ✗ BAD -->
<MyComponent show />
<!-- ✓ GOOD -->
<MyComponent :show="true" />
<MyComponent v-bind:show="true" />
</template>
```

</eslint-code-block>

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-true-attribute-shorthand.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-true-attribute-shorthand.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -159,6 +159,7 @@ module.exports = {
'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
'prefer-separate-static-class': require('./rules/prefer-separate-static-class'),
'prefer-template': require('./rules/prefer-template'),
'prefer-true-attribute-shorthand': require('./rules/prefer-true-attribute-shorthand'),
'prop-name-casing': require('./rules/prop-name-casing'),
'quote-props': require('./rules/quote-props'),
'require-component-is': require('./rules/require-component-is'),
Expand Down
110 changes: 110 additions & 0 deletions lib/rules/prefer-true-attribute-shorthand.js
@@ -0,0 +1,110 @@
/**
* @author Pig Fang
* See LICENSE file in root directory for full license.
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

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

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

module.exports = {
meta: {
type: 'suggestion',
docs: {
description:
'require shorthand form attribute when `v-bind` value is `true`',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/prefer-true-attribute-shorthand.html'
},
fixable: null,
hasSuggestions: true,
schema: [{ enum: ['always', 'never'] }],
messages: {
expectShort:
"Boolean prop with 'true' value should be written in shorthand form.",
expectLong:
"Boolean prop with 'true' value should be written in long form.",
rewriteIntoShort: 'Rewrite this prop into shorthand form.',
rewriteIntoLongVueProp:
'Rewrite this prop into long-form Vue component prop.',
rewriteIntoLongHtmlAttr:
'Rewrite this prop into long-form HTML attribute.'
}
},
/** @param {RuleContext} context */
create(context) {
/** @type {'always' | 'never'} */
const option = context.options[0] || 'always'

return utils.defineTemplateBodyVisitor(context, {
VAttribute(node) {
if (!utils.isCustomComponent(node.parent.parent)) {
return
}

if (option === 'never' && !node.directive && !node.value) {
context.report({
node,
messageId: 'expectLong',
suggest: [
{
messageId: 'rewriteIntoLongVueProp',
fix: (fixer) =>
fixer.replaceText(node, `:${node.key.rawName}="true"`)
},
{
messageId: 'rewriteIntoLongHtmlAttr',
fix: (fixer) =>
fixer.replaceText(
node,
`${node.key.rawName}="${node.key.rawName}"`
)
}
]
})
return
}

if (option !== 'always') {
return
}

if (
!node.directive ||
!node.value ||
!node.value.expression ||
node.value.expression.type !== 'Literal' ||
node.value.expression.value !== true
) {
return
}

const { argument } = node.key
if (!argument) {
return
}

context.report({
node,
messageId: 'expectShort',
suggest: [
{
messageId: 'rewriteIntoShort',
fix: (fixer) => {
const sourceCode = context.getSourceCode()
return fixer.replaceText(node, sourceCode.getText(argument))
}
}
]
})
}
})
}
}

0 comments on commit 1d1be85

Please sign in to comment.