Skip to content

Commit 28e7498

Browse files
authored
Support RegExp v flag (#2195)
1 parent 1629ebe commit 28e7498

13 files changed

+207
-29
lines changed

Diff for: rules/better-regex.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@ const create = context => {
2828
}
2929

3030
const {raw: original, regex} = node;
31-
32-
// Regular Expressions with `u` flag are not well handled by `regexp-tree`
31+
// Regular Expressions with `u` and `v` flag are not well handled by `regexp-tree`
3332
// https://github.com/DmitrySoshnikov/regexp-tree/issues/162
34-
if (regex.flags.includes('u')) {
33+
if (regex.flags.includes('u') || regex.flags.includes('v')) {
3534
return;
3635
}
3736

Diff for: rules/prefer-regexp-test.js

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ const cases = [
7777
const isRegExpNode = node => isRegexLiteral(node) || isNewExpression(node, {name: 'RegExp'});
7878

7979
const isRegExpWithoutGlobalFlag = (node, scope) => {
80+
if (isRegexLiteral(node)) {
81+
return !node.regex.flags.includes('g');
82+
}
83+
8084
const staticResult = getStaticValue(node, scope);
8185

8286
// Don't know if there is `g` flag

Diff for: rules/prefer-string-replace-all.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ function getPatternReplacement(node) {
1717
}
1818

1919
const {pattern, flags} = node.regex;
20-
if (flags.replace('u', '') !== 'g') {
20+
if (flags.replace('u', '').replace('v', '') !== 'g') {
2121
return;
2222
}
2323

2424
let tree;
2525

2626
try {
2727
tree = parseRegExp(pattern, flags, {
28-
unicodePropertyEscape: true,
28+
unicodePropertyEscape: flags.includes('u'),
29+
unicodeSet: flags.includes('v'),
2930
namedGroups: true,
3031
lookbehind: true,
3132
});

Diff for: test/better-regex.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ test({
4040
// Should not crash ESLint (#446 and #448)
4141
'/\\{\\{verificationUrl\\}\\}/gu',
4242
'/^test-(?<name>[a-zA-Z-\\d]+)$/u',
43+
String.raw`/[\p{Script_Extensions=Greek}--π]/v`,
4344

4445
// Should not suggest wrong regex (#447)
4546
'/(\\s|\\.|@|_|-)/u',

Diff for: test/prefer-regexp-test.mjs

+39
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ test.snapshot({
150150
let re = new RegExp('foo', 'g');
151151
if(str.match(re));
152152
`,
153+
'!/a/u.exec(foo)',
154+
'!/a/v.exec(foo)',
153155
],
154156
});
155157

@@ -173,3 +175,40 @@ test.vue({
173175
},
174176
],
175177
});
178+
179+
const supportsUnicodeSets = (() => {
180+
try {
181+
// eslint-disable-next-line prefer-regex-literals -- Can't test with regex literal
182+
return new RegExp('.', 'v').unicodeSets;
183+
} catch {}
184+
185+
return false;
186+
})();
187+
// These cases can be auto-fixed in environments supports `v` flag (eg, Node.js v20),
188+
// But will use suggestions instead in environments doesn't support `v` flag.
189+
test({
190+
valid: [],
191+
invalid: [
192+
{
193+
code: 'const re = /a/v; !re.exec(foo)',
194+
output: 'const re = /a/v; !re.test(foo)',
195+
},
196+
{
197+
code: 'const re = new RegExp("a", "v"); !re.exec(foo)',
198+
output: 'const re = new RegExp("a", "v"); !re.test(foo)',
199+
},
200+
].map(({code, output}) =>
201+
supportsUnicodeSets
202+
? {
203+
code,
204+
output,
205+
errors: 1,
206+
}
207+
: {
208+
code,
209+
errors: [
210+
{suggestions: [{output}]},
211+
],
212+
},
213+
),
214+
});

Diff for: test/prefer-string-replace-all.mjs

+4
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,13 @@ test.snapshot({
7777
'foo.replace(/\\W/g, bar)',
7878
'foo.replace(/\\u{61}/g, bar)',
7979
'foo.replace(/\\u{61}/gu, bar)',
80+
'foo.replace(/\\u{61}/gv, bar)',
8081
'foo.replace(/]/g, "bar")',
8182
// Extra flag
8283
'foo.replace(/a/gi, bar)',
8384
'foo.replace(/a/gui, bar)',
8485
'foo.replace(/a/uig, bar)',
86+
'foo.replace(/a/vig, bar)',
8587
// Variables
8688
'const pattern = new RegExp("foo", "g"); foo.replace(pattern, bar)',
8789
'foo.replace(new RegExp("foo", "g"), bar)',
@@ -99,9 +101,11 @@ test.snapshot({
99101
'foo.replace(/\\u{1f600}/gu, _)',
100102
'foo.replace(/\\n/g, _)',
101103
'foo.replace(/\\u{20}/gu, _)',
104+
'foo.replace(/\\u{20}/gv, _)',
102105

103106
'foo.replaceAll(/a]/g, _)',
104107
'foo.replaceAll(/\\r\\n\\u{1f600}/gu, _)',
108+
'foo.replaceAll(/\\r\\n\\u{1f600}/gv, _)',
105109
`foo.replaceAll(/a${' very'.repeat(30)} long string/g, _)`,
106110

107111
// Invalid RegExp #2010

Diff for: test/prefer-string-starts-ends-with.mjs

+2
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ test.snapshot({
219219
'/a$/.test(a ??= b)',
220220
'/^a/.test(a || b)',
221221
'/^a/.test(a && b)',
222+
'/^a/u.test("string")',
223+
'/^a/v.test("string")',
222224
// eslint-disable-next-line no-template-curly-in-string
223225
'/a$/.test(`${unknown}`)',
224226
'/a$/.test(String(unknown))',

Diff for: test/snapshots/prefer-regexp-test.mjs.md

+32
Original file line numberDiff line numberDiff line change
@@ -927,3 +927,35 @@ Generated by [AVA](https://avajs.dev).
927927
1 | let re = new RegExp('foo', 'g');␊
928928
2 | if(re.test(str));␊
929929
`
930+
931+
## Invalid #57
932+
1 | !/a/u.exec(foo)
933+
934+
> Output
935+
936+
`␊
937+
1 | !/a/u.test(foo)␊
938+
`
939+
940+
> Error 1/1
941+
942+
`␊
943+
> 1 | !/a/u.exec(foo)␊
944+
| ^^^^ Prefer \`.test(…)\` over \`.exec(…)\`.␊
945+
`
946+
947+
## Invalid #58
948+
1 | !/a/v.exec(foo)
949+
950+
> Output
951+
952+
`␊
953+
1 | !/a/v.test(foo)␊
954+
`
955+
956+
> Error 1/1
957+
958+
`␊
959+
> 1 | !/a/v.exec(foo)␊
960+
| ^^^^ Prefer \`.test(…)\` over \`.exec(…)\`.␊
961+
`

Diff for: test/snapshots/prefer-regexp-test.mjs.snap

80 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)