-
-
Notifications
You must be signed in to change notification settings - Fork 669
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add new rule "order-in-components" * Add initial implementation of `order-in-components` rule * Update test scripts * Improve order-in-components rule, add more test scenarios * Update readme * Update order-in-components docs * Update rule logic and fix tests * Fix order logic * Check for arguments existance * Apply order-in-components rule only to exported ObjectExpressions in .vue and .jsx files * Disable recommended setting in `order-in-components` rule
- Loading branch information
1 parent
a800d96
commit 9fff64d
Showing
7 changed files
with
518 additions
and
3 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,87 @@ | ||
# Keep proper order of properties in your components (order-in-components) | ||
|
||
This rule makes sure you keep declared order of properties in components. | ||
|
||
## :book: Rule Details | ||
|
||
Recommended order of properties is as follows: | ||
|
||
1. Options / Misc (`name`, `delimiters`, `functional`, `model`) | ||
2. Options / Assets (`components`, `directives`, `filters`) | ||
3. Options / Composition (`parent`, `mixins`, `extends`, `provide`, `inject`) | ||
4. `el` | ||
5. `template` | ||
6. `props` | ||
7. `propsData` | ||
8. `data` | ||
9. `computed` | ||
10. `watch` | ||
11. `lifecycleHooks` | ||
12. `methods` | ||
13. `render` | ||
14. `renderError` | ||
|
||
Note that `lifecycleHooks` is not a regular property - it indicates the group of all lifecycle hooks just to simplify the configuration. | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
|
||
export default { | ||
name: 'app', | ||
data () { | ||
return { | ||
msg: 'Welcome to Your Vue.js App' | ||
} | ||
}, | ||
props: { | ||
propA: Number, | ||
}, | ||
} | ||
|
||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
|
||
export default { | ||
name: 'app', | ||
props: { | ||
propA: Number, | ||
}, | ||
data () { | ||
return { | ||
msg: 'Welcome to Your Vue.js App' | ||
} | ||
}, | ||
} | ||
|
||
``` | ||
|
||
### Options | ||
|
||
If you want you can change the order providing the optional configuration in your `.eslintrc` file. Setting responsible for the above order looks like this: | ||
|
||
``` | ||
vue/order-in-components: [2, { | ||
order: [ | ||
['name', 'delimiters', 'functional', 'model'], | ||
['components', 'directives', 'filters'], | ||
['parent', 'mixins', 'extends', 'provide', 'inject'], | ||
'el', | ||
'template', | ||
'props', | ||
'propsData', | ||
'data', | ||
'computed', | ||
'watch', | ||
'lifecycle_hooks', | ||
'methods', | ||
'render', | ||
'renderError' | ||
] | ||
}] | ||
``` | ||
|
||
If you want some of properties to be treated equally in order you can group them into arrays, like we did with `name`, `delimiters`, `funcitonal` and `model`. |
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,148 @@ | ||
/** | ||
* @fileoverview Keep order of properties in components | ||
* @author Michał Sajnóg | ||
*/ | ||
'use strict' | ||
|
||
const defaultOrder = [ | ||
['name', 'delimiters', 'functional', 'model'], | ||
['components', 'directives', 'filters'], | ||
['parent', 'mixins', 'extends', 'provide', 'inject'], | ||
'el', | ||
'template', | ||
'props', | ||
'propsData', | ||
'data', | ||
'computed', | ||
'watch', | ||
'LIFECYCLE_HOOKS', | ||
'methods', | ||
'render', | ||
'renderError' | ||
] | ||
|
||
const groups = { | ||
LIFECYCLE_HOOKS: [ | ||
'beforeCreate', | ||
'created', | ||
'beforeMount', | ||
'mounted', | ||
'beforeUpdate', | ||
'updated', | ||
'activated', | ||
'deactivated', | ||
'beforeDestroy', | ||
'destroyed' | ||
] | ||
} | ||
|
||
function isComponentFile (node, path) { | ||
const isVueFile = path.endsWith('.vue') || path.endsWith('.jsx') | ||
return isVueFile && node.declaration.type === 'ObjectExpression' | ||
} | ||
|
||
function isVueComponent (node) { | ||
const callee = node.callee | ||
|
||
const isFullVueComponent = node.type === 'CallExpression' && | ||
callee.type === 'MemberExpression' && | ||
callee.object.type === 'Identifier' && | ||
callee.object.name === 'Vue' && | ||
callee.property.type === 'Identifier' && | ||
callee.property.name === 'component' && | ||
node.arguments.length && | ||
node.arguments.slice(-1)[0].type === 'ObjectExpression' | ||
|
||
const isDestructedVueComponent = callee.type === 'Identifier' && | ||
callee.name === 'component' | ||
|
||
return isFullVueComponent || isDestructedVueComponent | ||
} | ||
|
||
function isVueInstance (node) { | ||
const callee = node.callee | ||
return node.type === 'NewExpression' && | ||
callee.type === 'Identifier' && | ||
callee.name === 'Vue' && | ||
node.arguments.length && | ||
node.arguments[0].type === 'ObjectExpression' | ||
} | ||
|
||
function getOrderMap (order) { | ||
const orderMap = new Map() | ||
|
||
order.forEach((property, i) => { | ||
if (Array.isArray(property)) { | ||
property.forEach(p => orderMap.set(p, i)) | ||
} else { | ||
orderMap.set(property, i) | ||
} | ||
}) | ||
|
||
return orderMap | ||
} | ||
|
||
function checkOrder (propertiesNodes, orderMap, context) { | ||
const properties = propertiesNodes.map(property => property.key) | ||
|
||
properties.forEach((property, i) => { | ||
const propertiesAbove = properties.slice(0, i) | ||
const unorderedProperties = propertiesAbove | ||
.filter(p => orderMap.get(p.name) > orderMap.get(property.name)) | ||
.sort((p1, p2) => orderMap.get(p1.name) > orderMap.get(p2.name)) | ||
|
||
const firstUnorderedProperty = unorderedProperties[0] | ||
|
||
if (firstUnorderedProperty) { | ||
const line = firstUnorderedProperty.loc.start.line | ||
context.report({ | ||
node: property, | ||
message: `The "${property.name}" property should be above the "${firstUnorderedProperty.name}" property on line ${line}.` | ||
}) | ||
} | ||
}) | ||
} | ||
|
||
function create (context) { | ||
const options = context.options[0] || {} | ||
const order = options.order || defaultOrder | ||
const filePath = context.getFilename() | ||
|
||
const extendedOrder = order.map(property => groups[property] || property) | ||
const orderMap = getOrderMap(extendedOrder) | ||
|
||
return { | ||
ExportDefaultDeclaration (node) { | ||
// export default {} in .vue || .jsx | ||
if (!isComponentFile(node, filePath)) return | ||
checkOrder(node.declaration.properties, orderMap, context) | ||
}, | ||
CallExpression (node) { | ||
// Vue.component('xxx', {}) || component('xxx', {}) | ||
if (!isVueComponent(node)) return | ||
checkOrder(node.arguments.slice(-1)[0].properties, orderMap, context) | ||
}, | ||
NewExpression (node) { | ||
// new Vue({}) | ||
if (!isVueInstance(node)) return | ||
checkOrder(node.arguments[0].properties, orderMap, context) | ||
} | ||
} | ||
} | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
create, | ||
meta: { | ||
docs: { | ||
description: 'Keep order of properties in components', | ||
category: 'Best Practices', | ||
recommended: false | ||
}, | ||
fixable: null, | ||
schema: [] | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
Oops, something went wrong.