Skip to content

Commit

Permalink
feat: support for sorting by enum value
Browse files Browse the repository at this point in the history
  • Loading branch information
hugop95 committed Aug 14, 2024
1 parent baa701d commit 285a451
Show file tree
Hide file tree
Showing 5 changed files with 613 additions and 24 deletions.
23 changes: 23 additions & 0 deletions docs/content/rules/sort-enums.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,26 @@ Controls whether sorting should be case-sensitive or not.
- `true` — Ignore case when sorting alphabetically or naturally (e.g., “A” and “a” are the same).
- `false` — Consider case when sorting (e.g., “A” comes before “a”).

### sortByValue

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

Controls whether sorting should be done using the enum's values or names.

- `true` — Use enum values.
- `false` — Use enum names.

When this setting is `true`, numeric enums will have their values sorted numerically regardless of the `type` setting.

### forceNumericSort

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

Controls whether numeric enums should always be sorted numerically, regardless of the `type` and `sortByValue` settings.

- `true` — Use enum values.
- `false` — Use enum names.

### partitionByComment

<sub>default: `false`</sub>
Expand Down Expand Up @@ -144,6 +164,7 @@ Allows you to use comments to separate the members of enums into logical groups.
order: 'asc',
ignoreCase: true,
partitionByComment: false,
sortByValue: false
},
],
},
Expand All @@ -168,6 +189,8 @@ Allows you to use comments to separate the members of enums into logical groups.
order: 'asc',
ignoreCase: true,
partitionByComment: false,
sortByValue: false,
forceNumericSort: false
},
],
},
Expand Down
56 changes: 50 additions & 6 deletions rules/sort-enums.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { TSESTree } from '@typescript-eslint/types'

import type { CompareOptions } from '../utils/compare'
import type { SortingNode } from '../typings'

import { isPartitionComment } from '../utils/is-partition-comment'
Expand All @@ -17,11 +18,13 @@ import { compare } from '../utils/compare'

type MESSAGE_ID = 'unexpectedEnumsOrder'

type Options = [
export type Options = [
Partial<{
type: 'alphabetical' | 'line-length' | 'natural'
partitionByComment: string[] | boolean | string
forceNumericSort: boolean
order: 'desc' | 'asc'
sortByValue: boolean
ignoreCase: boolean
}>,
]
Expand Down Expand Up @@ -54,6 +57,15 @@ export default createEslintRule<Options, MESSAGE_ID>({
'Controls whether sorting should be case-sensitive or not.',
type: 'boolean',
},
sortByValue: {
description: 'Compare enum values instead of names.',
type: 'boolean',
},
forceNumericSort: {
description:
'Will always sort numeric enums by their value regardless of the sort type specified.',
type: 'boolean',
},
partitionByComment: {
description:
'Allows you to use comments to separate the class members into logical groups.',
Expand Down Expand Up @@ -85,7 +97,9 @@ export default createEslintRule<Options, MESSAGE_ID>({
type: 'alphabetical',
order: 'asc',
ignoreCase: true,
sortByValue: false,
partitionByComment: false,
forceNumericSort: false,
},
],
create: context => ({
Expand All @@ -94,21 +108,24 @@ export default createEslintRule<Options, MESSAGE_ID>({
/* v8 ignore next 2 */
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
node.body?.members ?? nodeValue.members ?? []
let members = getMembers(node)
if (
getMembers(node).length > 1 &&
getMembers(node).every(({ initializer }) => initializer)
members.length > 1 &&
members.every(({ initializer }) => initializer)
) {
let options = complete(context.options.at(0), {
partitionByComment: false,
type: 'alphabetical',
ignoreCase: true,
order: 'asc',
sortByValue: false,
forceNumericSort: false,
} as const)

let sourceCode = getSourceCode(context)
let partitionComment = options.partitionByComment

let formattedMembers: SortingNode[][] = getMembers(node).reduce(
let formattedMembers: SortingNode[][] = members.reduce(
(accumulator: SortingNode[][], member) => {
let comment = getCommentBefore(member, sourceCode)

Expand All @@ -135,10 +152,37 @@ export default createEslintRule<Options, MESSAGE_ID>({
},
[[]],
)
let isNumericEnum = members.every(
member =>
member.initializer?.type === 'Literal' &&
typeof member.initializer.value === 'number',
)

let compareOptions: CompareOptions = {
// If the enum is numeric, and we sort by value, always use the `natural` sort type, which will correctly sort them.
type:
isNumericEnum && (options.forceNumericSort || options.sortByValue)
? 'natural'
: options.type,
order: options.order,
ignoreCase: options.ignoreCase,
// Get the enum value rather than the name if needed
nodeValueGetter:
options.sortByValue || (isNumericEnum && options.forceNumericSort)
? sortingNode => {
if (
sortingNode.node.type === 'TSEnumMember' &&
sortingNode.node.initializer?.type === 'Literal'
) {
return sortingNode.node.initializer.value?.toString() ?? ''
}
return ''
}
: undefined,
}
for (let nodes of formattedMembers) {
pairwise(nodes, (left, right) => {
if (isPositive(compare(left, right, options))) {
if (isPositive(compare(left, right, compareOptions))) {
context.report({
messageId: 'unexpectedEnumsOrder',
data: {
Expand All @@ -150,7 +194,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
makeFixes(
fixer,
nodes,
sortNodes(nodes, options),
sortNodes(nodes, compareOptions),
sourceCode,
{ partitionComment },
),
Expand Down
Loading

0 comments on commit 285a451

Please sign in to comment.