Skip to content

Commit 2eba483

Browse files
committed
Add eslint-plugin-regexp support
Fixes xojs/xo#532
1 parent 2bb9c6c commit 2eba483

7 files changed

Lines changed: 171 additions & 3 deletions

File tree

eslint.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import eslintConfigXo from './index.js';
22

3-
export default [
3+
const config = [
44
...eslintConfigXo(),
55
{
66
ignores: ['test/fixture.ts', 'index.d.ts'],
@@ -18,3 +18,5 @@ export default [
1818
},
1919
},
2020
];
21+
22+
export default config;

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {pluginsRules} from './source/plugins-rules.js';
1414
import {jsonConfig, json5Config, jsoncConfig} from './source/json.js';
1515
import {getHtmlConfig} from './source/html.js';
1616
import {getMarkdownConfig} from './source/markdown.js';
17+
import {getRegexpConfig} from './source/regexp.js';
1718
import noUseExtendNativeRule from './source/rules/no-use-extend-native.js';
1819

1920
// Dynamically import TypeScript-related packages so that `typescript` is not
@@ -255,6 +256,7 @@ export default function eslintConfigXo({
255256
jsonConfig,
256257
json5Config,
257258
jsoncConfig,
259+
getRegexpConfig({files: [`**/*.{${lintedExtensions.join(',')}}`]}),
258260
getHtmlConfig({space}),
259261
getMarkdownConfig(),
260262
...missingTypeScriptConfig,

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"eslint-plugin-ava": "^16.0.0",
6969
"eslint-plugin-import-x": "^4.16.1",
7070
"eslint-plugin-n": "^17.24.0",
71+
"eslint-plugin-regexp": "^3.1.0",
7172
"eslint-plugin-unicorn": "^63.0.0",
7273
"globals": "^17.4.0",
7374
"typescript-eslint": "^8.57.2"

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Markdown files (`*.md`) are linted automatically using [`@eslint/markdown`](http
8787
- [`typescript-eslint`](https://github.com/typescript-eslint/typescript-eslint)
8888
- [`@html-eslint/eslint-plugin`](https://github.com/yeonjuan/html-eslint)
8989
- [`@eslint/markdown`](https://github.com/eslint/markdown)
90+
- [`eslint-plugin-regexp`](https://github.com/ota-meshi/eslint-plugin-regexp)
9091

9192
## Use the XO CLI instead
9293

source/regexp.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import pluginRegexp from 'eslint-plugin-regexp';
2+
3+
export function getRegexpConfig({files}) {
4+
return {
5+
name: 'xo/regexp',
6+
plugins: {
7+
regexp: pluginRegexp,
8+
},
9+
files,
10+
rules: {
11+
// The plugin provides better versions of these core rules.
12+
'no-invalid-regexp': 'off',
13+
'no-useless-backreference': 'off',
14+
'no-empty-character-class': 'off',
15+
16+
// Possible Errors
17+
'regexp/no-contradiction-with-assertion': 'error',
18+
'regexp/no-control-character': 'error',
19+
'regexp/no-dupe-disjunctions': 'error',
20+
'regexp/no-empty-alternative': 'error',
21+
'regexp/no-empty-capturing-group': 'error',
22+
'regexp/no-empty-character-class': 'error',
23+
'regexp/no-empty-group': 'error',
24+
'regexp/no-empty-lookarounds-assertion': 'error',
25+
'regexp/no-escape-backspace': 'error',
26+
'regexp/no-invalid-regexp': 'error',
27+
'regexp/no-lazy-ends': 'error',
28+
'regexp/no-misleading-capturing-group': 'error',
29+
'regexp/no-misleading-unicode-character': 'error',
30+
'regexp/no-missing-g-flag': 'error',
31+
'regexp/no-optional-assertion': 'error',
32+
'regexp/no-potentially-useless-backreference': 'error',
33+
'regexp/no-super-linear-backtracking': 'error',
34+
'regexp/no-super-linear-move': 'error',
35+
'regexp/no-useless-assertions': 'error',
36+
'regexp/no-useless-backreference': 'error',
37+
'regexp/no-useless-dollar-replacements': 'error',
38+
'regexp/strict': 'error',
39+
40+
// Best Practices
41+
'regexp/confusing-quantifier': 'error',
42+
'regexp/control-character-escape': 'error',
43+
'regexp/negation': 'error',
44+
'regexp/no-dupe-characters-character-class': 'error',
45+
'regexp/no-empty-string-literal': 'error',
46+
'regexp/no-extra-lookaround-assertions': 'error',
47+
'regexp/no-invisible-character': 'error',
48+
'regexp/no-legacy-features': 'error',
49+
'regexp/no-non-standard-flag': 'error',
50+
'regexp/no-obscure-range': 'error',
51+
'regexp/no-octal': 'error',
52+
'regexp/no-standalone-backslash': 'error',
53+
'regexp/no-trivially-nested-assertion': 'error',
54+
'regexp/no-trivially-nested-quantifier': 'error',
55+
'regexp/no-unused-capturing-group': 'error',
56+
'regexp/no-useless-character-class': 'error',
57+
'regexp/no-useless-flag': 'error',
58+
'regexp/no-useless-lazy': 'error',
59+
'regexp/no-useless-quantifier': 'error',
60+
'regexp/no-useless-range': 'error',
61+
'regexp/no-useless-set-operand': 'error',
62+
'regexp/no-useless-string-literal': 'error',
63+
'regexp/no-useless-two-nums-quantifier': 'error',
64+
'regexp/no-zero-quantifier': 'error',
65+
'regexp/optimal-lookaround-quantifier': 'error',
66+
'regexp/optimal-quantifier-concatenation': 'error',
67+
'regexp/prefer-escape-replacement-dollar-char': 'error',
68+
'regexp/prefer-predefined-assertion': 'error',
69+
70+
// I needs an option to only activate when at least 3 components.
71+
// 'regexp/prefer-quantifier': [
72+
// 'off',
73+
// {
74+
// allows: [
75+
// 'www',
76+
// '\\d\\d',
77+
// ],
78+
// },
79+
// ],
80+
81+
'regexp/prefer-range': 'error',
82+
'regexp/prefer-regexp-exec': 'error',
83+
'regexp/prefer-regexp-test': 'error',
84+
'regexp/prefer-set-operation': 'error',
85+
'regexp/require-unicode-regexp': 'off', // Conflicts with `require-unicode-regexp`.
86+
'regexp/require-unicode-sets-regexp': 'off', // Conflicts with `require-unicode-regexp`.
87+
'regexp/simplify-set-operations': 'error',
88+
'regexp/sort-alternatives': 'off', // Too opinionated about ordering.
89+
'regexp/use-ignore-case': 'error',
90+
91+
// Stylistic
92+
'regexp/grapheme-string-literal': 'error',
93+
'regexp/hexadecimal-escape': [
94+
'error',
95+
'always',
96+
],
97+
'regexp/letter-case': [
98+
'error',
99+
{
100+
caseInsensitive: 'lowercase',
101+
unicodeEscape: 'lowercase',
102+
hexadecimalEscape: 'lowercase',
103+
controlEscape: 'uppercase',
104+
},
105+
],
106+
'regexp/match-any': 'error',
107+
'regexp/no-useless-escape': 'error',
108+
'regexp/no-useless-non-capturing-group': 'error',
109+
'regexp/prefer-character-class': 'error',
110+
'regexp/prefer-d': 'error',
111+
'regexp/prefer-lookaround': [
112+
'error',
113+
{
114+
lookbehind: true,
115+
strictTypes: true,
116+
},
117+
],
118+
'regexp/prefer-named-backreference': 'error',
119+
'regexp/prefer-named-capture-group': 'error',
120+
'regexp/prefer-named-replacement': 'error',
121+
'regexp/prefer-plus-quantifier': 'error',
122+
'regexp/prefer-question-quantifier': 'error',
123+
'regexp/prefer-result-array-groups': 'error',
124+
'regexp/prefer-star-quantifier': 'error',
125+
'regexp/prefer-unicode-codepoint-escapes': 'error',
126+
'regexp/prefer-w': 'error',
127+
'regexp/sort-character-class-elements': 'error',
128+
'regexp/sort-flags': 'error',
129+
'regexp/unicode-escape': 'error',
130+
'regexp/unicode-property': [
131+
'error',
132+
{
133+
generalCategory: 'never',
134+
key: 'ignore',
135+
property: {
136+
binary: 'long',
137+
generalCategory: 'long',
138+
script: 'long',
139+
},
140+
},
141+
],
142+
},
143+
};
144+
}

source/typescript-rules.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,15 @@ export const getNamingConventionRule = ({isTsx}) => ({
8282
{
8383
// Interface name should not be prefixed with `I`.
8484
selector: 'interface',
85-
filter: /^(?!I)[A-Z]/v.source,
85+
filter: /^[A-HJ-Z]/v.source,
8686
format: [
8787
'StrictPascalCase',
8888
],
8989
},
9090
{
9191
// Type parameter name should either be `T` or a descriptive name.
9292
selector: 'typeParameter',
93-
filter: /^T$|^[A-Z][a-zA-Z]+$/v.source,
93+
filter: /^T$|^[A-Z][A-Za-z]+$/v.source,
9494
format: [
9595
'StrictPascalCase',
9696
],

test/test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,24 @@ test('html - indent respects space option', async t => {
309309
t.false(hasRule(spaceErrors, '@html-eslint/indent'));
310310
});
311311

312+
test('regexp - flags a non-optimal character class', async t => {
313+
// [0-9] should use \d instead
314+
const errors = await runEslint(
315+
'export const regex = /[0-9]+/;\n',
316+
eslintConfigXo(),
317+
);
318+
t.true(hasRule(errors, 'regexp/prefer-d'));
319+
});
320+
321+
test('regexp - applies to typescript files', async t => {
322+
const errors = await runEslint(
323+
'export const regex = /[0-9]+/;\n',
324+
eslintConfigXo(),
325+
{filePath: 'test/fixture.ts'},
326+
);
327+
t.true(hasRule(errors, 'regexp/prefer-d'));
328+
});
329+
312330
test('exported file globs include html and md', t => {
313331
t.true(allExtensions.includes('html'));
314332
t.true(allExtensions.includes('md'));

0 commit comments

Comments
 (0)