Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
prefer-prop-type-boolean-first
rule (#1822)
* Add `prefer-prop-type-boolean-first` rule * fix docs * update docs * merge valid test cases * add more tests * Add explanation to eslint-disable comment Co-authored-by: Flo Edelmann <florian-edelmann@online.de>
- Loading branch information
1 parent
0924d62
commit f5f4f97
Showing
6 changed files
with
495 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
--- | ||
pageClass: rule-details | ||
sidebarDepth: 0 | ||
title: vue/prefer-prop-type-boolean-first | ||
description: enforce `Boolean` comes first in component prop types | ||
--- | ||
# vue/prefer-prop-type-boolean-first | ||
|
||
> enforce `Boolean` comes first in component prop types | ||
- :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 | ||
|
||
When declaring types of a property in component, we can use array style to accept multiple types. | ||
|
||
When using components in template, | ||
we can use shorthand-style property if its value is `true`. | ||
|
||
However, if a property allows `Boolean` or `String` and we use it with shorthand form in somewhere else, | ||
different types order can introduce different behaviors: | ||
If `Boolean` comes first, it will be `true`; if `String` comes first, it will be `""` (empty string). | ||
|
||
See [this demo](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCBNeUNvbXBvbmVudCBmcm9tICcuL015Q29tcG9uZW50LnZ1ZSdcbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIFNob3J0aGFuZCBmb3JtOlxuICA8TXlDb21wb25lbnQgYm9vbCBib29sLW9yLXN0cmluZyBzdHJpbmctb3ItYm9vbCAvPlxuICBcbiAgTG9uZ2hhbmQgZm9ybTpcbiAgPE15Q29tcG9uZW50IDpib29sPVwidHJ1ZVwiIDpib29sLW9yLXN0cmluZz1cInRydWVcIiA6c3RyaW5nLW9yLWJvb2w9XCJ0cnVlXCIgLz5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSIsIk15Q29tcG9uZW50LnZ1ZSI6IjxzY3JpcHQ+XG5leHBvcnQgZGVmYXVsdCB7XG4gIHByb3BzOiB7XG4gICAgYm9vbDogQm9vbGVhbixcbiAgICBib29sT3JTdHJpbmc6IFtCb29sZWFuLCBTdHJpbmddLFxuICAgIHN0cmluZ09yQm9vbDogW1N0cmluZywgQm9vbGVhbl0sXG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxwcmU+XG5ib29sOiB7e2Jvb2x9fSAoe3sgdHlwZW9mIGJvb2wgfX0pXG5ib29sT3JTdHJpbmc6IHt7Ym9vbE9yU3RyaW5nfX0gKHt7IHR5cGVvZiBib29sT3JTdHJpbmcgfX0pXG5zdHJpbmdPckJvb2w6IHt7c3RyaW5nT3JCb29sfX0gKHt7IHR5cGVvZiBzdHJpbmdPckJvb2wgfX0pXG4gIDwvcHJlPlxuPC90ZW1wbGF0ZT4ifQ==). | ||
|
||
<eslint-code-block :rules="{'vue/prefer-prop-type-boolean-first': ['error']}"> | ||
|
||
```vue | ||
<script> | ||
export default { | ||
props: { | ||
// ✓ GOOD | ||
a: Boolean, | ||
b: String, | ||
c: [Boolean, String], | ||
d: { | ||
type: [Boolean, String] | ||
}, | ||
// ✗ BAD | ||
e: [String, Boolean], | ||
f: { | ||
type: [String, Boolean] | ||
} | ||
} | ||
} | ||
</script> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
## :couple: Related Rules | ||
|
||
- [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | ||
|
||
## :mag: Implementation | ||
|
||
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-prop-type-boolean-first.js) | ||
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-prop-type-boolean-first.js) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/** | ||
* @author Pig Fang | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
'use strict' | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Requirements | ||
// ------------------------------------------------------------------------------ | ||
|
||
const utils = require('../utils') | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Helpers | ||
// ------------------------------------------------------------------------------ | ||
|
||
/** | ||
* @param {ArrayExpression} node | ||
* @param {RuleContext} context | ||
*/ | ||
function checkArrayExpression(node, context) { | ||
const booleanType = node.elements.find( | ||
(element) => | ||
element && element.type === 'Identifier' && element.name === 'Boolean' | ||
) | ||
if (!booleanType) { | ||
return | ||
} | ||
const booleanTypeIndex = node.elements.indexOf(booleanType) | ||
if (booleanTypeIndex > 0) { | ||
context.report({ | ||
node: booleanType, | ||
messageId: 'shouldBeFirst', | ||
suggest: [ | ||
{ | ||
messageId: 'moveToFirst', | ||
fix: (fixer) => { | ||
const sourceCode = context.getSourceCode() | ||
|
||
const elements = node.elements.slice() | ||
elements.splice(booleanTypeIndex, 1) | ||
const code = elements | ||
.filter(utils.isDef) | ||
.map((element) => sourceCode.getText(element)) | ||
code.unshift('Boolean') | ||
|
||
return fixer.replaceText(node, `[${code.join(', ')}]`) | ||
} | ||
} | ||
] | ||
}) | ||
} | ||
} | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'enforce `Boolean` comes first in component prop types', | ||
categories: undefined, | ||
url: 'https://eslint.vuejs.org/rules/prefer-prop-type-boolean-first.html' | ||
}, | ||
fixable: null, | ||
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- `context.report` with suggestion is not recognized in `checkArrayExpression` | ||
hasSuggestions: true, | ||
schema: [], | ||
messages: { | ||
shouldBeFirst: 'Type `Boolean` should be at first in prop types.', | ||
moveToFirst: 'Move `Boolean` to be first in prop types.' | ||
} | ||
}, | ||
/** @param {RuleContext} context */ | ||
create(context) { | ||
/** | ||
* @param {import('../utils').ComponentProp} prop | ||
*/ | ||
function checkProperty(prop) { | ||
const { value } = prop | ||
if (!value) { | ||
return | ||
} | ||
|
||
if (value.type === 'ArrayExpression') { | ||
checkArrayExpression(value, context) | ||
} else if (value.type === 'ObjectExpression') { | ||
const type = value.properties.find( | ||
/** @return {property is Property} */ | ||
(property) => | ||
property.type === 'Property' && | ||
utils.getStaticPropertyName(property) === 'type' | ||
) | ||
if (!type || type.value.type !== 'ArrayExpression') { | ||
return | ||
} | ||
checkArrayExpression(type.value, context) | ||
} | ||
} | ||
|
||
return utils.compositingVisitors( | ||
utils.defineScriptSetupVisitor(context, { | ||
onDefinePropsEnter(_, props) { | ||
props.forEach(checkProperty) | ||
} | ||
}), | ||
utils.executeOnVue(context, (obj) => { | ||
const props = utils.getComponentPropsFromOptions(obj) | ||
props.forEach(checkProperty) | ||
}) | ||
) | ||
} | ||
} |
Oops, something went wrong.