Skip to content

Commit

Permalink
feat: add partition by comment and partition by new line in sort-arra…
Browse files Browse the repository at this point in the history
…y-includes and sort-sets
  • Loading branch information
hugop95 authored Sep 22, 2024
1 parent fae756a commit e4fc538
Show file tree
Hide file tree
Showing 5 changed files with 759 additions and 64 deletions.
40 changes: 40 additions & 0 deletions docs/content/rules/sort-array-includes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,44 @@ Allows you to group array elements by their kind, determining whether spread val
- `literals-first` — Group all literal values before spread values.
- `spreads-first` — Group all spread values before literal values.

### partitionByComment

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

Allows you to use comments to separate the members of arrays 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 array if there is an empty line between them. This can be useful for keeping logically separated groups of members in their defined order.

```ts
if ([
// Group 1
'Drone',
'Keyboard',
'Mouse',
'Smartphone',

// Group 2
'Laptop',
'Monitor',
'Smartwatch',
'Tablet',

// Group 3
'Headphones',
'Router',
].includes(product.name)) {
return 'Electronics'
}
```

## Usage

Expand All @@ -181,6 +219,7 @@ Allows you to group array elements by their kind, determining whether spread val
order: 'asc',
ignoreCase: true,
groupKind: 'literals-first',
partitionByNewLine: false,
},
],
},
Expand All @@ -205,6 +244,7 @@ Allows you to group array elements by their kind, determining whether spread val
order: 'asc',
ignoreCase: true,
groupKind: 'literals-first',
partitionByNewLine: false,
},
],
},
Expand Down
36 changes: 36 additions & 0 deletions docs/content/rules/sort-sets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,42 @@ Allows you to group set elements by their kind, determining whether spread value
- `literals-first` — Group all literal values before spread values.
- `spreads-first` — Group all spread values before literal values.

### partitionByComment

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

Allows you to use comments to separate the members of enums 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 a set if there is an empty line between them. This can be useful for keeping logically separated groups of members in their defined order.

```ts
let items = new Set([
// Group 1
'Drone',
'Keyboard',
'Mouse',
'Smartphone',

// Group 2
'Laptop',
'Monitor',
'Smartwatch',
'Tablet',

// Group 3
'Headphones',
'Router',
])
```

## Usage

Expand Down
178 changes: 114 additions & 64 deletions rules/sort-array-includes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import type { TSESTree } from '@typescript-eslint/types'

import type { SortingNode } from '../typings'

