Skip to content
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

feat: add new allowLineSeparatedGroups option to the sort-keys rule #16138

Merged
merged 8 commits into from Jul 30, 2022
122 changes: 122 additions & 0 deletions docs/src/rules/sort-keys.md
Expand Up @@ -92,6 +92,7 @@ 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.
* `allowLineSeparatedGroups` - if `true`, the rule allows to group object keys through line breaks. In other words, a blank line after a key will reset the sorting of keys. Default is `false`.
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

Example for a list:

Expand Down Expand Up @@ -263,6 +264,127 @@ let obj = {

:::

### allowLineSeparatedGroups

Examples of **incorrect** code for the `{allowLineSeparatedGroups: true}` option:

::: incorrect

```js
/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/
/*eslint-env es6*/

let obj1 = {
b: 1,
c () {

},
a: 3
}

let obj2 = {
b: 1,
c: 2,

z () {

},
y: 3
}

let obj3 = {
b: 1,
c: 2,
// comment
z () {

},
y: 3,
}
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

let obj4 = {
b: 1
// comment before comma
, a: 2
};
```

:::

Examples of **correct** code for the `{allowLineSeparatedGroups: true}` option:

::: correct

```js
/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/
/*eslint-env es6*/

let obj = {
e: 1,
f: 2,
g: 3,

a: 4,
b: 5,
c: 6
}

let obj = {
b: 1,

// comment
a: 4,
c: 5,
}

let obj = {
c: 1,
d: 2,

b () {

},
e: 3,
}

let obj = {
c: 1,
d: 2,
// comment

// comment
b() {

},
e: 4
}

let obj = {
b,

[foo + bar]: 1,
a
}

let obj = {
b: 1
// comment before comma

,
a: 2
};

var obj = {
b: 1,

a: 2,
...z,
c: 3
}
```

:::

## When Not To Use It

If you don't want to notify about properties' order, then it's safe to disable this rule.
Expand Down
35 changes: 35 additions & 0 deletions lib/rules/sort-keys.js
Expand Up @@ -105,6 +105,10 @@ module.exports = {
type: "integer",
minimum: 2,
default: 2
},
allowLineSeparatedGroups: {
type: "boolean",
default: false
}
},
additionalProperties: false
Expand All @@ -124,17 +128,20 @@ module.exports = {
const insensitive = options && options.caseSensitive === false;
const natural = options && options.natural;
const minKeys = options && options.minKeys;
const allowLineSeparatedGroups = options && options.allowLineSeparatedGroups || false;
const isValidOrder = isValidOrders[
order + (insensitive ? "I" : "") + (natural ? "N" : "")
];

// The stack to save the previous property's name for each object literals.
let stack = null;
const sourceCode = context.getSourceCode();

return {
ObjectExpression(node) {
stack = {
upper: stack,
prevNode: null,
prevName: null,
numKeys: node.properties.length
};
Expand All @@ -159,8 +166,36 @@ module.exports = {
const numKeys = stack.numKeys;
const thisName = getPropertyName(node);

// Get tokens between current node and previous node
const tokens = stack.prevNode && sourceCode
.getFirstTokensBetween(stack.prevNode, node, { skip: 0, includeComments: true });
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

let isBlankLineBetweenNodes = false;

if (tokens) {

// check blank line between tokens
tokens.forEach((token, index) => {
const previousToken = tokens[index - 1];

if (previousToken && (token.loc.start.line - previousToken.loc.end.line > 1)) {
isBlankLineBetweenNodes = true;
}
});

// check blank line between current node and last token
if (!isBlankLineBetweenNodes && (node.loc.start.line - tokens[tokens.length - 1].loc.start.line > 1)) {
isBlankLineBetweenNodes = true;
}
}

if (thisName !== null) {
stack.prevName = thisName;
stack.prevNode = node;
}
mdjermanovic marked this conversation as resolved.
Show resolved Hide resolved

if (allowLineSeparatedGroups && (isBlankLineBetweenNodes || thisName === null)) {
return;
}

if (prevName === null || thisName === null || numKeys < minKeys) {
Expand Down