Skip to content

Commit

Permalink
feat: add partition by comment option in sort-objects-types
Browse files Browse the repository at this point in the history
  • Loading branch information
hugop95 authored Sep 23, 2024
1 parent 03fe596 commit 69b643e
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 15 deletions.
15 changes: 14 additions & 1 deletion docs/content/rules/sort-object-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,22 @@ Controls whether sorting should be case-sensitive or not.
- `true` — Ignore case when sorting alphabetically or naturally (e.g., “A” and “a” are the same).
- `false` — Consider case when sorting (e.g., “A” comes before “a”).

### partitionByComment

<sub>default: `false`</sub>

Allows you to use comments to separate the members of types into logical groups. This can help in organizing and maintaining large enums by creating partitions within the enum based on comments.

- `true` — All comments will be treated as delimiters, creating partitions.
- `false` — Comments will not be used as delimiters.
- `string` — A glob pattern to specify which comments should act as delimiters.
- `string[]` — A list of glob patterns to specify which comments should act as delimiters.

### partitionByNewLine

<sub>default: `false`</sub>

When `true`, the rule will not sort the members of an interface if there is an empty line between them. This can be useful for keeping logically separated groups of members in their defined order.
When `true`, the rule will not sort the members of an object if there is an empty line between them. This can be useful for keeping logically separated groups of members in their defined order.

