Skip to content

Commit

Permalink
feat(sort-classes): add partition by new line and newlines between op…
Browse files Browse the repository at this point in the history
…tions
  • Loading branch information
hugop95 authored and azat-io committed Nov 19, 2024
1 parent 79399cc commit 4369803
Show file tree
Hide file tree
Showing 16 changed files with 383 additions and 70 deletions.
45 changes: 45 additions & 0 deletions docs/content/rules/sort-classes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,51 @@ Allows you to use comments to separate the class members into logical groups. Th
- `false` — Comments will not be used as delimiters.
- string — A regexp pattern to specify which comments should act as delimiters.

### partitionByNewLine

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

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

```ts
class User {
// Group 1
firstName: string;
lastName: string;

// Group 2
age: number;
birthDate: Date;

// Group 3
address: {
street: string;
city: string;
};
phone?: string;

// Group 4
updateAddress(address: string) {}
updatePhone(phone?: string) {}

// Group 5
editFirstName(firstName: string) {}
editLastName(lastName: string) {}
};
```

### newlinesBetween

<sub>default: `'ignore'`</sub>

Specifies how new lines should be handled between class member groups.

- `ignore` — Do not report errors related to new lines between object type groups.
- `always` — Enforce one new line between each group, and forbid new lines inside a group.
- `never` — No new lines are allowed in object types.

This options is only applicable when `partitionByNewLine` is `false`.

### groups

<sub>
Expand Down
87 changes: 72 additions & 15 deletions rules/sort-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { SortingNodeWithDependencies } from '../utils/sort-nodes-by-depende

import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
specialCharactersJsonSchema,
ignoreCaseJsonSchema,
localesJsonSchema,
Expand All @@ -33,10 +34,14 @@ import {
getFirstUnorderedNodeDependentOn,
sortNodesByDependencies,
} from '../utils/sort-nodes-by-dependencies'
import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration'
import { hasPartitionComment } from '../utils/is-partition-comment'
import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
import { getCommentsBefore } from '../utils/get-comments-before'
import { makeNewlinesFixes } from '../utils/make-newlines-fixes'
import { getNewlinesErrors } from '../utils/get-newlines-errors'
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 @@ -49,6 +54,8 @@ import { pairwise } from '../utils/pairwise'

type MESSAGE_ID =
| 'unexpectedClassesDependencyOrder'
| 'missedSpacingBetweenClassMembers'
| 'extraSpacingBetweenClassMembers'
| 'unexpectedClassesGroupOrder'
| 'unexpectedClassesOrder'

