Skip to content

Commit

Permalink
feat: add sort-enums rule
Browse files Browse the repository at this point in the history
  • Loading branch information
azat-io committed May 25, 2023
1 parent 8a65c69 commit 47167e0
Show file tree
Hide file tree
Showing 7 changed files with 976 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ export default defineConfig({
text: 'sort-array-includes',
link: '/rules/sort-array-includes',
},
{
text: 'sort-enums',
link: '/rules/sort-enums',
},
{
text: 'sort-interfaces',
link: '/rules/sort-interfaces',
Expand Down
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ title: Rules
| Name | Description | 💼 | 🛠 |
| :------------------------------------------------ | :------------------------------------------ | :-- | :-- |
| [sort-array-includes](/rules/sort-array-includes) | Enforce sorted arrays before include method || 🔧 |
| [sort-enums](/rules/sort-enums) | Enforce sorted TypeScript enums members || 🔧 |
| [sort-interfaces](/rules/sort-interfaces) | Enforce sorted interface properties || 🔧 |
| [sort-jsx-props](/rules/sort-jsx-props) | Enforce sorted JSX props || 🔧 |
| [sort-map-elements](/rules/sort-map-elements) | Enforce sorted Map elements || 🔧 |
Expand Down
115 changes: 115 additions & 0 deletions docs/rules/sort-enums.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
title: sort-enums
---

# sort-enums

> Enforce sorted TypeScript enum members.
## 💡 Examples

### Alphabetical and natural sorting

```ts
// Incorrect
enum Hinamizawa {
'Sonozaki Shion' = 'Sonozaki Shion',
'Furude Rika' = 'Furude Rika',
'Sonozaki Mion' = 'Sonozaki Mion',
'Ryūgū Rena' = 'Ryūgū Rena',
}

// Correct
enum Hinamizawa {
'Furude Rika' = 'Furude Rika',
'Ryūgū Rena' = 'Ryūgū Rena',
'Sonozaki Mion' = 'Sonozaki Mion',
'Sonozaki Shion' = 'Sonozaki Shion',
}
```

### Sorting by line length

```ts
// Incorrect
enum Hinamizawa {
'Sonozaki Shion' = 'Sonozaki Shion',
'Furude Rika' = 'Furude Rika',
'Sonozaki Mion' = 'Sonozaki Mion',
'Ryūgū Rena' = 'Ryūgū Rena',
}

// Correct
enum Hinamizawa {
'Sonozaki Shion' = 'Sonozaki Shion',
'Sonozaki Mion' = 'Sonozaki Mion',
'Furude Rika' = 'Furude Rika',
'Ryūgū Rena' = 'Ryūgū Rena',
}
```

## 🔧 Options

### `type`

- `enum` (default: `natural`):
- `natural` - sorting, which is similar to alphabetical order.
- `line-length` - sort by code line length.

### `order`

- `enum` (default: `asc`):
- `asc` - enforce properties to be in ascending order.
- `desc` - enforce properties to be in descending order.

## ⚙️ Usage

### Legacy config

```json
// .eslintrc
{
"rules": {
"perfectionist/sort-enums": [
"error",
{
"type": "line-length",
"order": "desc"
}
]
}
}
```

### Flat config

```js
// eslint.config.js
import perfectionist from 'eslint-plugin-perfectionist'

export default [
{
plugins: {
perfectionist,
},
rules: {
'perfectionist/sort-enums': [
'error',
{
type: 'line-length',
order: 'desc',
},
],
},
},
]
```

## 🚀 Version

Coming soon.

## 📚 Resources

- [Rule source](https://github.com/azat-io/eslint-plugin-perfectionist/blob/main/rules/sort-enums.ts)
- [Test source](https://github.com/azat-io/eslint-plugin-perfectionist/blob/main/test/sort-enums.test.ts)
3 changes: 3 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sortArrayIncludes, { RULE_NAME as sortArrayIncludesName } from '~/rules/sort-array-includes'
import sortEnums, { RULE_NAME as sortEnumsName } from '~/rules/sort-enums'
import sortInterfaces, { RULE_NAME as sortInterfacesName } from '~/rules/sort-interfaces'
import sortJsxProps, { RULE_NAME as sortJsxPropsName } from '~/rules/sort-jsx-props'
import sortMapElements, { RULE_NAME as sortMapElementsName } from '~/rules/sort-map-elements'
Expand Down Expand Up @@ -26,6 +27,7 @@ let createConfigWithOptions = (options: {
[key: string]: RuleDeclaration
} = {
[sortArrayIncludesName]: ['error', { spreadLast: true }],
[sortEnumsName]: ['error'],
[sortInterfacesName]: ['error'],
[sortJsxPropsName]: ['error'],
[sortMapElementsName]: ['error'],
Expand All @@ -49,6 +51,7 @@ export default {
name,
rules: {
[sortArrayIncludesName]: sortArrayIncludes,
[sortEnumsName]: sortEnums,
[sortInterfacesName]: sortInterfaces,
[sortJsxPropsName]: sortJsxProps,
[sortMapElementsName]: sortMapElements,
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ npm install --save-dev eslint-plugin-perfectionist
| Name | Description | 💼 | 🛠 |
| :------------------------------------------------------------------------------------------- | :------------------------------------------ | :-- | :-- |
| [sort-array-includes](https://eslint-plugin-perfectionist.azat.io/rules/sort-array-includes) | Enforce sorted arrays before include method || 🔧 |
| [sort-enums](https://eslint-plugin-perfectionist.azat.io/rules/sort-enums) | Enforce sorted TypeScript enums || 🔧 |
| [sort-interfaces](https://eslint-plugin-perfectionist.azat.io/rules/sort-interfaces) | Enforce sorted interface properties || 🔧 |
| [sort-jsx-props](https://eslint-plugin-perfectionist.azat.io/rules/sort-jsx-props) | Enforce sorted JSX props || 🔧 |
| [sort-map-elements](https://eslint-plugin-perfectionist.azat.io/rules/sort-map-elements) | Enforce sorted Map elements || 🔧 |
Expand Down
102 changes: 102 additions & 0 deletions rules/sort-enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { AST_NODE_TYPES } from '@typescript-eslint/types'

import { createEslintRule } from '~/utils/create-eslint-rule'
import { rangeToDiff } from '~/utils/range-to-diff'
import { SortType, SortOrder } from '~/typings'
import { sortNodes } from '~/utils/sort-nodes'
import type { SortingNode } from '~/typings'
import { complete } from '~/utils/complete'
import { compare } from '~/utils/compare'

type MESSAGE_ID = 'unexpectedEnumsOrder'

type Options = [
Partial<{
order: SortOrder
type: SortType
}>,
]

export const RULE_NAME = 'sort-enums'

export default createEslintRule<Options, MESSAGE_ID>({
name: RULE_NAME,
meta: {
type: 'suggestion',
docs: {
description: 'Enforce sorted TypeScript enums',
recommended: false,
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
type: {
enum: [SortType.alphabetical, SortType.natural, SortType['line-length']],
default: SortType.natural,
},
order: {
enum: [SortOrder.asc, SortOrder.desc],
default: SortOrder.asc,
},
},
additionalProperties: false,
},
],
messages: {
unexpectedEnumsOrder: 'Expected "{{second}}" to come before "{{first}}"',
},
},
defaultOptions: [
{
type: SortType.alphabetical,
order: SortOrder.asc,
},
],
create: context => ({
TSEnumDeclaration: node => {
let options = complete(context.options.at(0), {
type: SortType.alphabetical,
order: SortOrder.asc,
})

if (node.members.length > 1) {
let source = context.getSourceCode().text

let values: SortingNode[] = node.members.map(member => {
let name: string

if (member.id.type === AST_NODE_TYPES.Literal) {
name = `${member.id.value}`
} else {
name = `${source.slice(...member.id.range)}`
}

return {
size: rangeToDiff(member.range),
node: member,
name,
}
})

for (let i = 1; i < values.length; i++) {
let first = values.at(i - 1)!
let second = values.at(i)!

if (compare(first, second, options)) {
context.report({
messageId: 'unexpectedEnumsOrder',
data: {
first: first.name,
second: second.name,
},
node: second.node,
fix: fixer => sortNodes(fixer, source, values, options),
})
}
}
}
},
}),
})

0 comments on commit 47167e0

Please sign in to comment.