Skip to content

Commit fa55de3

Browse files
authored
feat(sort-enums)!: replace force numeric sort and update default sort by value option
1 parent baade3e commit fa55de3

File tree

4 files changed

+130
-103
lines changed

4 files changed

+130
-103
lines changed

docs/content/rules/sort-enums.mdx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -170,24 +170,16 @@ Specifies the sorting locales. Refer To [String.prototype.localeCompare() - loca
170170

171171
### sortByValue
172172

173-
<sub>default: `false`</sub>
173+
<sub>default: `'ifNumericEnum'`</sub>
174174

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

177-
- `true` — Use enum values.
178-
- `false` — Use enum names.
177+
- `'always'` — Use enum values.
178+
- `'never'` — Use enum names.
179+
- `'ifNumericEnum'` — Use enum values only if the enum is numeric.
179180

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

182-
### forceNumericSort
183-
184-
<sub>default: `false`</sub>
185-
186-
Controls whether numeric enums should always be sorted numerically, regardless of the `type` and `sortByValue` settings.
187-
188-
- `true` — Use enum values.
189-
- `false` — Use enum names.
190-
191183
### partitionByComment
192184

193185
<sub>default: `false`</sub>
@@ -360,8 +352,7 @@ Custom groups have a higher priority than any predefined group.
360352
partitionByComment: false,
361353
partitionByNewLine: false,
362354
newlinesBetween: 'ignore',
363-
sortByValue: false,
364-
forceNumericSort: false,
355+
sortByValue: "ifNumericEnum",
365356
groups: [],
366357
customGroups: [],
367358
},
@@ -392,8 +383,7 @@ Custom groups have a higher priority than any predefined group.
392383
partitionByComment: false,
393384
partitionByNewLine: false,
394385
newlinesBetween: 'ignore',
395-
sortByValue: false,
396-
forceNumericSort: false,
386+
sortByValue: "ifNumericEnum",
397387
groups: [],
398388
customGroups: [],
399389
},

rules/sort-enums.ts

Lines changed: 68 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { sortNodesByDependencies } from '../utils/sort-nodes-by-dependencies'
2828
import { getEslintDisabledLines } from '../utils/get-eslint-disabled-lines'
2929
import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
3030
import { doesCustomGroupMatch } from '../utils/does-custom-group-match'
31+
import { UnreachableCaseError } from '../utils/unreachable-case-error'
3132
import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
3233
import { singleCustomGroupJsonSchema } from './sort-enums/types'
3334
import { createEslintRule } from '../utils/create-eslint-rule'
@@ -55,13 +56,12 @@ interface SortEnumsSortingNode
5556

