Skip to content

Commit cba8dd9

Browse files
SamVerschuerensindresorhus
authored andcommitted
Add regex-shorthand rule - fixes #112 (#113)
1 parent 81b92a9 commit cba8dd9

File tree

6 files changed

+207
-2
lines changed

6 files changed

+207
-2
lines changed

docs/rules/regex-shorthand.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Enforce the use of regex shorthands to improve readability
2+
3+
4+
## Fail
5+
6+
```js
7+
const regex = /[0-9]/;
8+
const regex = /[^0-9]/;
9+
const regex = /[a-zA-Z0-9_]/;
10+
const regex = /[a-z0-9_]/i;
11+
const regex = /[^a-zA-Z0-9_]/;
12+
const regex = /[^a-z0-9_]/i;
13+
const regex = /[0-9]\.[a-zA-Z0-9_]\-[^0-9]/i;
14+
```
15+
16+
17+
## Pass
18+
19+
```js
20+
const regex = /\d/;
21+
const regex = /\D/;
22+
const regex = /\w/;
23+
const regex = /\w/i;
24+
const regex = /\W/;
25+
const regex = /\W/i;
26+
const regex = /\d\.\w\-\D/i;
27+
```

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ module.exports = {
3030
'unicorn/prefer-type-error': 'error',
3131
'unicorn/no-fn-reference-in-iterator': 'error',
3232
'unicorn/import-index': 'error',
33-
'unicorn/new-for-builtins': 'error'
33+
'unicorn/new-for-builtins': 'error',
34+
'unicorn/regex-shorthand': 'error'
3435
}
3536
}
3637
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"xo"
5050
],
5151
"dependencies": {
52+
"clean-regexp": "^1.0.0",
5253
"eslint-ast-utils": "^1.0.0",
5354
"import-modules": "^1.1.0",
5455
"lodash.camelcase": "^4.1.1",

readme.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ Configure it in `package.json`.
4949
"unicorn/prefer-type-error": "error",
5050
"unicorn/no-fn-reference-in-iterator": "error",
5151
"unicorn/import-index": "error",
52-
"unicorn/new-for-builtins": "error"
52+
"unicorn/new-for-builtins": "error",
53+
"unicorn/regex-shorthand": "error"
5354
}
5455
}
5556
}
@@ -75,6 +76,7 @@ Configure it in `package.json`.
7576
- [no-fn-reference-in-iterator](docs/rules/no-fn-reference-in-iterator.md) - Prevents passing a function reference directly to iterator methods. *(fixable)*
7677
- [import-index](docs/rules/import-index.md) - Enforce importing index files with `.`. *(fixable)*
7778
- [new-for-builtins](docs/rules/new-for-builtins.md) - Enforce the use of `new` for all builtins, except `String`, `Number` and `Boolean`. *(fixable)*
79+
- [regex-shorthand](docs/rules/regex-shorthand.md) - Enforce the use of regex shorthands to improve readability. *(fixable)*
7880

7981

8082
## Recommended config

rules/regex-shorthand.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
const cleanRegexp = require('clean-regexp');
3+
4+
const message = 'Use regex shorthands to improve readability.';
5+
6+
const create = context => {
7+
return {
8+
'Literal[regex]': node => {
9+
const oldPattern = node.regex.pattern;
10+
const flags = node.regex.flags;
11+
12+
const newPattern = cleanRegexp(oldPattern, flags);
13+
14+
if (oldPattern !== newPattern) {
15+
context.report({
16+
node,
17+
message,
18+
fix: fixer => fixer.replaceTextRange(node.range, `/${newPattern}/${flags}`)
19+
});
20+
}
21+
},
22+
'NewExpression[callee.name="RegExp"]': node => {
23+
const args = node.arguments;
24+
25+
if (args.length === 0 || args[0].type !== 'Literal') {
26+
return;
27+
}
28+
29+
const oldPattern = args[0].value;
30+
const flags = args[1] && args[1].type === 'Literal' ? args[1].value : '';
31+
32+
const newPattern = cleanRegexp(oldPattern, flags);
33+
34+
if (oldPattern !== newPattern) {
35+
context.report({
36+
node,
37+
message,
38+
fix: fixer => fixer.replaceTextRange(args[0].range, `'${newPattern}'`)
39+
});
40+
}
41+
}
42+
};
43+
};
44+
45+
module.exports = {
46+
create,
47+
meta: {
48+
fixable: 'code'
49+
}
50+
};

