Skip to content

Commit

Permalink
feat!: move from always-on-top option to groups in sort-objects rule
Browse files Browse the repository at this point in the history
  • Loading branch information
azat-io committed Sep 4, 2023
1 parent 7a792cc commit 0bbcb5a
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 103 deletions.
35 changes: 29 additions & 6 deletions docs/rules/sort-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ interface Options {
type?: 'alphabetical' | 'natural' | 'line-length'
order?: 'asc' | 'desc'
'ignore-case'?: boolean
'always-on-top'?: string[]
groups?: (string | string[])[]
'custom-groups': { [key: string]: string[] | string }
'partition-by-comment': string[] | string | boolean
}
```
Expand All @@ -109,11 +110,27 @@ interface Options {

Only affects alphabetical and natural sorting. When `true` the rule ignores the case-sensitivity of the order.

### always-on-top
### groups

<sub>(default: `[]`)</sub>

You can set a list of key names that will always go at the beginning of the object. For example: `['id', 'name']`
You can set up a list of object keys groups for sorting. Groups can be combined. There are no predefined groups.

### custom-groups

<sub>(default: `{}`)</sub>

You can define your own groups for object keys. The [minimatch](https://github.com/isaacs/minimatch) library is used for pattern matching.

Example:

```
{
"custom-groups": {
"top": "id"
}
}
```

### partition-by-comment

Expand All @@ -137,8 +154,11 @@ The [minimatch](https://github.com/isaacs/minimatch) library is used for pattern
{
"type": "natural",
"order": "asc",
"always-on-top": ["id", "name"],
"partition-by-comment": "Part:**"
"partition-by-comment": "Part:**",
"groups": ["id", "unknown"],
"custom-groups": {
"id": "id"
}
}
]
}
Expand All @@ -160,8 +180,11 @@ export default [
{
type: 'natural',
order: 'asc',
'always-on-top': ['id', 'name'],
'partition-by-comment': 'Part:**',
groups: ['id', 'unknown'],
'custom-groups': {
id: 'id',
},
},
],
},
Expand Down
81 changes: 39 additions & 42 deletions rules/sort-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import type { PartitionComment, SortingNode } from '../typings'
import { isPartitionComment } from '../utils/is-partition-comment'
import { getCommentBefore } from '../utils/get-comment-before'
import { createEslintRule } from '../utils/create-eslint-rule'
import { getGroupNumber } from '../utils/get-group-number'
import { toSingleLine } from '../utils/to-single-line'
import { rangeToDiff } from '../utils/range-to-diff'
import { SortOrder, SortType } from '../typings'
import { useGroups } from '../utils/use-groups'
import { makeFixes } from '../utils/make-fixes'
import { sortNodes } from '../utils/sort-nodes'
import { complete } from '../utils/complete'
import { pairwise } from '../utils/pairwise'
import { groupBy } from '../utils/group-by'
import { compare } from '../utils/compare'

type MESSAGE_ID = 'unexpectedObjectsOrder'
Expand All @@ -31,8 +32,9 @@ type SortingNodeWithPosition = SortingNode & {

type Options = [
Partial<{
'custom-groups': { [key: string]: string[] | string }
'partition-by-comment': PartitionComment
'always-on-top': string[]
groups: (string[] | string)[]
'ignore-case': boolean
order: SortOrder
type: SortType
Expand All @@ -54,6 +56,9 @@ export default createEslintRule<Options, MESSAGE_ID>({
{
type: 'object',
properties: {
'custom-groups': {
type: 'object',
},
'partition-by-comment': {
type: ['boolean', 'string', 'array'],
default: false,
Expand All @@ -74,7 +79,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
type: 'boolean',
default: false,
},
'always-on-top': {
groups: {
type: 'array',
default: [],
},
Expand Down Expand Up @@ -102,7 +107,8 @@ export default createEslintRule<Options, MESSAGE_ID>({
type: SortType.alphabetical,
'ignore-case': false,
order: SortOrder.asc,
'always-on-top': [],
'custom-groups': {},
groups: [],
})

let source = context.getSourceCode()
Expand Down Expand Up @@ -141,6 +147,8 @@ export default createEslintRule<Options, MESSAGE_ID>({
let position: Position = Position.ignore
let dependencies: string[] = []

let { getGroup, setCustomGroups } = useGroups(options.groups)

if (prop.key.type === AST_NODE_TYPES.Identifier) {
;({ name } = prop.key)
} else if (prop.key.type === AST_NODE_TYPES.Literal) {
Expand All @@ -149,13 +157,6 @@ export default createEslintRule<Options, MESSAGE_ID>({
name = source.text.slice(...prop.key.range)
}

if (
prop.key.type === AST_NODE_TYPES.Identifier &&
options['always-on-top'].includes(prop.key.name)
) {
position = Position.exception
}

if (prop.value.type === AST_NODE_TYPES.AssignmentPattern) {
let addDependencies = (
value: TSESTree.AssignmentPattern,
Expand Down Expand Up @@ -233,8 +234,11 @@ export default createEslintRule<Options, MESSAGE_ID>({
addDependencies(prop.value, true)
}

setCustomGroups(options['custom-groups'], name)

let value = {
size: rangeToDiff(prop.range),
group: getGroup(),
dependencies,
node: prop,
position,
Expand All @@ -250,45 +254,38 @@ export default createEslintRule<Options, MESSAGE_ID>({

for (let nodes of formatProperties(node.properties)) {
pairwise(nodes, (left, right) => {
let comparison: boolean
let leftNum = getGroupNumber(options.groups, left)
let rightNum = getGroupNumber(options.groups, right)

if (
left.position === Position.exception &&
right.position === Position.exception
leftNum > rightNum ||
(leftNum === rightNum && compare(left, right, options))
) {
comparison =
options['always-on-top'].indexOf(left.name) >
options['always-on-top'].indexOf(right.name)
} else if (left.position === right.position) {
comparison = compare(left, right, options)
} else {
let positionPower = {
[Position.exception]: 1,
[Position.ignore]: 0,
}

comparison =
positionPower[left.position] < positionPower[right.position]
}

if (comparison) {
let fix:
| ((fixer: TSESLint.RuleFixer) => TSESLint.RuleFix[])
| undefined = fixer => {
let groups = groupBy(nodes, ({ position }) => position)

let getGroup = (index: string) =>
index in groups ? groups[index] : []
let grouped: {
[key: string]: SortingNode[]
} = {}

for (let currentNode of nodes) {
let groupNum = getGroupNumber(options.groups, currentNode)

if (!(groupNum in grouped)) {
grouped[groupNum] = [currentNode]
} else {
grouped[groupNum] = sortNodes(
[...grouped[groupNum], currentNode],
options,
)
}
}

let sortedNodes = [
getGroup(Position.exception).sort(
(aNode, bNode) =>
options['always-on-top'].indexOf(aNode.name) -
options['always-on-top'].indexOf(bNode.name),
),
let sortedNodes: SortingNode[] = []

sortNodes(getGroup(Position.ignore), options),
].flat()
for (let group of Object.keys(grouped).sort()) {
sortedNodes.push(...sortNodes(grouped[group], options))
}

return makeFixes(fixer, nodes, sortedNodes, source, {
partitionComment: options['partition-by-comment'],
Expand Down
Loading

0 comments on commit 0bbcb5a

Please sign in to comment.