Skip to content

Commit 4db794e

Browse files
authored
Add regexp/sort-flags rule (#164)
1 parent b0308cb commit 4db794e

File tree

6 files changed

+213
-0
lines changed

6 files changed

+213
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
131131
| [regexp/prefer-t](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-t.html) | enforce using `\t` | :star::wrench: |
132132
| [regexp/prefer-unicode-codepoint-escapes](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-unicode-codepoint-escapes.html) | enforce use of unicode codepoint escapes | :wrench: |
133133
| [regexp/prefer-w](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-w.html) | enforce using `\w` | :star::wrench: |
134+
| [regexp/sort-flags](https://ota-meshi.github.io/eslint-plugin-regexp/rules/sort-flags.html) | require regex flags to be sorted | :wrench: |
134135

135136
<!--RULES_TABLE_END-->
136137
<!--RULES_SECTION_END-->

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
5959
| [regexp/prefer-t](./prefer-t.md) | enforce using `\t` | :star::wrench: |
6060
| [regexp/prefer-unicode-codepoint-escapes](./prefer-unicode-codepoint-escapes.md) | enforce use of unicode codepoint escapes | :wrench: |
6161
| [regexp/prefer-w](./prefer-w.md) | enforce using `\w` | :star::wrench: |
62+
| [regexp/sort-flags](./sort-flags.md) | require regex flags to be sorted | :wrench: |

docs/rules/sort-flags.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "regexp/sort-flags"
5+
description: "require regex flags to be sorted"
6+
---
7+
# regexp/sort-flags
8+
9+
> require regex flags to be sorted
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
13+
14+
## :book: Rule Details
15+
16+
The flags of JavaScript regular expressions should be sorted alphabetically
17+
because the flags of the `.flags` property of `RegExp` objects are always
18+
sorted. Not sorting flags in regex literals misleads readers into thinking that
19+
the order may have some purpose which it doesn't.
20+
21+
<eslint-code-block fix>
22+
23+
```js
24+
/* eslint regexp/sort-flags: "error" */
25+
26+
/* ✓ GOOD */
27+
var foo = /abc/
28+
var foo = /abc/iu
29+
var foo = /abc/gimsuy
30+
31+
/* ✗ BAD */
32+
var foo = /abc/mi
33+
var foo = /abc/us
34+
```
35+
36+
</eslint-code-block>
37+
38+
## :wrench: Options
39+
40+
Nothing.
41+
42+
## :heart: Compatibility
43+
44+
This rule was taken from [eslint-plugin-clean-regex].
45+
This rule is compatible with [clean-regex/sort-flags] rule.
46+
47+
[eslint-plugin-clean-regex]: https://github.com/RunDevelopment/eslint-plugin-clean-regex
48+
[clean-regex/sort-flags]: https://github.com/RunDevelopment/eslint-plugin-clean-regex/blob/master/docs/rules/sort-flags.md
49+
50+
## :mag: Implementation
51+
52+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/sort-flags.ts)
53+
- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/sort-flags.ts)

