Skip to content

Commit 623ee67

Browse files
authored
fix: more improve auto-fix of sort rules (#392)
1 parent 542ed5e commit 623ee67

File tree

3 files changed

+77
-36
lines changed

3 files changed

+77
-36
lines changed

.changeset/wild-dodos-look.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-jsonc": patch
3+
---
4+
5+
fix: more improve auto-fix of sort rules

lib/utils/fix-sort-elements.ts

+54-36
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "@eslint-community/eslint-utils";
1010
import type { Rule, AST as ESLintAST, SourceCode } from "eslint";
1111
import type { AST } from "jsonc-eslint-parser";
12+
import type * as ESTree from "estree";
1213

1314
/**
1415
* Check if the token is a comma.
@@ -55,14 +56,23 @@ export function* fixForSorting(
5556
): IterableIterator<Rule.Fix> {
5657
const targetInfo = calcTargetInfo(sourceCode, target);
5758

58-
const toBeforeToken = to.node
59-
? sourceCode.getTokenBefore(getFirstTokenOfNode(sourceCode, to.node))!
60-
: to.before;
61-
let insertRange = toBeforeToken.range;
62-
const toBeforeNextToken = sourceCode.getTokenAfter(toBeforeToken, {
59+
const toPrevInfo = getPrevElementInfo(sourceCode, to);
60+
61+
if (
62+
toPrevInfo.comma &&
63+
toPrevInfo.last.range![1] <= toPrevInfo.comma.range[0]
64+
) {
65+
yield fixer.removeRange(toPrevInfo.comma.range);
66+
}
67+
68+
let insertRange = [
69+
toPrevInfo.last.range![1],
70+
toPrevInfo.last.range![1],
71+
] as ESLintAST.Range;
72+
const toBeforeNextToken = sourceCode.getTokenAfter(toPrevInfo.last, {
6373
includeComments: true,
6474
})!;
65-
if (toBeforeNextToken.loc!.start.line - toBeforeToken.loc.end.line > 1) {
75+
if (toBeforeNextToken.loc!.start.line - toPrevInfo.last.loc!.end.line > 1) {
6676
// If there are blank lines, the element is inserted after the blank lines.
6777
const offset = sourceCode.getIndexFromLoc({
6878
line: toBeforeNextToken.loc!.start.line - 1,
@@ -94,35 +104,41 @@ function calcTargetInfo(
94104
const nodeLastToken = getLastTokenOfNode(sourceCode, node);
95105

96106
const endInfo = getElementEndInfo(sourceCode, node);
97-
const prevInfo = getPrevElementInfo(sourceCode, node);
107+
const prevInfo = getPrevElementInfo(sourceCode, { node });
98108

99109
let insertCode: string;
100110

101111
const removeRanges: ESLintAST.Range[] = [];
102-
if (prevInfo.comma && prevInfo.end <= prevInfo.comma.range[0]) {
112+
if (prevInfo.comma && prevInfo.last.range![1] <= prevInfo.comma.range[0]) {
103113
insertCode = `${sourceCode.text.slice(
104-
prevInfo.end,
114+
prevInfo.last.range![1],
105115
prevInfo.comma.range[0],
106116
)}${sourceCode.text.slice(prevInfo.comma.range[1], nodeLastToken.range[1])}`;
107117
removeRanges.push(
108-
[prevInfo.end, prevInfo.comma.range[0]],
118+
[prevInfo.last.range![1], prevInfo.comma.range[0]],
109119
[prevInfo.comma.range[1], nodeLastToken.range[1]],
110120
);
111121
} else {
112-
insertCode = sourceCode.text.slice(prevInfo.end, nodeLastToken.range[1]);
113-
removeRanges.push([prevInfo.end, nodeLastToken.range[1]]);
122+
insertCode = sourceCode.text.slice(
123+
prevInfo.last.range![1],
124+
nodeLastToken.range[1],
125+
);
126+
removeRanges.push([prevInfo.last.range![1], nodeLastToken.range[1]]);
114127
}
115128

116129
const hasTrailingComma =
117-
endInfo.comma && endInfo.comma.range[1] <= endInfo.end;
130+
endInfo.comma && endInfo.comma.range[1] <= endInfo.last.range![1];
118131
if (!hasTrailingComma) {
119132
insertCode += ",";
120133
if (prevInfo.comma) {
121134
removeRanges.push(prevInfo.comma.range);
122135
}
123136
}
124-
insertCode += sourceCode.text.slice(nodeLastToken.range[1], endInfo.end);
125-
removeRanges.push([nodeLastToken.range[1], endInfo.end]);
137+
insertCode += sourceCode.text.slice(
138+
nodeLastToken.range[1],
139+
endInfo.last.range![1],
140+
);
141+
removeRanges.push([nodeLastToken.range[1], endInfo.last.range![1]]);
126142

127143
return {
128144
insertCode,
@@ -214,8 +230,8 @@ function getElementEndInfo(
214230
comma: ESLintAST.Token | null;
215231
// Next element token
216232
nextElement: ESLintAST.Token | null;
217-
// The end of the range of the target element
218-
end: number;
233+
// The last token of the target element
234+
last: ESLintAST.Token | ESTree.Comment;
219235
} {
220236
const lastToken = getLastTokenOfNode(sourceCode, node);
221237
const afterToken = sourceCode.getTokenAfter(lastToken)!;
@@ -224,7 +240,7 @@ function getElementEndInfo(
224240
return {
225241
comma: null,
226242
nextElement: null,
227-
end: calcEndWithTrailingComments(),
243+
last: getLastTokenWithTrailingComments(),
228244
};
229245
}
230246
const comma = afterToken;
@@ -235,7 +251,7 @@ function getElementEndInfo(
235251
return {
236252
comma,
237253
nextElement: null,
238-
end: comma.range[1],
254+
last: comma,
239255
};
240256
}
241257
if (isClosingBrace(nextElement) || isClosingBracket(nextElement)) {
@@ -244,7 +260,7 @@ function getElementEndInfo(
244260
return {
245261
comma,
246262
nextElement: null,
247-
end: calcEndWithTrailingComments(),
263+
last: getLastTokenWithTrailingComments(),
248264
};
249265
}
250266

@@ -253,7 +269,7 @@ function getElementEndInfo(
253269
return {
254270
comma,
255271
nextElement,
256-
end: comma.range[1],
272+
last: comma,
257273
};
258274
}
259275
// There are line breaks between the target element and the next element.
@@ -266,34 +282,35 @@ function getElementEndInfo(
266282
return {
267283
comma,
268284
nextElement,
269-
end: comma.range[1],
285+
last: comma,
270286
};
271287
}
272288

273289
return {
274290
comma,
275291
nextElement,
276-
end: calcEndWithTrailingComments(),
292+
last: getLastTokenWithTrailingComments(),
277293
};
278294

279295
/**
280-
* Calculate the end of the target element with trailing comments.
296+
* Get the last token of the target element with trailing comments.
281297
*/
282-
function calcEndWithTrailingComments() {
283-
let end = lastToken.range[1];
298+
function getLastTokenWithTrailingComments() {
299+
if (lastToken == null) return afterToken;
300+
let last: ESLintAST.Token | ESTree.Comment = lastToken;
284301
let after = sourceCode.getTokenAfter(lastToken, {
285302
includeComments: true,
286303
})!;
287304
while (
288305
(isCommentToken(after) || isComma(after)) &&
289306
node.loc.end.line === after.loc!.end.line
290307
) {
291-
end = after.range![1];
308+
last = after;
292309
after = sourceCode.getTokenAfter(after, {
293310
includeComments: true,
294311
})!;
295312
}
296-
return end;
313+
return last;
297314
}
298315
}
299316

@@ -302,23 +319,24 @@ function getElementEndInfo(
302319
*/
303320
function getPrevElementInfo(
304321
sourceCode: SourceCode,
305-
node: AST.JSONNode,
322+
target: Target,
306323
): {
307324
// Leading comma
308325
comma: ESLintAST.Token | null;
309326
// Previous element token
310327
prevElement: ESLintAST.Token | null;
311-
// The end of the range of the target element
312-
end: number;
328+
// The last token of the target element
329+
last: ESLintAST.Token | ESTree.Comment;
313330
} {
314-
const firstToken = getFirstTokenOfNode(sourceCode, node);
315-
const beforeToken = sourceCode.getTokenBefore(firstToken)!;
331+
const beforeToken = target.node
332+
? sourceCode.getTokenBefore(getFirstTokenOfNode(sourceCode, target.node))!
333+
: target.before;
316334
if (isNotCommaToken(beforeToken)) {
317335
// If there is no comma, the element is the first element.
318336
return {
319337
comma: null,
320338
prevElement: null,
321-
end: beforeToken.range[1],
339+
last: beforeToken,
322340
};
323341
}
324342
const comma = beforeToken;
@@ -330,7 +348,7 @@ function getPrevElementInfo(
330348
return {
331349
comma,
332350
prevElement: null,
333-
end: comma.range[1],
351+
last: comma,
334352
};
335353
}
336354

@@ -339,6 +357,6 @@ function getPrevElementInfo(
339357
return {
340358
comma: endInfo.comma,
341359
prevElement,
342-
end: endInfo.end,
360+
last: endInfo.last,
343361
};
344362
}

tests/lib/rules/sort-keys.ts

+18
Original file line numberDiff line numberDiff line change
@@ -1038,5 +1038,23 @@ tester.run("sort-keys", rule as any, {
10381038
"Expected object keys to be in ascending order. 'a' should be before 'b'.",
10391039
],
10401040
},
1041+
{
1042+
code: `
1043+
{
1044+
"a": "A", /* c1 */
1045+
"c": "B", /* c2 */
1046+
"b": "C" /* c3 */
1047+
}`,
1048+
output: `
1049+
{
1050+
"a": "A", /* c1 */
1051+
"b": "C", /* c3 */
1052+
"c": "B" /* c2 */
1053+
}`,
1054+
options: ["asc"],
1055+
errors: [
1056+
"Expected object keys to be in ascending order. 'b' should be before 'c'.",
1057+
],
1058+
},
10411059
],
10421060
});

0 commit comments

Comments
 (0)