From 49b44867daa2faa56ab756ce20bc3191d816de4c Mon Sep 17 00:00:00 2001 From: Loren Date: Thu, 5 Dec 2019 14:21:51 -0500 Subject: [PATCH 1/6] sort keys --- docs/rules/README.md | 1 + docs/rules/sort-keys.md | 42 ++ lib/index.js | 1 + lib/rules/sort-keys.js | 219 +++++++ package.json | 1 + tests/lib/rules/sort-keys.js | 1062 ++++++++++++++++++++++++++++++++++ 6 files changed, 1326 insertions(+) create mode 100644 docs/rules/sort-keys.md create mode 100644 lib/rules/sort-keys.js create mode 100644 tests/lib/rules/sort-keys.js diff --git a/docs/rules/README.md b/docs/rules/README.md index e1c88ff56..ef06b19e1 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -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 ` +``` + + + + + +```vue + +``` + + + ## :books: Further reading - [sorts-keys] diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js index 4aa172372..a495a9243 100644 --- a/lib/rules/sort-keys.js +++ b/lib/rules/sort-keys.js @@ -96,15 +96,21 @@ module.exports = { type: 'boolean', default: true }, - natural: { - type: 'boolean', - default: false + ignoreChildrenOf: { + type: 'array' + }, + ignoreGrandchildrenOf: { + type: 'array' }, minKeys: { type: 'integer', minimum: 2, default: 2 }, + natural: { + type: 'boolean', + default: false + }, runOutsideVue: { type: 'boolean', default: true @@ -117,11 +123,14 @@ module.exports = { create (context) { // Parse options. - const order = context.options[0] || 'asc' const options = context.options[1] + const order = context.options[0] || 'asc' + + const ignoreGrandchildrenOf = (options && options.ignoreGrandchildrenOf) || ['computed', 'directives', 'inject', 'props', 'watch'] + const ignoreChildrenOf = (options && options.ignoreChildrenOf) || ['model'] const insensitive = options && options.caseSensitive === false - const natual = options && options.natural const minKeys = options && options.minKeys + const natual = options && options.natural const isValidOrder = isValidOrders[ order + (insensitive ? 'I' : '') + (natual ? 'N' : '') ] @@ -133,7 +142,15 @@ module.exports = { const reportErrors = (isVue) => { if (isVue) { - errors = errors.filter((error) => error.hasUpper) + errors = errors.filter((error) => { + let filter = error.hasUpper + if (!error.grandparentName) { + filter = filter && !ignoreChildrenOf.includes(error.parentName) + } else if (!error.greatGrandparentName) { + filter = filter && !ignoreGrandchildrenOf.includes(error.grandparentName) + } + return filter + }) } errors.forEach((error) => context.report(error)) errors = [] @@ -184,6 +201,9 @@ module.exports = { hasUpper: !!stack.upper, loc: node.key.loc, message: "Expected object keys to be in {{natual}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.", + parentName: stack.upper && stack.upper.prevName, + grandparentName: stack.upper && stack.upper.upper && stack.upper.upper.prevName, + greatGrandparentName: stack.upper && stack.upper.upper && stack.upper.upper.upper && stack.upper.upper.upper.prevName, data: { thisName, prevName, diff --git a/tests/lib/rules/sort-keys.js b/tests/lib/rules/sort-keys.js index 153ebe952..1bdec2044 100644 --- a/tests/lib/rules/sort-keys.js +++ b/tests/lib/rules/sort-keys.js @@ -35,6 +35,29 @@ ruleTester.run('sort-keys', rule, { `, parserOptions }, + { + filename: 'propsOrder.vue', + code: ` + export default { + name: 'app', + model: { + prop: 'checked', + event: 'change' + }, + props: { + propA: { + type: String, + default: 'abc', + }, + propB: { + type: String, + default: 'abc', + }, + }, + } + `, + parserOptions + }, { filename: 'test.vue', code: ` @@ -929,6 +952,39 @@ ruleTester.run('sort-keys', rule, { line: 12 }] }, + { + filename: 'example.vue', + code: ` + export default { + methods: { + toggleMenu() { + return { + // These should have errors since they are not part of the vue component + model: { + prop: 'checked', + event: 'change' + }, + props: { + propA: { + z: 1, + a: 2 + }, + }, + }; + } + }, + name: 'burger', + }; + `, + parserOptions, + errors: [{ + message: 'Expected object keys to be in ascending order. \'event\' should be before \'prop\'.', + line: 9 + }, { + message: 'Expected object keys to be in ascending order. \'a\' should be before \'z\'.', + line: 14 + }] + }, { filename: 'example.vue', code: ` @@ -1057,6 +1113,26 @@ ruleTester.run('sort-keys', rule, { message: 'Expected object keys to be in ascending order. \'msg\' should be before \'z\'.', line: 9 }] + }, + { + filename: 'propsOrder.vue', + code: ` + export default { + name: 'app', + props: { + propA: { + type: String, + default: 'abc', + }, + }, + } + `, + options: ['asc', { ignoreGrandchildrenOf: [] }], + parserOptions, + errors: [{ + message: 'Expected object keys to be in ascending order. \'default\' should be before \'type\'.', + line: 7 + }] } ] }) From bd071ea56304aeda6f3281b6cf3d0b068b662d40 Mon Sep 17 00:00:00 2001 From: Loren Date: Thu, 9 Jan 2020 09:04:45 -0500 Subject: [PATCH 4/6] more finegrained parent analysis --- docs/rules/sort-keys.md | 12 ++++++-- lib/rules/sort-keys.js | 54 ++++++++++++++++++++++++++---------- tests/lib/rules/sort-keys.js | 52 ++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 17 deletions(-) diff --git a/docs/rules/sort-keys.md b/docs/rules/sort-keys.md index 378255cc1..93c0cbba4 100644 --- a/docs/rules/sort-keys.md +++ b/docs/rules/sort-keys.md @@ -13,7 +13,13 @@ This rule is almost the same rule as core [sorts-keys] rule but it will not erro ```json { - "sort-keys": ["error", "asc", {"caseSensitive": true, "natural": false, "minKeys": 2}] + "sort-keys": ["error", "asc", { + "caseSensitive": true, + "ignoreChildrenOf": ["model"], + "ignoreGrandchildrenOf": ["computed", "directives", "inject", "props", "watch"], + "minKeys": 2, + "natural": false + }] } ``` @@ -25,8 +31,8 @@ The 1st option is `"asc"` or `"desc"`. The 2nd option is an object which has 5 properties. * `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`. -* `ignoreChildrenOf` - an array of properties to ignore the children of. Default is `['model']` -* `ignoreGrandchildrenOf` - an array of properties to ignore the grandchildren sort order. Default is `['computed', 'directives', 'inject', 'props', 'watch']` +* `ignoreChildrenOf` - an array of properties to ignore the children of. Default is `["model"]` +* `ignoreGrandchildrenOf` - an array of properties to ignore the grandchildren sort order. Default is `["computed", "directives", "inject", "props", "watch"]` * `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. diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js index a495a9243..8f900ef0e 100644 --- a/lib/rules/sort-keys.js +++ b/lib/rules/sort-keys.js @@ -139,20 +139,33 @@ module.exports = { let stack = null let errors = [] + const names = {} const reportErrors = (isVue) => { if (isVue) { errors = errors.filter((error) => { - let filter = error.hasUpper - if (!error.grandparentName) { - filter = filter && !ignoreChildrenOf.includes(error.parentName) - } else if (!error.greatGrandparentName) { - filter = filter && !ignoreGrandchildrenOf.includes(error.grandparentName) + let parentIsRoot = !error.hasUpper + let grandParentIsRoot = !error.grandparent + let greatGrandparentIsRoot = !error.greatGrandparent + + const stackPrevChar = stack && stack.prevChar + if (stackPrevChar) { + parentIsRoot = stackPrevChar === error.parent + grandParentIsRoot = stackPrevChar === error.grandparent + greatGrandparentIsRoot = stackPrevChar === error.greatGrandparent + } + + if (parentIsRoot) { + return false + } else if (grandParentIsRoot) { + return !error.parentIsProperty || !ignoreChildrenOf.includes(names[error.parent]) + } else if (greatGrandparentIsRoot) { + return !error.parentIsProperty || !ignoreGrandchildrenOf.includes(names[error.grandparent]) } - return filter + return true }) } - errors.forEach((error) => context.report(error)) + errors.forEach((error) => error.errors.forEach((e) => context.report(e))) errors = [] } @@ -163,16 +176,28 @@ module.exports = { } stack = { upper: stack, + prevChar: null, prevName: null, - numKeys: node.properties.length + numKeys: node.properties.length, + parentIsProperty: node.parent.type === 'Property', + errors: [] } }, - 'ObjectExpression:exit' () { + 'ObjectExpression:exit' (node) { + errors.push({ + errors: stack.errors, + hasUpper: !!stack.upper, + parentIsProperty: node.parent.type === 'Property', + parent: stack.upper && stack.upper.prevChar, + grandparent: stack.upper && stack.upper.upper && stack.upper.upper.prevChar, + greatGrandparent: stack.upper && stack.upper.upper && stack.upper.upper.upper && stack.upper.upper.upper.prevChar + }) stack = stack.upper }, SpreadElement (node) { if (node.parent.type === 'ObjectExpression') { stack.prevName = null + stack.prevChar = null } }, 'Program:exit' () { @@ -189,6 +214,11 @@ module.exports = { if (thisName !== null) { stack.prevName = thisName + stack.prevChar = node.range[0] + if (Object.prototype.hasOwnProperty.call(names, node.range[0])) { + throw new Error('Name clash') + } + names[node.range[0]] = thisName } if (prevName === null || thisName === null || numKeys < minKeys) { @@ -196,14 +226,10 @@ module.exports = { } if (!isValidOrder(prevName, thisName)) { - errors.push({ + stack.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}}'.", - parentName: stack.upper && stack.upper.prevName, - grandparentName: stack.upper && stack.upper.upper && stack.upper.upper.prevName, - greatGrandparentName: stack.upper && stack.upper.upper && stack.upper.upper.upper && stack.upper.upper.upper.prevName, data: { thisName, prevName, diff --git a/tests/lib/rules/sort-keys.js b/tests/lib/rules/sort-keys.js index 1bdec2044..33519b91d 100644 --- a/tests/lib/rules/sort-keys.js +++ b/tests/lib/rules/sort-keys.js @@ -17,6 +17,20 @@ const parserOptions = { ruleTester.run('sort-keys', rule, { valid: [ + { + filename: 'test.vue', + code: ` + const obj = { + foo() { + Vue.component('my-component', { + name: 'app', + data() {} + }) + } + } + `, + parserOptions + }, { filename: 'test.vue', code: ` @@ -1133,6 +1147,44 @@ ruleTester.run('sort-keys', rule, { message: 'Expected object keys to be in ascending order. \'default\' should be before \'type\'.', line: 7 }] + }, + { + filename: 'propsOrder.vue', + code: ` + const obj = { + z: 1, + foo() { + Vue.component('my-component', { + name: 'app', + }) + } + } + `, + parserOptions, + errors: [{ + message: 'Expected object keys to be in ascending order. \'foo\' should be before \'z\'.', + line: 4 + }] + }, + { + filename: 'propsOrder.vue', + code: ` + export default { + computed: { + foo () { + return { + b, + a + } + } + } + } + `, + parserOptions, + errors: [{ + message: 'Expected object keys to be in ascending order. \'a\' should be before \'b\'.', + line: 7 + }] } ] }) From 50404e98690a9d63767755653d899ae4edf0e53a Mon Sep 17 00:00:00 2001 From: Loren Date: Thu, 9 Jan 2020 09:06:34 -0500 Subject: [PATCH 5/6] expand the test --- tests/lib/rules/sort-keys.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/lib/rules/sort-keys.js b/tests/lib/rules/sort-keys.js index 33519b91d..c8094b5b3 100644 --- a/tests/lib/rules/sort-keys.js +++ b/tests/lib/rules/sort-keys.js @@ -1156,14 +1156,19 @@ ruleTester.run('sort-keys', rule, { foo() { Vue.component('my-component', { name: 'app', + data: {} }) - } + }, + a: 2 } `, parserOptions, errors: [{ message: 'Expected object keys to be in ascending order. \'foo\' should be before \'z\'.', line: 4 + }, { + message: 'Expected object keys to be in ascending order. \'a\' should be before \'foo\'.', + line: 10 }] }, { From 3630bf46105d0977e55475d16d56ed190082c4c0 Mon Sep 17 00:00:00 2001 From: Loren Date: Tue, 28 Jan 2020 07:44:53 -0500 Subject: [PATCH 6/6] Update docs/rules/sort-keys.md Co-Authored-By: Yosuke Ota --- docs/rules/sort-keys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/sort-keys.md b/docs/rules/sort-keys.md index 93c0cbba4..38939fd55 100644 --- a/docs/rules/sort-keys.md +++ b/docs/rules/sort-keys.md @@ -101,7 +101,7 @@ export default { - [sorts-keys] -[sorts-keys]: https://eslint.org/docs/rules/sorts-keys +[sort-keys]: https://eslint.org/docs/rules/sort-keys ## :mag: Implementation