-
-
Notifications
You must be signed in to change notification settings - Fork 688
New rule order-in-components
#42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
933adf6
Add new rule "order-in-components"
michalsnik 553fbcd
Add initial implementation of `order-in-components` rule
michalsnik ca2347b
Update test scripts
michalsnik bbcf47f
Improve order-in-components rule, add more test scenarios
michalsnik 448408f
Update readme
michalsnik 574cbeb
Update order-in-components docs
michalsnik 09b28d6
Update rule logic and fix tests
michalsnik 88db99b
Fix order logic
michalsnik e19c167
Check for arguments existance
michalsnik 2b0f870
Apply order-in-components rule only to exported ObjectExpressions in …
michalsnik a6facb2
Disable recommended setting in `order-in-components` rule
michalsnik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
} | ||
} | ||
} | ||
|
||
// ------------------------------------------------------------------------------ | ||
// 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that this line can throw
TypeError
ifcomponent()
with no argument exists.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, but I couldn't imagine such a component though. Fixed it anyway :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many people use ESLint in editor integrations. In that case, ESLint runs on every character typing, so TypeErrors mess development experience. 😉