Skip to content

Commit 8654e44

Browse files
authoredNov 11, 2023
feat: add partition by new line option to sort-objects rule
1 parent 4b81340 commit 8654e44

File tree

5 files changed

+274
-20
lines changed

5 files changed

+274
-20
lines changed
 

‎docs/rules/sort-objects.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,10 @@ interface Options {
8484
order?: 'asc' | 'desc'
8585
'ignore-case'?: boolean
8686
groups?: (string | string[])[]
87-
'custom-groups': { [key: string]: string[] | string }
88-
'styled-components': boolean
89-
'partition-by-comment': string[] | string | boolean
87+
'custom-groups'?: { [key: string]: string[] | string }
88+
'styled-components'?: boolean
89+
'partition-by-comment'?: string[] | string | boolean
90+
'partition-by-new-line'?: boolean
9091
}
9192
```
9293

@@ -147,6 +148,12 @@ You can set comments that would separate the properties of objects into logical
147148

148149
The [minimatch](https://github.com/isaacs/minimatch) library is used for pattern matching.
149150

151+
### partition-by-new-line
152+
153+
<sub>(default: `false`)</sub>
154+
155+
When `true`, does not sort the object's keys if there is an empty string between them.
156+
150157
## ⚙️ Usage
151158

152159
::: code-group

‎rules/sort-imports.ts

+4-14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { SortingNode } from '../typings'
88

99
import { getCommentBefore } from '../utils/get-comment-before'
1010
import { createEslintRule } from '../utils/create-eslint-rule'
11+
import { getLinesBetween } from '../utils/get-lines-between'
1112
import { getGroupNumber } from '../utils/get-group-number'
1213
import { getNodeRange } from '../utils/get-node-range'
1314
import { rangeToDiff } from '../utils/range-to-diff'
@@ -393,18 +394,6 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
393394
},
394395
).length
395396

396-
let getLinesBetweenImports = (
397-
left: SortingNode,
398-
right: SortingNode,
399-
) => {
400-
let linesBetweenImports = source.lines.slice(
401-
left.node.loc.end.line,
402-
right.node.loc.start.line - 1,
403-
)
404-
405-
return linesBetweenImports.filter(line => !line.trim().length).length
406-
}
407-
408397
let fix = (
409398
fixer: TSESLint.RuleFixer,
410399
nodesToFix: SortingNode[],
@@ -452,7 +441,8 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
452441
let nextNode = formatted.at(i + 1)
453442

454443
if (nextNode) {
455-
let linesBetweenImports = getLinesBetweenImports(
444+
let linesBetweenImports = getLinesBetween(
445+
source,
456446
nodesToFix.at(i)!,
457447
nodesToFix.at(i + 1)!,
458448
)
@@ -530,7 +520,7 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
530520
let leftNum = getGroupNumber(options.groups, left)
531521
let rightNum = getGroupNumber(options.groups, right)
532522

533-
let numberOfEmptyLinesBetween = getLinesBetweenImports(left, right)
523+
let numberOfEmptyLinesBetween = getLinesBetween(source, left, right)
534524

535525
if (
536526
!(

‎rules/sort-objects.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { PartitionComment, SortingNode } from '../typings'
66
import { isPartitionComment } from '../utils/is-partition-comment'
77
import { getCommentBefore } from '../utils/get-comment-before'
88
import { createEslintRule } from '../utils/create-eslint-rule'
9+
import { getLinesBetween } from '../utils/get-lines-between'
910
import { getGroupNumber } from '../utils/get-group-number'
1011
import { toSingleLine } from '../utils/to-single-line'
1112
import { rangeToDiff } from '../utils/range-to-diff'
@@ -33,6 +34,7 @@ type Options = [
3334
Partial<{
3435
'custom-groups': { [key: string]: string[] | string }
3536
'partition-by-comment': PartitionComment
37+
'partition-by-new-line': boolean
3638
groups: (string[] | string)[]
3739
'styled-components': boolean
3840
'ignore-case': boolean
@@ -62,6 +64,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
6264
type: ['boolean', 'string', 'array'],
6365
default: false,
6466
},
67+
'partition-by-new-line': {
68+
type: 'boolean',
69+
default: false,
70+
},
6571
'styled-components': {
6672
type: 'boolean',
6773
default: true,
@@ -107,6 +113,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
107113
) => {
108114
if (node.properties.length > 1) {
109115
let options = complete(context.options.at(0), {
116+
'partition-by-new-line': false,
110117
'partition-by-comment': false,
111118
type: SortType.alphabetical,
112119
'styled-components': true,
@@ -158,6 +165,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
158165
}
159166

160167
let comment = getCommentBefore(prop, source)
168+
let lastProp = accumulator.at(-1)?.at(-1)
161169

162170
if (
163171
options['partition-by-comment'] &&
@@ -184,6 +192,20 @@ export default createEslintRule<Options, MESSAGE_ID>({
184192
name = source.text.slice(...prop.key.range)
185193
}
186194

195+
let propSortingNode = {
196+
size: rangeToDiff(prop.range),
197+
node: prop,
198+
name,
199+
}
200+
201+
if (
202+
options['partition-by-new-line'] &&
203+
lastProp &&
204+
getLinesBetween(source, lastProp, propSortingNode)
205+
) {
206+
accumulator.push([])
207+
}
208+
187209
if (prop.value.type === 'AssignmentPattern') {
188210
let addDependencies = (value: TSESTree.AssignmentPattern) => {
189211
if (value.right.type === 'Identifier') {
@@ -252,12 +274,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
252274
setCustomGroups(options['custom-groups'], name)
253275

254276
let value = {
255-
size: rangeToDiff(prop.range),
277+
...propSortingNode,
256278
group: getGroup(),
257279
dependencies,
258-
node: prop,
259280
position,
260-
name,
261281
}
262282

263283
accumulator.at(-1)!.push(value)

‎test/sort-objects.test.ts

+221
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,82 @@ describe(RULE_NAME, () => {
891891
],
892892
},
893893
)
894+
895+
ruleTester.run(
896+
`${RULE_NAME}(${type}): allows to use new line as partition`,
897+
rule,
898+
{
899+
valid: [
900+
{
901+
code: dedent`
902+
let terrorist = {
903+
name: 'Twelve',
904+
nickname: 'Sphinx #2',
905+
906+
status: Status.Deceased,
907+
908+
height: '167 cm',
909+
weight: '55 kg',
910+
}
911+
`,
912+
options: [
913+
{
914+
...options,
915+
'partition-by-new-line': true,
916+
},
917+
],
918+
},
919+
],
920+
invalid: [
921+
{
922+
code: dedent`
923+
let terrorist = {
924+
nickname: 'Sphinx #2',
925+
name: 'Twelve',
926+
927+
status: Status.Deceased,
928+
929+
weight: '55 kg',
930+
height: '167 cm',
931+
}
932+
`,
933+
output: dedent`
934+
let terrorist = {
935+
name: 'Twelve',
936+
nickname: 'Sphinx #2',
937+
938+
status: Status.Deceased,
939+
940+
height: '167 cm',
941+
weight: '55 kg',
942+
}
943+
`,
944+
options: [
945+
{
946+
...options,
947+
'partition-by-new-line': true,
948+
},
949+
],
950+
errors: [
951+
{
952+
messageId: 'unexpectedObjectsOrder',
953+
data: {
954+
left: 'nickname',
955+
right: 'name',
956+
},
957+
},
958+
{
959+
messageId: 'unexpectedObjectsOrder',
960+
data: {
961+
left: 'weight',
962+
right: 'height',
963+
},
964+
},
965+
],
966+
},
967+
],
968+
},
969+
)
894970
})
895971

896972
describe(`${RULE_NAME}: sorting by natural order`, () => {
@@ -1582,6 +1658,82 @@ describe(RULE_NAME, () => {
15821658
],
15831659
},
15841660
)
1661+
1662+
ruleTester.run(
1663+
`${RULE_NAME}(${type}): allows to use new line as partition`,
1664+
rule,
1665+
{
1666+
valid: [
1667+
{
1668+
code: dedent`
1669+
let terrorist = {
1670+
name: 'Twelve',
1671+
nickname: 'Sphinx #2',
1672+
1673+
status: Status.Deceased,
1674+
1675+
height: '167 cm',
1676+
weight: '55 kg',
1677+
}
1678+
`,
1679+
options: [
1680+
{
1681+
...options,
1682+
'partition-by-new-line': true,
1683+
},
1684+
],
1685+
},
1686+
],
1687+
invalid: [
1688+
{
1689+
code: dedent`
1690+
let terrorist = {
1691+
nickname: 'Sphinx #2',
1692+
name: 'Twelve',
1693+
1694+
status: Status.Deceased,
1695+
1696+
weight: '55 kg',
1697+
height: '167 cm',
1698+
}
1699+
`,
1700+
output: dedent`
1701+
let terrorist = {
1702+
name: 'Twelve',
1703+
nickname: 'Sphinx #2',
1704+
1705+
status: Status.Deceased,
1706+
1707+
height: '167 cm',
1708+
weight: '55 kg',
1709+
}
1710+
`,
1711+
options: [
1712+
{
1713+
...options,
1714+
'partition-by-new-line': true,
1715+
},
1716+
],
1717+
errors: [
1718+
{
1719+
messageId: 'unexpectedObjectsOrder',
1720+
data: {
1721+
left: 'nickname',
1722+
right: 'name',
1723+
},
1724+
},
1725+
{
1726+
messageId: 'unexpectedObjectsOrder',
1727+
data: {
1728+
left: 'weight',
1729+
right: 'height',
1730+
},
1731+
},
1732+
],
1733+
},
1734+
],
1735+
},
1736+
)
15851737
})
15861738

15871739
describe(`${RULE_NAME}: sorting by line length`, () => {
@@ -2321,6 +2473,75 @@ describe(RULE_NAME, () => {
23212473
],
23222474
},
23232475
)
2476+
2477+
ruleTester.run(
2478+
`${RULE_NAME}(${type}): allows to use new line as partition`,
2479+
rule,
2480+
{
2481+
valid: [
2482+
{
2483+
code: dedent`
2484+
let terrorist = {
2485+
nickname: 'Sphinx #2',
2486+
name: 'Twelve',
2487+
2488+
status: Status.Deceased,
2489+
2490+
height: '167 cm',
2491+
weight: '55 kg',
2492+
}
2493+
`,
2494+
options: [
2495+
{
2496+
...options,
2497+
'partition-by-new-line': true,
2498+
},
2499+
],
2500+
},
2501+
],
2502+
invalid: [
2503+
{
2504+
code: dedent`
2505+
let terrorist = {
2506+
nickname: 'Sphinx #2',
2507+
name: 'Twelve',
2508+
2509+
status: Status.Deceased,
2510+
2511+
weight: '55 kg',
2512+
height: '167 cm',
2513+
}
2514+
`,
2515+
output: dedent`
2516+
let terrorist = {
2517+
nickname: 'Sphinx #2',
2518+
name: 'Twelve',
2519+
2520+
status: Status.Deceased,
2521+
2522+
height: '167 cm',
2523+
weight: '55 kg',
2524+
}
2525+
`,
2526+
options: [
2527+
{
2528+
...options,
2529+
'partition-by-new-line': true,
2530+
},
2531+
],
2532+
errors: [
2533+
{
2534+
messageId: 'unexpectedObjectsOrder',
2535+
data: {
2536+
left: 'weight',
2537+
right: 'height',
2538+
},
2539+
},
2540+
],
2541+
},
2542+
],
2543+
},
2544+
)
23242545
})
23252546

23262547
describe(`${RULE_NAME}: misc`, () => {

‎utils/get-lines-between.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { TSESLint } from '@typescript-eslint/utils'
2+
3+
import type { SortingNode } from '../typings'
4+
5+
export let getLinesBetween = (
6+
source: TSESLint.SourceCode,
7+
left: SortingNode,
8+
right: SortingNode,
9+
) => {
10+
let linesBetween = source.lines.slice(
11+
left.node.loc.end.line,
12+
right.node.loc.start.line - 1,
13+
)
14+
15+
return linesBetween.filter(line => !line.trim().length).length
16+
}

0 commit comments

Comments
 (0)
Please sign in to comment.