Skip to content

Commit ea1a563

Browse files
author
Loren
committed
sort keys
1 parent e8f130c commit ea1a563

File tree

6 files changed

+1326
-0
lines changed

6 files changed

+1326
-0
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ For example:
158158
| [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: |
159159
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | |
160160
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: |
161+
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys within components after the top level details | |
161162
| [vue/space-infix-ops](./space-infix-ops.md) | require spacing around infix operators | :wrench: |
162163
| [vue/space-unary-ops](./space-unary-ops.md) | enforce consistent spacing before or after unary operators | :wrench: |
163164
| [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: |

docs/rules/sort-keys.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/sort-keys
5+
description: enforce sort-keys in a manner that is compatible with order-in-components
6+
---
7+
# vue/sort-keys
8+
> enforce sort-keys within components after the top level details
9+
10+
This rule is almost the same rule as core [sorts-keys] rule but it will not error on top component properties allowing that order to be enforced with `order-in-components`.
11+
12+
## Options
13+
14+
```json
15+
{
16+
"sort-keys": ["error", "asc", {"caseSensitive": true, "natural": false, "minKeys": 2}]
17+
}
18+
```
19+
20+
The 1st option is `"asc"` or `"desc"`.
21+
22+
* `"asc"` (default) - enforce properties to be in ascending order.
23+
* `"desc"` - enforce properties to be in descending order.
24+
25+
The 2nd option is an object which has 3 properties.
26+
27+
* `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`.
28+
* `minKeys` - Specifies the minimum number of keys that an object should have in order for the object's unsorted keys to produce an error. Default is `2`, which means by default all objects with unsorted keys will result in lint errors.
29+
* `natural` - if `true`, enforce properties to be in natural order. Default is `false`. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting.
30+
31+
While using this rule, you may disable the normal `sort-keys` rule. This rule will apply to plain js files as well as Vue component scripts.
32+
33+
## :books: Further reading
34+
35+
- [sorts-keys]
36+
37+
[sorts-keys]: https://eslint.org/docs/rules/sorts-keys
38+
39+
## :mag: Implementation
40+
41+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/sort-keys.js)
42+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/sort-keys.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ module.exports = {
6868
'return-in-computed-property': require('./rules/return-in-computed-property'),
6969
'script-indent': require('./rules/script-indent'),
7070
'singleline-html-element-content-newline': require('./rules/singleline-html-element-content-newline'),
71+
'sort-keys': require('./rules/sort-keys'),
7172
'space-infix-ops': require('./rules/space-infix-ops'),
7273
'space-unary-ops': require('./rules/space-unary-ops'),
7374
'this-in-template': require('./rules/this-in-template'),

lib/rules/sort-keys.js

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/**
2+
* @fileoverview enforce sort-keys in a manner that is compatible with order-in-components
3+
* @author Loren Klingman
4+
* Original ESLint sort-keys by Toru Nagashima
5+
*/
6+
'use strict'
7+
8+
// ------------------------------------------------------------------------------
9+
// Requirements
10+
// ------------------------------------------------------------------------------
11+
12+
const naturalCompare = require('natural-compare')
13+
const utils = require('../utils')
14+
15+
// ------------------------------------------------------------------------------
16+
// Helpers
17+
// ------------------------------------------------------------------------------
18+
19+
/**
20+
* Gets the property name of the given `Property` node.
21+
*
22+
* - If the property's key is an `Identifier` node, this returns the key's name
23+
* whether it's a computed property or not.
24+
* - If the property has a static name, this returns the static name.
25+
* - Otherwise, this returns null.
26+
* @param {ASTNode} node The `Property` node to get.
27+
* @returns {string|null} The property name or null.
28+
* @private
29+
*/
30+
function getPropertyName (node) {
31+
const staticName = utils.getStaticPropertyName(node)
32+
33+
if (staticName !== null) {
34+
return staticName
35+
}
36+
37+
return node.key.name || null
38+
}
39+
40+
/**
41+
* Functions which check that the given 2 names are in specific order.
42+
*
43+
* Postfix `I` is meant insensitive.
44+
* Postfix `N` is meant natual.
45+
* @private
46+
*/
47+
const isValidOrders = {
48+
asc (a, b) {
49+
return a <= b
50+
},
51+
ascI (a, b) {
52+
return a.toLowerCase() <= b.toLowerCase()
53+
},
54+
ascN (a, b) {
55+
return naturalCompare(a, b) <= 0
56+
},
57+
ascIN (a, b) {
58+
return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0
59+
},
60+
desc (a, b) {
61+
return isValidOrders.asc(b, a)
62+
},
63+
descI (a, b) {
64+
return isValidOrders.ascI(b, a)
65+
},
66+
descN (a, b) {
67+
return isValidOrders.ascN(b, a)
68+
},
69+
descIN (a, b) {
70+
return isValidOrders.ascIN(b, a)
71+
}
72+
}
73+
74+
// ------------------------------------------------------------------------------
75+
// Rule Definition
76+
// ------------------------------------------------------------------------------
77+
78+
module.exports = {
79+
meta: {
80+
type: 'suggestion',
81+
docs: {
82+
description: 'enforce sort-keys in a manner that is compatible with order-in-components',
83+
category: null,
84+
recommended: false,
85+
url: 'https://eslint.vuejs.org/rules/sort-keys.html'
86+
},
87+
fixable: null,
88+
schema: [
89+
{
90+
enum: ['asc', 'desc']
91+
},
92+
{
93+
type: 'object',
94+
properties: {
95+
caseSensitive: {
96+
type: 'boolean',
97+
default: true
98+
},
99+
natural: {
100+
type: 'boolean',
101+
default: false
102+
},
103+
minKeys: {
104+
type: 'integer',
105+
minimum: 2,
106+
default: 2
107+
},
108+
runOutsideVue: {
109+
type: 'boolean',
110+
default: true
111+
}
112+
},
113+
additionalProperties: false
114+
}
115+
]
116+
},
117+
118+
create (context) {
119+
// Parse options.
120+
const order = context.options[0] || 'asc'
121+
const options = context.options[1]
122+
const insensitive = options && options.caseSensitive === false
123+
const natual = options && options.natural
124+
const minKeys = options && options.minKeys
125+
const isValidOrder = isValidOrders[
126+
order + (insensitive ? 'I' : '') + (natual ? 'N' : '')
127+
]
128+
129+
// The stack to save the previous property's name for each object literals.
130+
let stack = null
131+
132+
let errors = []
133+
134+
const reportErrors = (isVue) => {
135+
if (isVue) {
136+
errors = errors.filter((error) => error.hasUpper)
137+
}
138+
errors.forEach((error) => context.report(error))
139+
errors = []
140+
}
141+
142+
const sortTests = {
143+
ObjectExpression (node) {
144+
if (!stack) {
145+
reportErrors(false)
146+
}
147+
stack = {
148+
upper: stack,
149+
prevName: null,
150+
numKeys: node.properties.length
151+
}
152+
},
153+
'ObjectExpression:exit' () {
154+
// stolen by the VueComponent code
155+
stack = stack.upper
156+
},
157+
SpreadElement (node) {
158+
if (node.parent.type === 'ObjectExpression') {
159+
stack.prevName = null
160+
}
161+
},
162+
'Program:exit' () {
163+
reportErrors(false)
164+
},
165+
Property (node) {
166+
if (node.parent.type === 'ObjectPattern') {
167+
return
168+
}
169+
170+
const prevName = stack.prevName
171+
const numKeys = stack.numKeys
172+
const thisName = getPropertyName(node)
173+
174+
if (thisName !== null) {
175+
stack.prevName = thisName
176+
}
177+
178+
if (prevName === null || thisName === null || numKeys < minKeys) {
179+
return
180+
}
181+
182+
if (!isValidOrder(prevName, thisName)) {
183+
errors.push({
184+
node,
185+
hasUpper: !!stack.upper,
186+
loc: node.key.loc,
187+
message: "Expected object keys to be in {{natual}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.",
188+
data: {
189+
thisName,
190+
prevName,
191+
order,
192+
insensitive: insensitive ? 'insensitive ' : '',
193+
natual: natual ? 'natural ' : ''
194+
}
195+
})
196+
}
197+
}
198+
}
199+
200+
const execOnVue = utils.executeOnVue(context, (obj) => {
201+
reportErrors(true)
202+
})
203+
204+
const result = { ...sortTests }
205+
206+
Object.keys(execOnVue).forEach((key) => {
207+
if (Object.prototype.hasOwnProperty.call(sortTests, key)) {
208+
result[key] = (node) => {
209+
sortTests[key](node)
210+
execOnVue[key](node)
211+
}
212+
} else {
213+
result[key] = execOnVue[key]
214+
}
215+
})
216+
217+
return result
218+
}
219+
}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"eslint": "^5.0.0 || ^6.0.0"
4747
},
4848
"dependencies": {
49+
"natural-compare": "^1.4.0",
4950
"vue-eslint-parser": "^6.0.5"
5051
},
5152
"devDependencies": {

0 commit comments

Comments
 (0)