Expand Down Expand Up @@ -78,6 +85,8 @@ const defaultOptions: Required<SortClassesOptions[0]> = {
'unknown',
],
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
type: 'alphabetical',
ignoreCase: true,
specialCharacters: 'keep',
Expand Down Expand Up @@ -108,6 +117,13 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({
description:
'Allows to use comments to separate the class members into logical groups.',
},
partitionByNewLine: partitionByNewLineJsonSchema,
newlinesBetween: {
description:
'Specifies how new lines should be handled between class members groups.',
enum: ['ignore', 'always', 'never'],
type: 'string',
},
groups: groupsJsonSchema,
customGroups: {
description: 'Specifies custom groups.',
Expand Down Expand Up @@ -157,6 +173,10 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({
unexpectedClassesOrder: 'Expected "{{right}}" to come before "{{left}}".',
unexpectedClassesDependencyOrder:
'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
missedSpacingBetweenClassMembers:
'Missed spacing between "{{left}}" and "{{right}}" objects.',
extraSpacingBetweenClassMembers:
'Extra spacing between "{{left}}" and "{{right}}" objects.',
},
},
defaultOptions: [defaultOptions],
Expand All @@ -168,6 +188,7 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({
let options = complete(context.options.at(0), settings, defaultOptions)

validateGroupsConfiguration(options.groups, options.customGroups)
validateNewlinesAndPartitionConfiguration(options)

let sourceCode = getSourceCode(context)
let className = node.parent.id?.name
Expand Down Expand Up @@ -304,15 +325,6 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({

let formattedNodes: SortingNodeWithDependencies[][] = node.body.reduce(
(accumulator: SortingNodeWithDependencies[][], member) => {
let comments = getCommentsBefore(member, sourceCode)

if (
options.partitionByComment &&
hasPartitionComment(options.partitionByComment, comments)
) {
accumulator.push([])
}

let name: string
let dependencies: string[] = []
let { getGroup, defineGroup } = useGroups(options)
Expand Down Expand Up @@ -557,6 +569,24 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({
),
}

let comments = getCommentsBefore(member, sourceCode)
let lastMember = accumulator.at(-1)?.at(-1)
if (
options.partitionByComment &&
hasPartitionComment(options.partitionByComment, comments)
) {
accumulator.push([])
}
if (
(options.partitionByNewLine &&
lastMember &&
getLinesBetween(sourceCode, lastMember, sortingNode)) ||
(options.partitionByComment &&
hasPartitionComment(options.partitionByComment, comments))
) {
accumulator.push([])
}

accumulator.at(-1)!.push(sortingNode)

return accumulator
Expand Down Expand Up @@ -585,21 +615,40 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({

let indexOfLeft = sortedNodes.indexOf(left)
let indexOfRight = sortedNodes.indexOf(right)

let messageIds: MESSAGE_ID[] = []
let firstUnorderedNodeDependentOnRight =
getFirstUnorderedNodeDependentOn(right, nodes)
if (
firstUnorderedNodeDependentOnRight ||
indexOfLeft > indexOfRight
) {
let messageId: MESSAGE_ID
if (firstUnorderedNodeDependentOnRight) {
messageId = 'unexpectedClassesDependencyOrder'
messageIds.push('unexpectedClassesDependencyOrder')
} else {
messageId =
messageIds.push(
leftNum !== rightNum
? 'unexpectedClassesGroupOrder'
: 'unexpectedClassesOrder'
: 'unexpectedClassesOrder',
)
}
}

messageIds = [
...messageIds,
...getNewlinesErrors({
left,
leftNum,
right,
rightNum,
sourceCode,
missedSpacingError: 'missedSpacingBetweenClassMembers',
extraSpacingError: 'extraSpacingBetweenClassMembers',
options,
}),
]

for (let messageId of messageIds) {
context.report({
messageId,
data: {
Expand All @@ -610,8 +659,16 @@ export default createEslintRule<SortClassesOptions, MESSAGE_ID>({
nodeDependentOnRight: firstUnorderedNodeDependentOnRight?.name,
},
node: right.node,
fix: (fixer: TSESLint.RuleFixer) =>
makeFixes(fixer, nodes, sortedNodes, sourceCode, options),
fix: (fixer: TSESLint.RuleFixer) => [
...makeFixes(fixer, nodes, sortedNodes, sourceCode, options),
...makeNewlinesFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
],
})
}
})
Expand Down
2 changes: 2 additions & 0 deletions rules/sort-classes.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,10 @@ export type SortClassesOptions = [
Partial<{
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
newlinesBetween: 'ignore' | 'always' | 'never'
specialCharacters: 'remove' | 'trim' | 'keep'
locales: NonNullable<Intl.LocalesArgument>
partitionByNewLine: boolean
customGroups: CustomGroup[]
groups: (Group[] | Group)[]
order: 'desc' | 'asc'
Expand Down
7 changes: 2 additions & 5 deletions rules/sort-enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { CompareOptions } from '../utils/compare'

import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
specialCharactersJsonSchema,
ignoreCaseJsonSchema,
localesJsonSchema,
Expand Down Expand Up @@ -87,11 +88,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
description:
'Allows you to use comments to separate the members of enums into logical groups.',
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
},
Expand Down
7 changes: 2 additions & 5 deletions rules/sort-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { SortingNode } from '../typings'

import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
specialCharactersJsonSchema,
ignoreCaseJsonSchema,
localesJsonSchema,
Expand Down Expand Up @@ -74,11 +75,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
description:
'Allows you to use comments to separate the exports into logical groups.',
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: partitionByNewLineJsonSchema,
groupKind: {
description: 'Specifies top-level groups.',
type: 'string',
Expand Down
7 changes: 2 additions & 5 deletions rules/sort-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { SortingNode } from '../typings'

import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
specialCharactersJsonSchema,
customGroupsJsonSchema,
ignoreCaseJsonSchema,
Expand Down Expand Up @@ -100,11 +101,7 @@ export default createEslintRule<Options<string[]>, MESSAGE_ID>({
description:
'Allows you to use comments to separate the interface properties into logical groups.',
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: partitionByNewLineJsonSchema,
newlinesBetween: {
description:
'Specifies how new lines should be handled between object types groups.',
Expand Down
7 changes: 2 additions & 5 deletions rules/sort-intersection-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { SortingNode } from '../typings'

import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
specialCharactersJsonSchema,
ignoreCaseJsonSchema,
localesJsonSchema,
Expand Down Expand Up @@ -98,11 +99,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
description:
'Allows you to use comments to separate the intersection types members into logical groups.',
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: partitionByNewLineJsonSchema,
newlinesBetween: {
description:
'Specifies how new lines should be handled between object types groups.',
Expand Down
7 changes: 2 additions & 5 deletions rules/sort-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { SortingNode } from '../typings'

import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
specialCharactersJsonSchema,
ignoreCaseJsonSchema,
localesJsonSchema,
Expand Down Expand Up @@ -71,11 +72,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
description:
'Allows you to use comments to separate the maps members into logical groups.',
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
},
Expand Down
7 changes: 2 additions & 5 deletions rules/sort-named-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { SortingNode } from '../typings'

import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
specialCharactersJsonSchema,
ignoreCaseJsonSchema,
localesJsonSchema,
Expand Down Expand Up @@ -76,11 +77,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
description:
'Allows you to use comments to separate the named exports members into logical groups.',
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
},
Expand Down
7 changes: 2 additions & 5 deletions rules/sort-named-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { SortingNode } from '../typings'

import {
partitionByCommentJsonSchema,
partitionByNewLineJsonSchema,
specialCharactersJsonSchema,
ignoreCaseJsonSchema,
localesJsonSchema,
Expand Down Expand Up @@ -82,11 +83,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
description:
'Allows you to use comments to separate the named imports members into logical groups.',
},
partitionByNewLine: {
description:
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
partitionByNewLine: partitionByNewLineJsonSchema,
},
additionalProperties: false,
},
Expand Down
Loading

0 comments on commit 4369803

Please sign in to comment.