lib/rules/sort-flags.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type { Literal } from "estree"
2+
import type { RegExpVisitor } from "regexpp/visitor"
3+
import type { RegExpContext } from "../utils"
4+
import { createRule, defineRegexpVisitor } from "../utils"
5+
6+
export default createRule("sort-flags", {
7+
meta: {
8+
docs: {
9+
description: "require regex flags to be sorted",
10+
// TODO Switch to recommended in the major version.
11+
// recommended: true,
12+
recommended: false,
13+
},
14+
fixable: "code",
15+
schema: [],
16+
messages: {
17+
sortFlags:
18+
"The flags '{{flags}}' should in the order '{{sortedFlags}}'.",
19+
},
20+
type: "suggestion", // "problem",
21+
},
22+
create(context) {
23+
/**
24+
* Report
25+
*/
26+
function report(
27+
node: Literal,
28+
flags: string,
29+
sortedFlags: string,
30+
flagsRange: [number, number],
31+
) {
32+
const sourceCode = context.getSourceCode()
33+
context.report({
34+
node,
35+
loc: {
36+
start: sourceCode.getLocFromIndex(flagsRange[0]),
37+
end: sourceCode.getLocFromIndex(flagsRange[1]),
38+
},
39+
messageId: "sortFlags",
40+
data: { flags, sortedFlags },
41+
fix(fixer) {
42+
return fixer.replaceTextRange(flagsRange, sortedFlags)
43+
},
44+
})
45+
}
46+
47+
/**
48+
* Sort regexp flags
49+
*/
50+
function sortFlags(flagsStr: string): string {
51+
return [...flagsStr]
52+
.sort((a, b) => a.codePointAt(0)! - b.codePointAt(0)!)
53+
.join("")
54+
}
55+
56+
/**
57+
* Create visitor
58+
*/
59+
function createVisitor({
60+
regexpNode,
61+
}: RegExpContext): RegExpVisitor.Handlers {
62+
if (regexpNode.type === "Literal") {
63+
const flags = regexpNode.regex.flags
64+
const sortedFlags = sortFlags(flags)
65+
if (flags !== sortedFlags) {
66+
report(regexpNode, flags, sortedFlags, [
67+
regexpNode.range![1] - regexpNode.regex.flags.length,
68+
regexpNode.range![1],
69+
])
70+
}
71+
} else {
72+
const flagsArg = regexpNode.arguments[1]
73+
if (
74+
flagsArg.type === "Literal" &&
75+
typeof flagsArg.value === "string"
76+
) {
77+
const flags = flagsArg.value
78+
const sortedFlags = sortFlags(flags)
79+
if (flags !== sortedFlags) {
80+
report(flagsArg, flags, sortedFlags, [
81+
flagsArg.range![0] + 1,
82+
flagsArg.range![1] - 1,
83+
])
84+
}
85+
}
86+
}
87+
88+
return {} // not visit RegExpNodes
89+
}
90+
91+
return defineRegexpVisitor(context, {
92+
createVisitor,
93+
})
94+
},
95+
})

lib/utils/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import preferStarQuantifier from "../rules/prefer-star-quantifier"
4747
import preferT from "../rules/prefer-t"
4848
import preferUnicodeCodepointEscapes from "../rules/prefer-unicode-codepoint-escapes"
4949
import preferW from "../rules/prefer-w"
50+
import sortFlags from "../rules/sort-flags"
5051

5152
export const rules = [
5253
confusingQuantifier,
@@ -97,4 +98,5 @@ export const rules = [
9798
preferT,
9899
preferUnicodeCodepointEscapes,
99100
preferW,
101+
sortFlags,
100102
] as RuleModule[]

tests/lib/rules/sort-flags.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { RuleTester } from "eslint"
2+
import rule from "../../../lib/rules/sort-flags"
3+
4+
const tester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 2020,
7+
sourceType: "module",
8+
},
9+
})
10+
11+
tester.run("sort-flags", rule as any, {
12+
valid: [
13+
String.raw`/\w/i`,
14+
String.raw`/\w/im`,
15+
String.raw`/\w/gi`,
16+
String.raw`/\w/gimsuy`,
17+
String.raw`new RegExp("\\w", "i")`,
18+
String.raw`new RegExp("\\w", "gi")`,
19+
String.raw`new RegExp("\\w", "gimsuy")`,
20+
String.raw`new RegExp("\\w", "dgimsuy")`,
21+
22+
// ignore
23+
String.raw`
24+
const flags = "yusimg"
25+
new RegExp("\\w", flags)
26+
`,
27+
],
28+
invalid: [
29+
{
30+
code: String.raw`/\w/yusimg`,
31+
output: String.raw`/\w/gimsuy`,
32+
errors: [
33+
{
34+
message: "The flags 'yusimg' should in the order 'gimsuy'.",
35+
column: 5,
36+
},
37+
],
38+
},
39+
{
40+
code: String.raw`new RegExp("\\w", "yusimg")`,
41+
output: String.raw`new RegExp("\\w", "gimsuy")`,
42+
errors: [
43+
{
44+
message: "The flags 'yusimg' should in the order 'gimsuy'.",
45+
column: 20,
46+
},
47+
],
48+
},
49+
{
50+
code: String.raw`new RegExp("\\w", "yusimgd")`,
51+
output: String.raw`new RegExp("\\w", "dgimsuy")`,
52+
errors: [
53+
{
54+
message:
55+
"The flags 'yusimgd' should in the order 'dgimsuy'.",
56+
column: 20,
57+
},
58+
],
59+
},
60+
],
61+
})

0 commit comments

Comments
 (0)