Skip to content

Commit ed08b95

Browse files
committed
feat: add sort-regexp rule
1 parent 5032f1d commit ed08b95

File tree

9 files changed

+3873
-0
lines changed

9 files changed

+3873
-0
lines changed

docs/content/rules/sort-regexp.mdx

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
---
2+
title: sort-regexp
3+
description: Keep regular expressions predictable by sorting flags, character classes, and alternation branches. This ESLint rule helps you avoid subtle regex bugs and improves readability
4+
shortDescription: Enforce sorted regular expressions
5+
keywords:
6+
- eslint
7+
- regex sorting
8+
- regular expressions
9+
- eslint rule
10+
- code quality
11+
- javascript linting
12+
---
13+
14+
import CodeExample from '../../components/CodeExample.svelte'
15+
import CodeTabs from '../../components/CodeTabs.svelte'
16+
import dedent from 'dedent'
17+
18+
Enforce consistent ordering in regular expressions: flags, character-class elements, and alternation branches inside capture groups or the top-level pattern.
19+
20+
Large patterns become easier to maintain when related branches are grouped and repeatedly used flags or character classes follow the same order. This rule helps you spot missing alternatives, avoid duplicated work, and keeps refactors safer by applying one sorting strategy across your codebase.
21+
22+
The rule is **safe** – it only reorders elements without changing their meaning.
23+
24+
## Try it out
25+
26+
<CodeExample
27+
alphabetical={dedent`
28+
const patterns = {
29+
alternatives: /(apple|banana|orange|pear)/,
30+
alias: /(?<role>administrator|guest|user)/,
31+
characterClass: /[0-9A-Za-fz]/,
32+
flags: /pattern/gimsuy,
33+
}
34+
`}
35+
lineLength={dedent`
36+
const patterns = {
37+
alternatives: /(banana|orange|apple|pear)/,
38+
alias: /(?<role>administrator|guest|user)/,
39+
characterClass: /[0-9A-Za-fz]/,
40+
flags: /pattern/yusmig,
41+
}
42+
`}
43+
initial={dedent`
44+
const patterns = {
45+
alternatives: /(pear|apple|orange|banana)/,
46+
alias: /(?<role>user|administrator|guest)/,
47+
characterClass: /[z0-9a-fA-Z]/,
48+
flags: /pattern/yusmig,
49+
}
50+
`}
51+
client:load
52+
lang="tsx"
53+
/>
54+
55+
## Options
56+
57+
This rule accepts an options object with the following properties:
58+
59+
### type
60+
61+
<sub>default: `'alphabetical'`</sub>
62+
63+
Specifies the sorting strategy applied to matches (flags, character-class elements, and alternation branches).
64+
65+
- `'alphabetical'` — Sort values alphabetically using [`localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare).
66+
- `'natural'` — Sort values in [natural](https://github.com/yobacca/natural-orderby) order (e.g., `(?=2)` comes before `(?=10)`).
67+
- `'line-length'` — Sort values by their textual length.
68+
- `'custom'` — Sort values according to a custom alphabet defined in [`alphabet`](#alphabet).
69+
- `'unsorted'` — Do not reorder values. [`groups`](#groups) and [`customGroups`](#customgroups) are still applied.
70+
71+
### order
72+
73+
<sub>default: `'asc'`</sub>
74+
75+
Specifies the direction of sorting.
76+
77+
- `'asc'` — Ascending order (shorter to longer, A to Z).
78+
- `'desc'` — Descending order (longer to shorter, Z to A).
79+
80+
### fallbackSort
81+
82+
<sub>
83+
type:
84+
```ts
85+
{
86+
type: 'alphabetical' | 'natural' | 'line-length' | 'custom' | 'unsorted'
87+
order?: 'asc' | 'desc'
88+
}
89+
```
90+
</sub>
91+
<sub>default: `{ type: 'unsorted' }`</sub>
92+
93+
Used when the primary [`type`](#type) considers two values equal. For example, rely on alphabetical order when two branches share the same length.
94+
95+
### alphabet
96+
97+
<sub>default: `''`</sub>
98+
99+
Defines the custom alphabet for `'custom'` sorting. Use the [`Alphabet` helper](https://perfectionist.dev/alphabet) to build consistent alphabets quickly.
100+
101+
### locales
102+
103+
<sub>default: `'en-US'`</sub>
104+
105+
Locales passed to `localeCompare` when alphabetical comparisons are required.
106+
107+
### ignoreCase
108+
109+
<sub>default: `true`</sub>
110+
111+
Whether comparisons should be case-insensitive.
112+
113+
- `true` — Treat upper- and lowercase branches as equal (default).
114+
- `false` — Preserve case sensitivity.
115+
116+
### specialCharacters
117+
118+
<sub>default: `'keep'`</sub>
119+
120+
Controls how special characters are handled before comparison.
121+
122+
- `'keep'` — Keep them (default).
123+
- `'trim'` — Trim leading special characters.
124+
- `'remove'` — Remove all special characters.
125+
126+
### ignoreAlias
127+
128+
<sub>default: `false`</sub>
129+
130+
Determines whether named capturing group aliases (e.g., `(?<role>...)`) are considered during comparison.
131+
132+
- `false` — Sort by `alias: pattern` (default).
133+
- `true` — Ignore the alias and only compare the branch content.
134+
135+
This is useful when you only care about the pattern and not how it is aliased.
136+
137+
### customGroups
138+
139+
<sub>
140+
type: `Array<CustomGroupDefinition | CustomGroupAnyOfDefinition>`
141+
</sub>
142+
<sub>default: `[]`</sub>
143+
144+
Define custom groups to control how alternatives are organized before sorting.
145+
146+
```ts
147+
interface CustomGroupDefinition {
148+
groupName: string
149+
selector?: 'alias' | 'pattern'
150+
elementNamePattern?: string | string[] | { pattern: string; flags?: string } | { pattern: string; flags?: string }[]
151+
elementValuePattern?: string | string[] | { pattern: string; flags?: string } | { pattern: string; flags?: string }[]
152+
type?: 'alphabetical' | 'natural' | 'line-length' | 'custom' | 'unsorted'
153+
order?: 'asc' | 'desc'
154+
fallbackSort?: { type: string; order?: 'asc' | 'desc' }
155+
}
156+
157+
interface CustomGroupAnyOfDefinition {
158+
groupName: string
159+
anyOf: Array<{
160+
selector?: 'alias' | 'pattern'
161+
elementNamePattern?: string | string[] | { pattern: string; flags?: string } | { pattern: string; flags?: string }[]
162+
elementValuePattern?: string | string[] | { pattern: string; flags?: string } | { pattern: string; flags?: string }[]
163+
}>
164+
type?: 'alphabetical' | 'natural' | 'line-length' | 'custom' | 'unsorted'
165+
order?: 'asc' | 'desc'
166+
fallbackSort?: { type: string; order?: 'asc' | 'desc' }
167+
}
168+
```
169+
170+
Attributes:
171+
172+
- `groupName` — Identifier referenced in [`groups`](#groups).
173+
- `selector` — Filter by element type:
174+
- `'alias'` — Named capturing group alternatives.
175+
- `'pattern'` — Plain alternatives without aliases.
176+
- `elementNamePattern` — Regex applied to the alias (if present).
177+
- `elementValuePattern` — Regex applied to the branch content.
178+
- `type`, `order`, `fallbackSort` — Override the respective global options for the group.
179+
180+
Custom groups are evaluated in order; the first match wins and overrides predefined groups.
181+
182+
### groups
183+
184+
<sub>
185+
type: `Array<string | string[]>`
186+
</sub>
187+
<sub>default: `[]`</sub>
188+
189+
Controls the order in which groups are evaluated. Use the selectors from [`customGroups`](#customgroups) or custom group names. When an element does not match any provided group, it falls back to the implicit `unknown` group at the end of the list.
190+
191+
You can combine multiple groups by wrapping them in an array; all members will be sorted together using the global or overridden options.
192+
193+
## Usage
194+
195+
<CodeTabs
196+
code={[
197+
{
198+
source: dedent`
199+
// eslint.config.js
200+
import perfectionist from 'eslint-plugin-perfectionist'
201+
202+
export default [
203+
{
204+
plugins: {
205+
perfectionist,
206+
},
207+
rules: {
208+
'perfectionist/sort-regexp': [
209+
'error',
210+
{
211+
type: 'alphabetical',
212+
order: 'asc',
213+
fallbackSort: { type: 'unsorted' },
214+
ignoreCase: true,
215+
ignoreAlias: false,
216+
specialCharacters: 'keep',
217+
locales: 'en-US',
218+
alphabet: '',
219+
groups: [],
220+
customGroups: [],
221+
},
222+
],
223+
},
224+
},
225+
]
226+
`,
227+
name: 'Flat Config',
228+
value: 'flat',
229+
},
230+
{
231+
source: dedent`
232+
// .eslintrc.js
233+
module.exports = {
234+
plugins: [
235+
'perfectionist',
236+
],
237+
rules: {
238+
'perfectionist/sort-regexp': [
239+
'error',
240+
{
241+
type: 'alphabetical',
242+
order: 'asc',
243+
fallbackSort: { type: 'unsorted' },
244+
ignoreCase: true,
245+
ignoreAlias: false,
246+
specialCharacters: 'keep',
247+
locales: 'en-US',
248+
alphabet: '',
249+
groups: [],
250+
customGroups: [],
251+
},
252+
],
253+
},
254+
}
255+
`,
256+
name: 'Legacy Config',
257+
value: 'legacy',
258+
},
259+
]}
260+
type="config-type"
261+
client:load
262+
lang="tsx"
263+
/>
264+
265+
## Version
266+
267+
This rule was introduced in [v5.0.0](https://github.com/azat-io/eslint-plugin-perfectionist/releases/tag/v5.0.0).
268+
269+
## Resources
270+
271+
- [Rule source](https://github.com/azat-io/eslint-plugin-perfectionist/blob/main/rules/sort-regexp.ts)
272+
- [Test source](https://github.com/azat-io/eslint-plugin-perfectionist/blob/main/test/rules/sort-regexp.test.ts)

docs/public/llms.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Key features:
4444
- [sort-named-imports](https://perfectionist.dev/rules/sort-named-imports): Sort named imports
4545
- [sort-object-types](https://perfectionist.dev/rules/sort-object-types): Sort TypeScript object type properties
4646
- [sort-objects](https://perfectionist.dev/rules/sort-objects): Sort object properties
47+
- [sort-regexp](https://perfectionist.dev/rules/sort-regexp): Sort regular expressions
4748
- [sort-sets](https://perfectionist.dev/rules/sort-sets): Sort Set elements
4849
- [sort-switch-case](https://perfectionist.dev/rules/sort-switch-case): Sort switch statement cases
4950
- [sort-union-types](https://perfectionist.dev/rules/sort-union-types): Sort TypeScript union types

index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import sortImports from './rules/sort-imports'
2020
import sortExports from './rules/sort-exports'
2121
import sortObjects from './rules/sort-objects'
2222
import sortModules from './rules/sort-modules'
23+
import sortRegexp from './rules/sort-regexp'
2324
import sortEnums from './rules/sort-enums'
2425
import sortMaps from './rules/sort-maps'
2526
import sortSets from './rules/sort-sets'
@@ -45,6 +46,7 @@ interface PluginConfig {
4546
'sort-imports': Rule.RuleModule
4647
'sort-exports': Rule.RuleModule
4748
'sort-objects': Rule.RuleModule
49+
'sort-regexp': Rule.RuleModule
4850
'sort-enums': Rule.RuleModule
4951
'sort-sets': Rule.RuleModule
5052
'sort-maps': Rule.RuleModule
@@ -92,6 +94,7 @@ export let rules = {
9294
'sort-imports': sortImports,
9395
'sort-exports': sortExports,
9496
'sort-objects': sortObjects,
97+
'sort-regexp': sortRegexp,
9598
'sort-enums': sortEnums,
9699
'sort-sets': sortSets,
97100
'sort-maps': sortMaps,

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"test:unit": "vitest --run --coverage"
5858
},
5959
"dependencies": {
60+
"@eslint-community/regexpp": "^4.12.1",
6061
"@typescript-eslint/utils": "^8.46.2",
6162
"natural-orderby": "^5.0.0"
6263
},

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ module.exports = {
193193
| [sort-named-imports](https://perfectionist.dev/rules/sort-named-imports) | Enforce sorted named imports | 🔧 |
194194
| [sort-object-types](https://perfectionist.dev/rules/sort-object-types) | Enforce sorted object types | 🔧 |
195195
| [sort-objects](https://perfectionist.dev/rules/sort-objects) | Enforce sorted objects | 🔧 |
196+
| [sort-regexp](https://perfectionist.dev/rules/sort-regexp) | Enforce sorted regular expressions | 🔧 |
196197
| [sort-sets](https://perfectionist.dev/rules/sort-sets) | Enforce sorted Set elements | 🔧 |
197198
| [sort-switch-case](https://perfectionist.dev/rules/sort-switch-case) | Enforce sorted switch case statements | 🔧 |
198199
| [sort-union-types](https://perfectionist.dev/rules/sort-union-types) | Enforce sorted union types | 🔧 |

0 commit comments

Comments
 (0)