```ts
type User = {
Expand Down Expand Up @@ -215,6 +226,7 @@ Example:
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
partitionByComment: false,
partitionByNewLine: false,
groups: [],
customGroups: {},
Expand All @@ -241,6 +253,7 @@ Example:
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
partitionByComment: false,
partitionByNewLine: false,
groups: [],
customGroups: {},
Expand Down
60 changes: 46 additions & 14 deletions rules/sort-object-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import type { TSESTree } from '@typescript-eslint/types'
import type { SortingNode } from '../typings'

import { validateGroupsConfiguration } from '../utils/validate-groups-configuration'
import { hasPartitionComment } from '../utils/is-partition-comment'
import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
import { getCommentsBefore } from '../utils/get-comments-before'
import { createEslintRule } from '../utils/create-eslint-rule'
import { getLinesBetween } from '../utils/get-lines-between'
import { getGroupNumber } from '../utils/get-group-number'
Expand All @@ -27,13 +29,16 @@ type Options<T extends string[]> = [
groupKind: 'required-first' | 'optional-first' | 'mixed'
customGroups: { [key in T[number]]: string[] | string }
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
groups: (Group<T>[] | Group<T>)[]
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
]

type SortObjectTypesSortingNode = SortingNode<TSESTree.TypeElement>

export default createEslintRule<Options<string[]>, MESSAGE_ID>({
name: 'sort-object-types',
meta: {
Expand Down Expand Up @@ -62,6 +67,24 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
partitionByComment: {
description:
'Allows you to use comments to separate the type members into logical groups.',
anyOf: [
{
type: 'array',
items: {
type: 'string',
},
},
{
type: 'boolean',
},
{
type: 'string',
},
],
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
Expand Down Expand Up @@ -122,6 +145,7 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
partitionByComment: false,
partitionByNewLine: false,
groupKind: 'mixed',
groups: [],
Expand All @@ -134,6 +158,7 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
let settings = getSettings(context.settings)

let options = complete(context.options.at(0), settings, {
partitionByComment: false,
partitionByNewLine: false,
type: 'alphabetical',
groupKind: 'mixed',
Expand All @@ -150,16 +175,17 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
)

let sourceCode = getSourceCode(context)
let partitionComment = options.partitionByComment

let formattedMembers: SortingNode<TSESTree.TypeElement>[][] =
let formattedMembers: SortObjectTypesSortingNode[][] =
node.members.reduce(
(accumulator: SortingNode<TSESTree.TypeElement>[][], member) => {
(accumulator: SortObjectTypesSortingNode[][], member) => {
let name: string
let raw = sourceCode.text.slice(
member.range.at(0),
member.range.at(1),
)
let lastMember = accumulator.at(-1)?.at(-1)
let lastSortingNode = accumulator.at(-1)?.at(-1)

let { getGroup, defineGroup, setCustomGroups } = useGroups(
options.groups,
Expand Down Expand Up @@ -201,24 +227,27 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
let endsWithComma = raw.endsWith(';') || raw.endsWith(',')
let endSize = endsWithComma ? 1 : 0

let memberSortingNode = {
let sortingNode: SortObjectTypesSortingNode = {
size: rangeToDiff(member.range) - endSize,
group: getGroup(),
node: member,
name,
}

if (
options.partitionByNewLine &&
lastMember &&
getLinesBetween(sourceCode, lastMember, memberSortingNode)
(partitionComment &&
hasPartitionComment(
partitionComment,
getCommentsBefore(member, sourceCode),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween(sourceCode, lastSortingNode, sortingNode))
) {
accumulator.push([])
}

accumulator.at(-1)?.push({
...memberSortingNode,
group: getGroup(),
})
accumulator.at(-1)?.push(sortingNode)

return accumulator
},
Expand All @@ -228,7 +257,7 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
for (let nodes of formattedMembers) {
let groupedByKind
if (options.groupKind !== 'mixed') {
groupedByKind = nodes.reduce<SortingNode<TSESTree.TypeElement>[][]>(
groupedByKind = nodes.reduce<SortObjectTypesSortingNode[][]>(
(accumulator, currentNode) => {
let requiredIndex =
options.groupKind === 'required-first' ? 0 : 1
Expand All @@ -251,7 +280,7 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
groupedByKind = [nodes]
}

let sortedNodes: SortingNode[] = []
let sortedNodes: SortObjectTypesSortingNode[] = []

for (let nodesByKind of groupedByKind) {
sortedNodes.push(...sortNodesByGroups(nodesByKind, options))
Expand All @@ -275,7 +304,10 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
rightGroup: right.group,
},
node: right.node,
fix: fixer => makeFixes(fixer, nodes, sortedNodes, sourceCode),
fix: fixer =>
makeFixes(fixer, nodes, sortedNodes, sourceCode, {
partitionComment,
}),
})
}
})
Expand Down
147 changes: 147 additions & 0 deletions test/sort-object-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,153 @@ describe(ruleName, () => {
},
)

describe(`${ruleName}(${type}): partition comments`, () => {
ruleTester.run(
`${ruleName}(${type}): allows to use partition comments`,
rule,
{
valid: [],
invalid: [
{
code: dedent`
type Type = {
// Part: A
cc: string
d: string
// Not partition comment
bbb: string
// Part: B
aaaa: string
e: string
// Part: C
'gg': string
// Not partition comment
fff: string
}
`,
output: dedent`
type Type = {
// Part: A
// Not partition comment
bbb: string
cc: string
d: string
// Part: B
aaaa: string
e: string
// Part: C
// Not partition comment
fff: string
'gg': string
}
`,
options: [
{
...options,
partitionByComment: 'Part**',
},
],
errors: [
{
messageId: 'unexpectedObjectTypesOrder',
data: {
left: 'd',
right: 'bbb',
},
},
{
messageId: 'unexpectedObjectTypesOrder',
data: {
left: 'gg',
right: 'fff',
},
},
],
},
],
},
)

ruleTester.run(
`${ruleName}(${type}): allows to use all comments as parts`,
rule,
{
valid: [
{
code: dedent`
type Type = {
// Comment
bb: string
// Other comment
a: string
}
`,
options: [
{
...options,
partitionByComment: true,
},
],
},
],
invalid: [],
},
)

ruleTester.run(
`${ruleName}(${type}): allows to use multiple partition comments`,
rule,
{
valid: [],
invalid: [
{
code: dedent`
type Type = {
/* Partition Comment */
// Part: A
d: string
// Part: B
aaa: string
c: string
bb: string
/* Other */
e: string
}
`,
output: dedent`
type Type = {
/* Partition Comment */
// Part: A
d: string
// Part: B
aaa: string
bb: string
c: string
/* Other */
e: string
}
`,
options: [
{
...options,
partitionByComment: ['Partition Comment', 'Part: *', 'Other'],
},
],
errors: [
{
messageId: 'unexpectedObjectTypesOrder',
data: {
left: 'c',
right: 'bb',
},
},
],
},
],
},
)
})

ruleTester.run(
`${ruleName}(${type}): allows to sort required values first`,
rule,
Expand Down

0 comments on commit 69b643e

Please sign in to comment.