diff --git a/docs/rules/sort-keys.md b/docs/rules/sort-keys.md index cd4b78983ac7..9b8f5e68a841 100644 --- a/docs/rules/sort-keys.md +++ b/docs/rules/sort-keys.md @@ -53,7 +53,7 @@ let obj = {a: 1, ["c" + "d"]: 3, b: 2}; let obj = {a: 1, [`${c}`]: 3, b: 2}; let obj = {a: 1, [tag`c`]: 3, b: 2}; -// This rule ignores objects that have a spread operator in them. +// This rule ignores objects that have a non-boundary spread operator in them. let obj = {b: 1, ...c, a: 2}; ``` diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js index 0668e617d3c7..800c70d979e2 100644 --- a/lib/rules/sort-keys.js +++ b/lib/rules/sort-keys.js @@ -115,11 +115,47 @@ module.exports = { // The stack to save the previous property's name for each object literals. let stack = null; + /** + * Checks if object contains non-boundary spread elements + * @param {ASTNode} node - The node to check. + * @returns {boolean} node has non-boundary spread elements + * @private + */ + function hasNonBoundarySpreadElement(node) { + + // if object contains 2 or fewer properties a spread element will always be boundary + if (node.properties.length < 3) { + return false; + } + + let previousType = node.properties[0].type; + let mustBeSpread = false; + + for (let i = 1; i < node.properties.length; i++) { + const property = node.properties[i]; + + if (mustBeSpread) { + if (property.type !== "SpreadElement") { + return true; + } + } else { + if (previousType !== "SpreadElement" && property.type === "SpreadElement") { + mustBeSpread = true; + } else { + previousType = property.type; + } + } + } + + return false; + } + return { - ObjectExpression() { + ObjectExpression(node) { stack = { upper: stack, - prevName: null + prevName: null, + hasNonBoundarySpread: hasNonBoundarySpreadElement(node) }; }, @@ -128,7 +164,7 @@ module.exports = { }, Property(node) { - if (node.parent.type === "ObjectPattern" || node.parent.properties.some(n => n.type === "SpreadElement")) { + if (node.parent.type === "ObjectPattern" || stack.hasNonBoundarySpread || node.type === "SpreadElement") { return; } diff --git a/tests/lib/rules/sort-keys.js b/tests/lib/rules/sort-keys.js index 5a3a0a0571d7..10d074116e84 100644 --- a/tests/lib/rules/sort-keys.js +++ b/tests/lib/rules/sort-keys.js @@ -33,10 +33,15 @@ ruleTester.run("sort-keys", rule, { // ignore non-simple computed properties. { code: "var obj = {a:1, b:3, [a + b]: -1, c:2}", options: [], parserOptions: { ecmaVersion: 6 } }, - // ignore spread properties. + // ignore non-boundary spread properties. { code: "var obj = {a:1, ...z, b:1}", options: [], parserOptions: { ecmaVersion: 2018 } }, { code: "var obj = {b:1, ...z, a:1}", options: [], parserOptions: { ecmaVersion: 2018 } }, + // boundary spread properties + { code: "var obj = {...z, a:1, b:1}", options: [], parserOptions: { ecmaVersion: 2018 } }, + { code: "var obj = {...z, ...c, a:1, b:1}", options: [], parserOptions: { ecmaVersion: 2018 } }, + { code: "var obj = {a:1, b:1, ...z}", options: [], parserOptions: { ecmaVersion: 2018 } }, + // ignore destructuring patterns. { code: "let {a, b} = {}", options: [], parserOptions: { ecmaVersion: 6 } }, @@ -151,6 +156,38 @@ ruleTester.run("sort-keys", rule, { errors: ["Expected object keys to be in ascending order. 'Z' should be before 'À'."] }, + // not ignore boundary spread properties + { + code: "var obj = {...z, c:1, b:1}", + options: [], + parserOptions: { ecmaVersion: 2018 }, + errors: ["Expected object keys to be in ascending order. 'b' should be before 'c'."] + }, + { + code: "var obj = {c:1, b:1, ...a}", + options: [], + parserOptions: { ecmaVersion: 2018 }, + errors: ["Expected object keys to be in ascending order. 'b' should be before 'c'."] + }, + { + code: "var obj = {...z, ...a, c:1, b:1}", + options: [], + parserOptions: { ecmaVersion: 2018 }, + errors: ["Expected object keys to be in ascending order. 'b' should be before 'c'."] + }, + { + code: "var obj = {...z, b:1, a:1, ...d, ...c}", + options: [], + parserOptions: { ecmaVersion: 2018 }, + errors: ["Expected object keys to be in ascending order. 'a' should be before 'b'."] + }, + { + code: "var obj = {...z, a:2, b:0, ...x, ...c}", + options: ["desc"], + parserOptions: { ecmaVersion: 2018 }, + errors: ["Expected object keys to be in descending order. 'b' should be before 'a'."] + }, + // not ignore simple computed properties. { code: "var obj = {a:1, b:3, [a]: -1, c:2}",