Skip to content

Commit

Permalink
feat: add always-on-top option to sort-objects rule
Browse files Browse the repository at this point in the history
  • Loading branch information
azat-io committed Jun 3, 2023
1 parent 3340a9f commit 464108f
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 6 deletions.
7 changes: 7 additions & 0 deletions docs/rules/sort-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ interface Options {
type?: 'alphabetical' | 'natural' | 'natural'
order?: 'asc' | 'desc'
'ignore-case'?: boolean
'always-on-top'?: string[]
}
```

Expand All @@ -95,6 +96,12 @@ interface Options {

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

### always-on-top

<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']`

## ⚙️ Usage

### Legacy Config
Expand Down
7 changes: 6 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ let createConfigWithOptions = (options: {
[sortMapElementsName]: ['error'],
[sortNamedExportsName]: ['error'],
[sortNamedImportsName]: ['error'],
[sortObjectsName]: ['error'],
[sortObjectsName]: [
'error',
{
'always-on-top': [],
},
],
[sortUnionTypesName]: ['error'],
}
return {
Expand Down
69 changes: 64 additions & 5 deletions rules/sort-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ import { sortNodes } from '../utils/sort-nodes'
import { makeFixes } from '../utils/make-fixes'
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'

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

type SortingNodeWithPosition = SortingNode & { position: Position }

type Options = [
Partial<{
'always-on-top': string[]
'ignore-case': boolean
order: SortOrder
type: SortType
Expand Down Expand Up @@ -53,6 +62,10 @@ export default createEslintRule<Options, MESSAGE_ID>({
type: 'boolean',
default: false,
},
'always-on-top': {
type: 'array',
default: [],
},
},
additionalProperties: false,
},
Expand All @@ -75,21 +88,23 @@ export default createEslintRule<Options, MESSAGE_ID>({
type: SortType.alphabetical,
'ignore-case': false,
order: SortOrder.asc,
'always-on-top': [],
})

let source = context.getSourceCode()

let formatProperties = (
props: TSESTree.ObjectLiteralElement[],
): SortingNode[][] =>
): SortingNodeWithPosition[][] =>
props.reduce(
(accumulator: SortingNode[][], prop) => {
(accumulator: SortingNodeWithPosition[][], prop) => {
if (prop.type === AST_NODE_TYPES.SpreadElement) {
accumulator.push([])
return accumulator
}

let name: string
let position: Position = Position.ignore

if (prop.key.type === AST_NODE_TYPES.Identifier) {
;({ name } = prop.key)
Expand All @@ -99,9 +114,17 @@ 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
}

let value = {
size: rangeToDiff(prop.range),
node: prop,
position,
name,
}

Expand All @@ -114,16 +137,52 @@ export default createEslintRule<Options, MESSAGE_ID>({

formatProperties(node.properties).forEach(nodes => {
pairwise(nodes, (first, second) => {
if (compare(first, second, options)) {
let comparison: boolean

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

comparison =
positionPower[first.position] < positionPower[second.position]
}

if (comparison) {
context.report({
messageId: 'unexpectedObjectsOrder',
data: {
first: first.name,
second: second.name,
},
node: second.node,
fix: fixer =>
makeFixes(fixer, nodes, sortNodes(nodes, options), source),
fix: fixer => {
let groups = groupBy(nodes, ({ position }) => position)

let getGroup = (index: string) =>
index in groups ? groups[index] : []

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

return makeFixes(fixer, nodes, sortedNodes, source)
},
})
}
})
Expand Down
Loading

0 comments on commit 464108f

Please sign in to comment.