Skip to content

Commit

Permalink
sort keys
Browse files Browse the repository at this point in the history
  • Loading branch information
Loren committed Dec 5, 2019
1 parent e8f130c commit ea1a563
Show file tree
Hide file tree
Showing 6 changed files with 1,326 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -158,6 +158,7 @@ For example:
| [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: |
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | |
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: |
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys within components after the top level details | |
| [vue/space-infix-ops](./space-infix-ops.md) | require spacing around infix operators | :wrench: |
| [vue/space-unary-ops](./space-unary-ops.md) | enforce consistent spacing before or after unary operators | :wrench: |
| [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: |
Expand Down
42 changes: 42 additions & 0 deletions docs/rules/sort-keys.md
@@ -0,0 +1,42 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/sort-keys
description: enforce sort-keys in a manner that is compatible with order-in-components
---
# vue/sort-keys
> enforce sort-keys within components after the top level details
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`.

## Options

```json
{
"sort-keys": ["error", "asc", {"caseSensitive": true, "natural": false, "minKeys": 2}]
}
```

The 1st option is `"asc"` or `"desc"`.

* `"asc"` (default) - enforce properties to be in ascending order.
* `"desc"` - enforce properties to be in descending order.

The 2nd option is an object which has 3 properties.

* `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`.
* `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.
* `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.

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.

## :books: Further reading

- [sorts-keys]

[sorts-keys]: https://eslint.org/docs/rules/sorts-keys

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/sort-keys.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/sort-keys.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -68,6 +68,7 @@ module.exports = {
'return-in-computed-property': require('./rules/return-in-computed-property'),
'script-indent': require('./rules/script-indent'),
'singleline-html-element-content-newline': require('./rules/singleline-html-element-content-newline'),
'sort-keys': require('./rules/sort-keys'),
'space-infix-ops': require('./rules/space-infix-ops'),
'space-unary-ops': require('./rules/space-unary-ops'),
'this-in-template': require('./rules/this-in-template'),
Expand Down
219 changes: 219 additions & 0 deletions lib/rules/sort-keys.js
@@ -0,0 +1,219 @@
/**
* @fileoverview enforce sort-keys in a manner that is compatible with order-in-components
* @author Loren Klingman
* Original ESLint sort-keys by Toru Nagashima
*/
'use strict'

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

const naturalCompare = require('natural-compare')
const utils = require('../utils')

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

/**
* Gets the property name of the given `Property` node.
*
* - If the property's key is an `Identifier` node, this returns the key's name
* whether it's a computed property or not.
* - If the property has a static name, this returns the static name.
* - Otherwise, this returns null.
* @param {ASTNode} node The `Property` node to get.
* @returns {string|null} The property name or null.
* @private
*/
function getPropertyName (node) {
const staticName = utils.getStaticPropertyName(node)

if (staticName !== null) {
return staticName
}

return node.key.name || null
}

/**
* Functions which check that the given 2 names are in specific order.
*
* Postfix `I` is meant insensitive.
* Postfix `N` is meant natual.
* @private
*/
const isValidOrders = {
asc (a, b) {
return a <= b
},
ascI (a, b) {
return a.toLowerCase() <= b.toLowerCase()
},
ascN (a, b) {
return naturalCompare(a, b) <= 0
},
ascIN (a, b) {
return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0
},
desc (a, b) {
return isValidOrders.asc(b, a)
},
descI (a, b) {
return isValidOrders.ascI(b, a)
},
descN (a, b) {
return isValidOrders.ascN(b, a)
},
descIN (a, b) {
return isValidOrders.ascIN(b, a)
}
}

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

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce sort-keys in a manner that is compatible with order-in-components',
category: null,
recommended: false,
url: 'https://eslint.vuejs.org/rules/sort-keys.html'
},
fixable: null,
schema: [
{
enum: ['asc', 'desc']
},
{
type: 'object',
properties: {
caseSensitive: {
type: 'boolean',
default: true
},
natural: {
type: 'boolean',
default: false
},
minKeys: {
type: 'integer',
minimum: 2,
default: 2
},
runOutsideVue: {
type: 'boolean',
default: true
}
},
additionalProperties: false
}
]
},

create (context) {
// Parse options.
const order = context.options[0] || 'asc'
const options = context.options[1]
const insensitive = options && options.caseSensitive === false
const natual = options && options.natural
const minKeys = options && options.minKeys
const isValidOrder = isValidOrders[
order + (insensitive ? 'I' : '') + (natual ? 'N' : '')
]

// The stack to save the previous property's name for each object literals.
let stack = null

let errors = []

const reportErrors = (isVue) => {
if (isVue) {
errors = errors.filter((error) => error.hasUpper)
}
errors.forEach((error) => context.report(error))
errors = []
}

const sortTests = {
ObjectExpression (node) {
if (!stack) {
reportErrors(false)
}
stack = {
upper: stack,
prevName: null,
numKeys: node.properties.length
}
},
'ObjectExpression:exit' () {
// stolen by the VueComponent code
stack = stack.upper
},
SpreadElement (node) {
if (node.parent.type === 'ObjectExpression') {
stack.prevName = null
}
},
'Program:exit' () {
reportErrors(false)
},
Property (node) {
if (node.parent.type === 'ObjectPattern') {
return
}

const prevName = stack.prevName
const numKeys = stack.numKeys
const thisName = getPropertyName(node)

if (thisName !== null) {
stack.prevName = thisName
}

if (prevName === null || thisName === null || numKeys < minKeys) {
return
}

if (!isValidOrder(prevName, thisName)) {
errors.push({
node,
hasUpper: !!stack.upper,
loc: node.key.loc,
message: "Expected object keys to be in {{natual}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.",
data: {
thisName,
prevName,
order,
insensitive: insensitive ? 'insensitive ' : '',
natual: natual ? 'natural ' : ''
}
})
}
}
}

const execOnVue = utils.executeOnVue(context, (obj) => {
reportErrors(true)
})

const result = { ...sortTests }

Object.keys(execOnVue).forEach((key) => {
if (Object.prototype.hasOwnProperty.call(sortTests, key)) {
result[key] = (node) => {
sortTests[key](node)
execOnVue[key](node)
}
} else {
result[key] = execOnVue[key]
}
})

return result
}
}
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -46,6 +46,7 @@
"eslint": "^5.0.0 || ^6.0.0"
},
"dependencies": {
"natural-compare": "^1.4.0",
"vue-eslint-parser": "^6.0.5"
},
"devDependencies": {
Expand Down

0 comments on commit ea1a563

Please sign in to comment.