5657
let defaultOptions: Required<Options[number]> = {
5758
fallbackSort: { type: 'unsorted' },
59+
sortByValue: 'ifNumericEnum',
5860
partitionByComment: false,
5961
partitionByNewLine: false,
6062
specialCharacters: 'keep',
6163
newlinesBetween: 'ignore',
62-
forceNumericSort: false,
6364
type: 'alphabetical',
64-
sortByValue: false,
6565
ignoreCase: true,
6666
locales: 'en-US',
6767
customGroups: [],
@@ -202,9 +202,7 @@ export default createEslintRule<Options, MessageId>({
202202
let nodes = formattedMembers.flat()
203203

204204
let isNumericEnum = nodes.every(
205-
sortingNode =>
206-
sortingNode.numericValue !== null &&
207-
!Number.isNaN(sortingNode.numericValue),
205+
sortingNode => sortingNode.numericValue !== null,
208206
)
209207

210208
let nodeValueGetter = computeNodeValueGetter({
@@ -262,14 +260,10 @@ export default createEslintRule<Options, MessageId>({
262260
{
263261
properties: {
264262
...commonJsonSchemas,
265-
forceNumericSort: {
266-
description:
267-
'Will always sort numeric enums by their value regardless of the sort type specified.',
268-
type: 'boolean',
269-
},
270263
sortByValue: {
271-
description: 'Compare enum values instead of names.',
272-
type: 'boolean',
264+
description: 'Specifies whether to sort enums by value.',
265+
enum: ['always', 'ifNumericEnum', 'never'],
266+
type: 'string',
273267
},
274268
customGroups: buildCustomGroupsArrayJsonSchema({
275269
singleCustomGroupJsonSchema,
@@ -306,9 +300,12 @@ function getBinaryExpressionNumberValue(
306300
leftExpression: TSESTree.PrivateIdentifier | TSESTree.Expression,
307301
rightExpression: TSESTree.Expression,
308302
operator: string,
309-
): number {
303+
): number | null {
310304
let left = getExpressionNumberValue(leftExpression)
311305
let right = getExpressionNumberValue(rightExpression)
306+
if (left === null || right === null) {
307+
return null
308+
}
312309
switch (operator) {
313310
case '**':
314311
return left ** right
@@ -334,11 +331,40 @@ function getBinaryExpressionNumberValue(
334331
return left ^ right
335332
/* v8 ignore next 2 - Unsure if we can reach it */
336333
default:
337-
return Number.NaN
334+
return null
335+
}
336+
}
337+
338+
function computeNodeValueGetter({
339+
isNumericEnum,
340+
options,
341+
}: {
342+
options: Pick<Required<Options[number]>, 'sortByValue'>
343+
isNumericEnum: boolean
344+
}): NodeValueGetterFunction<SortEnumsSortingNode> | null {
345+
switch (options.sortByValue) {
346+
case 'ifNumericEnum':
347+
if (!isNumericEnum) {
348+
return null
349+
}
350+
break
351+
case 'always':
352+
break
353+
case 'never':
354+
return null
355+
/* v8 ignore next 2 */
356+
default:
357+
throw new UnreachableCaseError(options.sortByValue)
358+
}
359+
return sortingNode => {
360+
if (isNumericEnum) {
361+
return sortingNode.numericValue!.toString()
362+
}
363+
return sortingNode.value ?? ''
338364
}
339365
}
340366

341-
function getExpressionNumberValue(expression: TSESTree.Node): number {
367+
function getExpressionNumberValue(expression: TSESTree.Node): number | null {
342368
switch (expression.type) {
343369
case 'BinaryExpression':
344370
return getBinaryExpressionNumberValue(
@@ -352,55 +378,20 @@ function getExpressionNumberValue(expression: TSESTree.Node): number {
352378
expression.operator,
353379
)
354380
case 'Literal':
355-
return typeof expression.value === 'number'
356-
? expression.value
357-
: Number.NaN
381+
return typeof expression.value === 'number' ? expression.value : null
358382
default:
359-
return Number.NaN
383+
return null
360384
}
361385
}
362386

363-
function computeNodeValueGetter({
364-
isNumericEnum,
365-
options,
366-
}: {
367-
options: Pick<Required<Options[number]>, 'forceNumericSort' | 'sortByValue'>
368-
isNumericEnum: boolean
369-
}): NodeValueGetterFunction<SortEnumsSortingNode> | null {
370-
return options.sortByValue || (isNumericEnum && options.forceNumericSort)
371-
? sortingNode => {
372-
if (isNumericEnum) {
373-
return sortingNode.numericValue!.toString()
374-
}
375-
return sortingNode.value ?? ''
376-
}
377-
: null
378-
}
379-
380-
function computeOptionType({
381-
isNumericEnum,
382-
options,
383-
}: {
384-
options: Pick<
385-
Required<Options[number]>,
386-
'forceNumericSort' | 'sortByValue' | 'type'
387-
>
388-
isNumericEnum: boolean
389-
}): TypeOption {
390-
/**
391-
* If the enum is numeric, and we sort by value, always use the `natural` sort
392-
* type, which will correctly sort them.
393-
*/
394-
return isNumericEnum && (options.forceNumericSort || options.sortByValue)
395-
? 'natural'
396-
: options.type
397-
}
398-
399387
function getUnaryExpressionNumberValue(
400388
argumentExpression: TSESTree.Expression,
401389
operator: string,
402-
): number {
390+
): number | null {
403391
let argument = getExpressionNumberValue(argumentExpression)
392+
if (argument === null) {
393+
return null
394+
}
404395
switch (operator) {
405396
case '+':
406397
return argument
@@ -410,6 +401,26 @@ function getUnaryExpressionNumberValue(
410401
return ~argument
411402
/* v8 ignore next 2 - Unsure if we can reach it */
412403
default:
413-
return Number.NaN
404+
return null
405+
}
406+
}
407+
408+
function computeOptionType({
409+
isNumericEnum,
410+
options,
411+
}: {
412+
options: Pick<Required<Options[number]>, 'sortByValue' | 'type'>
413+
isNumericEnum: boolean
414+
}): TypeOption {
415+
/**
416+
* If the enum is numeric, and we sort by value, always use the `natural` sort
417+
* type, which will correctly sort them.
418+
*/
419+
if (!isNumericEnum) {
420+
return options.type
421+
}
422+
if (options.sortByValue === 'never') {
423+
return options.type
414424
}
425+
return 'natural'
415426
}

rules/sort-enums/types.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ export type Options = Partial<
2525
*/
2626
customGroups: CustomGroupsOption<SingleCustomGroup>
2727

28+
/**
29+
* Whether to sort enum members by their values instead of names. When true,
30+
* compares enum values; when false, compares enum member names.
31+
*
32+
* @default ifNumericEnum
33+
*/
34+
sortByValue: 'ifNumericEnum' | 'always' | 'never'
35+
2836
/**
2937
* Partition enum members by comment delimiters. Members separated by
3038
* specific comments are sorted independently.
@@ -48,22 +56,6 @@ export type Options = Partial<
4856
* separated by empty lines are sorted independently.
4957
*/
5058
partitionByNewLine: boolean
51-
52-
/**
53-
* Forces numeric enums to be sorted by their value. When true, numeric
54-
* enums are always sorted by value regardless of the main sort type.
55-
*
56-
* @default false
57-
*/
58-
forceNumericSort: boolean
59-
60-
/**
61-
* Whether to sort enum members by their values instead of names. When true,
62-
* compares enum values; when false, compares enum member names.
63-
*
64-
* @default false
65-
*/
66-
sortByValue: boolean
6759
} & CommonOptions
6860
>[]
6961

0 commit comments

Comments
 (0)