test/regex-shorthand.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import test from 'ava';
2+
import avaRuleTester from 'eslint-ava-rule-tester';
3+
import rule from '../rules/regex-shorthand';
4+
5+
const ruleTester = avaRuleTester(test, {
6+
env: {
7+
es6: true
8+
},
9+
parserOptions: {
10+
sourceType: 'module'
11+
}
12+
});
13+
14+
const error = {
15+
ruleId: 'regex-shorthand',
16+
message: 'Use regex shorthands to improve readability.'
17+
};
18+
19+
ruleTester.run('regex-shorthand', rule, {
20+
valid: [
21+
'const foo = /\\d/',
22+
'const foo = /\\W/i',
23+
'const foo = /\\w/ig',
24+
'const foo = /[a-z]/ig',
25+
'const foo = /\\d*?/ig',
26+
'const foo = /[a-z0-9_]/',
27+
`const foo = new RegExp('\\d')`,
28+
`const foo = new RegExp('\\d', 'ig')`,
29+
`const foo = new RegExp('\\d*?')`,
30+
`const foo = new RegExp('[a-z]', 'i')`
31+
],
32+
invalid: [
33+
{
34+
code: 'const foo = /[0-9]/',
35+
errors: [error],
36+
output: 'const foo = /\\d/'
37+
},
38+
{
39+
code: `const foo = new RegExp('[0-9]')`,
40+
errors: [error],
41+
output: `const foo = new RegExp('\\d')`
42+
},
43+
{
44+
code: 'const foo = /[0-9]/ig',
45+
errors: [error],
46+
output: 'const foo = /\\d/ig'
47+
},
48+
{
49+
code: `const foo = new RegExp('[0-9]', 'ig')`,
50+
errors: [error],
51+
output: `const foo = new RegExp('\\d', 'ig')`
52+
},
53+
{
54+
code: 'const foo = /[^0-9]/',
55+
errors: [error],
56+
output: 'const foo = /\\D/'
57+
},
58+
{
59+
code: 'const foo = /[A-Za-z0-9_]/',
60+
errors: [error],
61+
output: 'const foo = /\\w/'
62+
},
63+
{
64+
code: 'const foo = /[A-Za-z\\d_]/',
65+
errors: [error],
66+
output: 'const foo = /\\w/'
67+
},
68+
{
69+
code: 'const foo = /[a-zA-Z0-9_]/',
70+
errors: [error],
71+
output: 'const foo = /\\w/'
72+
},
73+
{
74+
code: 'const foo = /[a-zA-Z\\d_]/',
75+
errors: [error],
76+
output: 'const foo = /\\w/'
77+
},
78+
{
79+
code: 'const foo = /[A-Za-z0-9_]+[0-9]?\\.[A-Za-z0-9_]*/',
80+
errors: [error],
81+
output: 'const foo = /\\w+\\d?\\.\\w*/'
82+
},
83+
{
84+
code: 'const foo = /[a-z0-9_]/i',
85+
errors: [error],
86+
output: 'const foo = /\\w/i'
87+
},
88+
{
89+
code: 'const foo = /[a-z\\d_]/i',
90+
errors: [error],
91+
output: 'const foo = /\\w/i'
92+
},
93+
{
94+
code: 'const foo = /[^A-Za-z0-9_]/',
95+
errors: [error],
96+
output: 'const foo = /\\W/'
97+
},
98+
{
99+
code: 'const foo = /[^A-Za-z\\d_]/',
100+
errors: [error],
101+
output: 'const foo = /\\W/'
102+
},
103+
{
104+
code: 'const foo = /[^a-z0-9_]/i',
105+
errors: [error],
106+
output: 'const foo = /\\W/i'
107+
},
108+
{
109+
code: 'const foo = /[^a-z\\d_]/i',
110+
errors: [error],
111+
output: 'const foo = /\\W/i'
112+
},
113+
{
114+
code: 'const foo = /[^a-z\\d_]/ig',
115+
errors: [error],
116+
output: 'const foo = /\\W/ig'
117+
},
118+
{
119+
code: 'const foo = /[^\\d_a-z]/ig',
120+
errors: [error],
121+
output: 'const foo = /\\W/ig'
122+
}
123+
]
124+
});

0 commit comments

Comments
 (0)