Skip to content

Commit 285a451

Browse files
authored
feat: support for sorting by enum value
1 parent baa701d commit 285a451

File tree

5 files changed

+613
-24
lines changed

5 files changed

+613
-24
lines changed

docs/content/rules/sort-enums.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,26 @@ Controls whether sorting should be case-sensitive or not.
112112
- `true` — Ignore case when sorting alphabetically or naturally (e.g., “A” and “a” are the same).
113113
- `false` — Consider case when sorting (e.g., “A” comes before “a”).
114114

115+
### sortByValue
116+
117+
<sub>default: `false`</sub>
118+
119+
Controls whether sorting should be done using the enum's values or names.
120+
121+
- `true` — Use enum values.
122+
- `false` — Use enum names.
123+
124+
When this setting is `true`, numeric enums will have their values sorted numerically regardless of the `type` setting.
125+
126+
### forceNumericSort
127+
128+
<sub>default: `false`</sub>
129+
130+
Controls whether numeric enums should always be sorted numerically, regardless of the `type` and `sortByValue` settings.
131+
132+
- `true` — Use enum values.
133+
- `false` — Use enum names.
134+
115135
### partitionByComment
116136

117137
<sub>default: `false`</sub>
@@ -144,6 +164,7 @@ Allows you to use comments to separate the members of enums into logical groups.
144164
order: 'asc',
145165
ignoreCase: true,
146166
partitionByComment: false,
167+
sortByValue: false
147168
},
148169
],
149170
},
@@ -168,6 +189,8 @@ Allows you to use comments to separate the members of enums into logical groups.
168189
order: 'asc',
169190
ignoreCase: true,
170191
partitionByComment: false,
192+
sortByValue: false,
193+
forceNumericSort: false
171194
},
172195
],
173196
},

rules/sort-enums.ts

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { TSESTree } from '@typescript-eslint/types'
22

3+
import type { CompareOptions } from '../utils/compare'
34
import type { SortingNode } from '../typings'
45

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

1819
type MESSAGE_ID = 'unexpectedEnumsOrder'
1920

20-
type Options = [
21+
export type Options = [
2122
Partial<{
2223
type: 'alphabetical' | 'line-length' | 'natural'
2324
partitionByComment: string[] | boolean | string
25+
forceNumericSort: boolean
2426
order: 'desc' | 'asc'
27+
sortByValue: boolean
2528
ignoreCase: boolean
2629
}>,
2730
]
@@ -54,6 +57,15 @@ export default createEslintRule<Options, MESSAGE_ID>({
5457
'Controls whether sorting should be case-sensitive or not.',
5558
type: 'boolean',
5659
},
60+
sortByValue: {
61+
description: 'Compare enum values instead of names.',
62+
type: 'boolean',
63+
},
64+
forceNumericSort: {
65+
description:
66+
'Will always sort numeric enums by their value regardless of the sort type specified.',
67+
type: 'boolean',
68+
},
5769
partitionByComment: {
5870
description:
5971
'Allows you to use comments to separate the class members into logical groups.',
@@ -85,7 +97,9 @@ export default createEslintRule<Options, MESSAGE_ID>({
8597
type: 'alphabetical',
8698
order: 'asc',
8799
ignoreCase: true,
100+
sortByValue: false,
88101
partitionByComment: false,
102+
forceNumericSort: false,
89103
},
90104
],
91105
create: context => ({
@@ -94,21 +108,24 @@ export default createEslintRule<Options, MESSAGE_ID>({
94108
/* v8 ignore next 2 */
95109
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
96110
node.body?.members ?? nodeValue.members ?? []
111+
let members = getMembers(node)
97112
if (
98-
getMembers(node).length > 1 &&
99-
getMembers(node).every(({ initializer }) => initializer)
113+
members.length > 1 &&
114+
members.every(({ initializer }) => initializer)
100115
) {
101116
let options = complete(context.options.at(0), {
102117
partitionByComment: false,
103118
type: 'alphabetical',
104119
ignoreCase: true,
105120
order: 'asc',
121+
sortByValue: false,
122+
forceNumericSort: false,
106123
} as const)
107124

108125
let sourceCode = getSourceCode(context)
109126
let partitionComment = options.partitionByComment
110127

111-
let formattedMembers: SortingNode[][] = getMembers(node).reduce(
128+
let formattedMembers: SortingNode[][] = members.reduce(
112129
(accumulator: SortingNode[][], member) => {
113130
let comment = getCommentBefore(member, sourceCode)
114131

@@ -135,10 +152,37 @@ export default createEslintRule<Options, MESSAGE_ID>({
135152
},
136153
[[]],
137154
)
155+
let isNumericEnum = members.every(
156+
member =>
157+
member.initializer?.type === 'Literal' &&
158+
typeof member.initializer.value === 'number',
159+
)
138160

161+
let compareOptions: CompareOptions = {
162+
// If the enum is numeric, and we sort by value, always use the `natural` sort type, which will correctly sort them.
163+
type:
164+
isNumericEnum && (options.forceNumericSort || options.sortByValue)
165+
? 'natural'
166+
: options.type,
167+
order: options.order,
168+
ignoreCase: options.ignoreCase,
169+
// Get the enum value rather than the name if needed
170+
nodeValueGetter:
171+
options.sortByValue || (isNumericEnum && options.forceNumericSort)
172+
? sortingNode => {
173+
if (
174+
sortingNode.node.type === 'TSEnumMember' &&
175+
sortingNode.node.initializer?.type === 'Literal'
176+
) {
177+
return sortingNode.node.initializer.value?.toString() ?? ''
178+
}
179+
return ''
180+
}
181+
: undefined,
182+
}
139183
for (let nodes of formattedMembers) {
140184
pairwise(nodes, (left, right) => {
141-
if (isPositive(compare(left, right, options))) {
185+
if (isPositive(compare(left, right, compareOptions))) {
142186
context.report({
143187
messageId: 'unexpectedEnumsOrder',
144188
data: {
@@ -150,7 +194,7 @@ export default createEslintRule<Options, MESSAGE_ID>({
150194
makeFixes(
151195
fixer,
152196
nodes,
153-
sortNodes(nodes, options),
197+
sortNodes(nodes, compareOptions),
154198
sourceCode,
155199
{ partitionComment },
156200
),

0 commit comments

Comments
 (0)