Skip to content

Commit

Permalink
feat(sort-objects): adds newlinesBetween
Browse files Browse the repository at this point in the history
  • Loading branch information
hugop95 committed Oct 18, 2024
1 parent 5402aa1 commit 9517cb7
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 26 deletions.
14 changes: 14 additions & 0 deletions docs/content/rules/sort-objects.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,18 @@ const user = {

Each group of keys (separated by empty lines) is treated independently, and the order within each group is preserved.

### newlinesBetween

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

Specifies how new lines should be handled between object groups.

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

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

### styledComponents

<sub>default: `true`</sub>
Expand Down Expand Up @@ -358,6 +370,7 @@ Determines the matcher used for patterns in the `partitionByComment`, `ignorePat
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
styledComponents: true,
ignorePattern: [],
matcher: 'minimatch',
Expand Down Expand Up @@ -389,6 +402,7 @@ Determines the matcher used for patterns in the `partitionByComment`, `ignorePat
specialCharacters: 'keep',
partitionByComment: false,
partitionByNewLine: false,
newlinesBetween: 'ignore',
styledComponents: true,
ignorePattern: [],
matcher: 'minimatch',
Expand Down
88 changes: 63 additions & 25 deletions rules/sort-objects.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import type { TSESTree } from '@typescript-eslint/types'
import type { TSESLint } from '@typescript-eslint/utils'

import type { SortingNodeWithDependencies } from '../utils/sort-nodes-by-dependencies'

import {
getFirstUnorderedNodeDependentOn,
sortNodesByDependencies,
} from '../utils/sort-nodes-by-dependencies'
import { validateNewlinesAndPartitionConfiguration } from '../utils/validate-newlines-and-partition-configuration'
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 { 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 { getNodeParent } from '../utils/get-node-parent'
import { toSingleLine } from '../utils/to-single-line'
import { rangeToDiff } from '../utils/range-to-diff'
import { getSettings } from '../utils/get-settings'
import { useGroups } from '../utils/use-groups'
Expand All @@ -26,25 +27,20 @@ import { pairwise } from '../utils/pairwise'
import { matches } from '../utils/matches'

type MESSAGE_ID =
| 'missedSpacingBetweenObjectMembers'
| 'unexpectedObjectsDependencyOrder'
| 'extraSpacingBetweenObjectMembers'
| 'unexpectedObjectsGroupOrder'
| 'unexpectedObjectsOrder'

export enum Position {
'exception' = 'exception',
'ignore' = 'ignore',
}

type Group = 'unknown' | string
type SortingNodeWithPosition = SortingNodeWithDependencies & {
position: Position
}

type Options = [
Partial<{
customGroups: { [key: string]: string[] | string }
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
newlinesBetween: 'ignore' | 'always' | 'never'
specialCharacters: 'remove' | 'trim' | 'keep'
matcher: 'minimatch' | 'regex'
groups: (Group[] | Group)[]
Expand Down Expand Up @@ -119,6 +115,12 @@ export default createEslintRule<Options, MESSAGE_ID>({
'Allows to use spaces to separate the nodes into logical groups.',
type: 'boolean',
},
newlinesBetween: {
description:
'Specifies how new lines should be handled between object types groups.',
enum: ['ignore', 'always', 'never'],
type: 'string',
},
styledComponents: {
description: 'Controls whether to sort styled components.',
type: 'boolean',
Expand Down Expand Up @@ -179,6 +181,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
unexpectedObjectsOrder: 'Expected "{{right}}" to come before "{{left}}".',
unexpectedObjectsDependencyOrder:
'Expected dependency "{{right}}" to come before "{{nodeDependentOnRight}}".',
missedSpacingBetweenObjectMembers:
'Missed spacing between "{{left}}" and "{{right}}" objects.',
extraSpacingBetweenObjectMembers:
'Extra spacing between "{{left}}" and "{{right}}" objects.',
},
},
defaultOptions: [
Expand Down Expand Up @@ -212,6 +218,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
ignorePattern: [],
matcher: 'minimatch',
ignoreCase: true,
newlinesBetween: 'ignore',
specialCharacters: 'keep',
customGroups: {},
order: 'asc',
Expand All @@ -223,6 +230,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
['unknown'],
Object.keys(options.customGroups),
)
validateNewlinesAndPartitionConfiguration(options)

let shouldIgnore = false

Expand Down Expand Up @@ -383,9 +391,9 @@ export default createEslintRule<Options, MESSAGE_ID>({
| TSESTree.RestElement
| TSESTree.Property
)[],
): SortingNodeWithPosition[][] =>
): SortingNodeWithDependencies[][] =>
props.reduce(
(accumulator: SortingNodeWithPosition[][], prop) => {
(accumulator: SortingNodeWithDependencies[][], prop) => {
if (
prop.type === 'SpreadElement' ||
prop.type === 'RestElement'
Expand All @@ -409,7 +417,6 @@ export default createEslintRule<Options, MESSAGE_ID>({
}

let name: string
let position: Position = Position.ignore
let dependencies: string[] = []

let { getGroup, setCustomGroups } = useGroups(options)
Expand Down Expand Up @@ -446,7 +453,6 @@ export default createEslintRule<Options, MESSAGE_ID>({
...propSortingNode,
group: getGroup(),
dependencies,
position,
}

accumulator.at(-1)!.push(value)
Expand All @@ -463,36 +469,68 @@ export default createEslintRule<Options, MESSAGE_ID>({
.flat(),
)
let nodes = formattedMembers.flat()

pairwise(nodes, (left, right) => {
let leftNum = getGroupNumber(options.groups, left)
let rightNum = getGroupNumber(options.groups, right)

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

let messageIds: MESSAGE_ID[] = []
let firstUnorderedNodeDependentOnRight:
| SortingNodeWithDependencies
| undefined

if (indexOfLeft > indexOfRight) {
let firstUnorderedNodeDependentOnRight =
firstUnorderedNodeDependentOnRight =
getFirstUnorderedNodeDependentOn(right, nodes)
let leftNum = getGroupNumber(options.groups, left)
let rightNum = getGroupNumber(options.groups, right)
let messageId: MESSAGE_ID
if (firstUnorderedNodeDependentOnRight) {
messageId = 'unexpectedObjectsDependencyOrder'
messageIds.push('unexpectedObjectsDependencyOrder')
} else {
messageId =
messageIds.push(
leftNum !== rightNum
? 'unexpectedObjectsGroupOrder'
: 'unexpectedObjectsOrder'
: 'unexpectedObjectsOrder',
)
}
}

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

for (let messageId of messageIds) {
context.report({
messageId,
data: {
left: toSingleLine(left.name),
left: left.name,
leftGroup: left.group,
right: toSingleLine(right.name),
right: right.name,
rightGroup: right.group,
nodeDependentOnRight: firstUnorderedNodeDependentOnRight?.name,
},
node: right.node,
fix: (fixer: TSESLint.RuleFixer) =>
makeFixes(fixer, nodes, sortedNodes, sourceCode, options),
fix: fixer => [
...makeFixes(fixer, nodes, sortedNodes, sourceCode, options),
...makeNewlinesFixes(
fixer,
nodes,
sortedNodes,
sourceCode,
options,
),
],
})
}
})
Expand Down
127 changes: 126 additions & 1 deletion test/sort-objects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ describe(ruleName, () => {
iHaveFooInMyName: string,
meTooIHaveFoo: string,
a: string,
b: string,
b: "b",,
}
`,
options: [
Expand Down Expand Up @@ -1617,6 +1617,131 @@ describe(ruleName, () => {
invalid: [],
},
)

describe(`${ruleName}: newlinesBetween`, () => {
ruleTester.run(
`${ruleName}(${type}): removes newlines when never`,
rule,
{
valid: [],
invalid: [
{
code: dedent`
let Obj = {
a: () => null,
c: "c",
b: "b",
d: "d",
}
`,
output: dedent`
let Obj = {
a: () => null,
b: "b",
c: "c",
d: "d",
}
`,
options: [
{
...options,
newlinesBetween: 'never',
groups: ['method', 'unknown'],
},
],
errors: [
{
messageId: 'extraSpacingBetweenObjectMembers',
data: {
left: 'a',
right: 'c',
},
},
{
messageId: 'unexpectedObjectsOrder',
data: {
left: 'c',
right: 'b',
},
},
{
messageId: 'extraSpacingBetweenObjectMembers',
data: {
left: 'b',
right: 'd',
},
},
],
},
],
},
)

ruleTester.run(
`${ruleName}(${type}): keeps one newline when always`,
rule,
{
valid: [],
invalid: [
{
code: dedent`
let Obj = {
a: () => null,
c: "c",
b: "b",
d: "d",
}
`,
output: dedent`
let Obj = {
a: () => null,
b: "b",
c: "c",
d: "d",
}
`,
options: [
{
...options,
newlinesBetween: 'always',
groups: ['method', 'unknown'],
},
],
errors: [
{
messageId: 'extraSpacingBetweenObjectMembers',
data: {
left: 'a',
right: 'c',
},
},
{
messageId: 'unexpectedObjectsOrder',
data: {
left: 'c',
right: 'b',
},
},
{
messageId: 'extraSpacingBetweenObjectMembers',
data: {
left: 'b',
right: 'd',
},
},
],
},
],
},
)
})
})

describe(`${ruleName}: sorting by natural order`, () => {
Expand Down

0 comments on commit 9517cb7

Please sign in to comment.