import { hasPartitionComment } from '../utils/is-partition-comment'
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'
import { getSourceCode } from '../utils/get-source-code'
import { toSingleLine } from '../utils/to-single-line'
Expand All @@ -23,6 +26,8 @@ export type Options = [
Partial<{
groupKind: 'literals-first' | 'spreads-first' | 'mixed'
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
partitionByNewLine: boolean
order: 'desc' | 'asc'
ignoreCase: boolean
}>,
Expand Down Expand Up @@ -51,6 +56,29 @@ export let jsonSchema: JSONSchema4 = {
enum: ['mixed', 'literals-first', 'spreads-first'],
type: 'string',
},
partitionByComment: {
description:
'Allows you to use comments to separate the array 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.',
type: 'boolean',
},
},
additionalProperties: false,
}
Expand All @@ -75,6 +103,8 @@ export default createEslintRule<Options, MESSAGE_ID>({
order: 'asc',
ignoreCase: true,
groupKind: 'literals-first',
partitionByComment: false,
partitionByNewLine: false,
},
],
create: context => ({
Expand Down Expand Up @@ -108,76 +138,96 @@ export let sortArray = <MessageIds extends string>(
type: 'alphabetical',
ignoreCase: true,
order: 'asc',
partitionByComment: false,
partitionByNewLine: false,
} as const)

let sourceCode = getSourceCode(context)
let nodes: ({ type: string } & SortingNode)[] = elements
.reduce(
(
accumulator: ({ type: string } & SortingNode)[][],
element: TSESTree.SpreadElement | TSESTree.Expression | null,
) => {
if (element !== null) {
let group = 'unknown'
if (typeof options.groupKind === 'string') {
group = element.type === 'SpreadElement' ? 'spread' : 'literal'
}
accumulator.at(0)!.push({
name:
element.type === 'Literal'
? `${element.value}`
: sourceCode.text.slice(...element.range),
size: rangeToDiff(element.range),
type: element.type,
node: element,
group,
})
let partitionComment = options.partitionByComment

let formattedMembers: SortingNode[][] = elements.reduce(
(
accumulator: SortingNode[][],
element: TSESTree.SpreadElement | TSESTree.Expression | null,
) => {
if (element !== null) {
let group = 'unknown'
if (typeof options.groupKind === 'string') {
group = element.type === 'SpreadElement' ? 'spread' : 'literal'
}

return accumulator
},
[[], []],
)
.flat()

pairwise(nodes, (left, right) => {
let groupKindOrder = ['unknown']

if (typeof options.groupKind === 'string') {
groupKindOrder =
options.groupKind === 'literals-first'
? ['literal', 'spread']
: ['spread', 'literal']
}
let lastSortingNode = accumulator.at(-1)?.at(-1)
let sortingNode: SortingNode = {
name:
element.type === 'Literal'
? `${element.value}`
: sourceCode.text.slice(...element.range),
size: rangeToDiff(element.range),
node: element,
group,
}
if (
(partitionComment &&
hasPartitionComment(
partitionComment,
getCommentsBefore(element, sourceCode),
)) ||
(options.partitionByNewLine &&
lastSortingNode &&
getLinesBetween(sourceCode, lastSortingNode, sortingNode))
) {
accumulator.push([])
}

let leftNum = getGroupNumber(groupKindOrder, left)
let rightNum = getGroupNumber(groupKindOrder, right)
accumulator.at(-1)!.push(sortingNode)
}

if (
(options.groupKind !== 'mixed' && leftNum > rightNum) ||
((options.groupKind === 'mixed' || leftNum === rightNum) &&
isPositive(compare(left, right, options)))
) {
context.report({
messageId,
data: {
left: toSingleLine(left.name),
right: toSingleLine(right.name),
},
node: right.node,
fix: fixer => {
let sortedNodes =
options.groupKind !== 'mixed'
? groupKindOrder
.map(group => nodes.filter(n => n.group === group))
.map(groupedNodes => sortNodes(groupedNodes, options))
.flat()
: sortNodes(nodes, options)

return makeFixes(fixer, nodes, sortedNodes, sourceCode)
},
})
}
})
return accumulator
},
[[]],
)

for (let nodes of formattedMembers) {
pairwise(nodes, (left, right) => {
let groupKindOrder = ['unknown']

if (typeof options.groupKind === 'string') {
groupKindOrder =
options.groupKind === 'literals-first'
? ['literal', 'spread']
: ['spread', 'literal']
}
let leftNum = getGroupNumber(groupKindOrder, left)
let rightNum = getGroupNumber(groupKindOrder, right)

if (
(options.groupKind !== 'mixed' && leftNum > rightNum) ||
((options.groupKind === 'mixed' || leftNum === rightNum) &&
isPositive(compare(left, right, options)))
) {
context.report({
messageId,
data: {
left: toSingleLine(left.name),
right: toSingleLine(right.name),
},
node: right.node,
fix: fixer => {
let sortedNodes =
options.groupKind !== 'mixed'
? groupKindOrder
.map(group => nodes.filter(n => n.group === group))
.map(groupedNodes => sortNodes(groupedNodes, options))
.flat()
: sortNodes(nodes, options)

return makeFixes(fixer, nodes, sortedNodes, sourceCode, {
partitionComment,
})
},
})
}
})
}
}
}
Loading

0 comments on commit e4fc538

Please sign in to comment.