Skip to content

Commit

Permalink
Support RegExp v flag (#2195)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Sep 25, 2023
1 parent 1629ebe commit 28e7498
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 29 deletions.
5 changes: 2 additions & 3 deletions rules/better-regex.js
Expand Up @@ -28,10 +28,9 @@ const create = context => {
}

const {raw: original, regex} = node;

// Regular Expressions with `u` flag are not well handled by `regexp-tree`
// Regular Expressions with `u` and `v` flag are not well handled by `regexp-tree`
// https://github.com/DmitrySoshnikov/regexp-tree/issues/162
if (regex.flags.includes('u')) {
if (regex.flags.includes('u') || regex.flags.includes('v')) {
return;
}

Expand Down
4 changes: 4 additions & 0 deletions rules/prefer-regexp-test.js
Expand Up @@ -77,6 +77,10 @@ const cases = [
const isRegExpNode = node => isRegexLiteral(node) || isNewExpression(node, {name: 'RegExp'});

const isRegExpWithoutGlobalFlag = (node, scope) => {
if (isRegexLiteral(node)) {
return !node.regex.flags.includes('g');
}

const staticResult = getStaticValue(node, scope);

// Don't know if there is `g` flag
Expand Down
5 changes: 3 additions & 2 deletions rules/prefer-string-replace-all.js
Expand Up @@ -17,15 +17,16 @@ function getPatternReplacement(node) {
}

const {pattern, flags} = node.regex;
if (flags.replace('u', '') !== 'g') {
if (flags.replace('u', '').replace('v', '') !== 'g') {
return;
}

let tree;

try {
tree = parseRegExp(pattern, flags, {
unicodePropertyEscape: true,
unicodePropertyEscape: flags.includes('u'),
unicodeSet: flags.includes('v'),
namedGroups: true,
lookbehind: true,
});
Expand Down
1 change: 1 addition & 0 deletions test/better-regex.mjs
Expand Up @@ -40,6 +40,7 @@ test({
// Should not crash ESLint (#446 and #448)
'/\\{\\{verificationUrl\\}\\}/gu',
'/^test-(?<name>[a-zA-Z-\\d]+)$/u',
String.raw`/[\p{Script_Extensions=Greek}--π]/v`,

// Should not suggest wrong regex (#447)
'/(\\s|\\.|@|_|-)/u',
Expand Down
39 changes: 39 additions & 0 deletions test/prefer-regexp-test.mjs
Expand Up @@ -150,6 +150,8 @@ test.snapshot({
let re = new RegExp('foo', 'g');
if(str.match(re));
`,
'!/a/u.exec(foo)',
'!/a/v.exec(foo)',
],
});

Expand All @@ -173,3 +175,40 @@ test.vue({
},
],
});

const supportsUnicodeSets = (() => {
try {
// eslint-disable-next-line prefer-regex-literals -- Can't test with regex literal
return new RegExp('.', 'v').unicodeSets;
} catch {}

return false;
})();
// These cases can be auto-fixed in environments supports `v` flag (eg, Node.js v20),
// But will use suggestions instead in environments doesn't support `v` flag.
test({
valid: [],
invalid: [
{
code: 'const re = /a/v; !re.exec(foo)',
output: 'const re = /a/v; !re.test(foo)',
},
{
code: 'const re = new RegExp("a", "v"); !re.exec(foo)',
output: 'const re = new RegExp("a", "v"); !re.test(foo)',
},
].map(({code, output}) =>
supportsUnicodeSets
? {
code,
output,
errors: 1,
}
: {
code,
errors: [
{suggestions: [{output}]},
],
},
),
});
4 changes: 4 additions & 0 deletions test/prefer-string-replace-all.mjs
Expand Up @@ -77,11 +77,13 @@ test.snapshot({
'foo.replace(/\\W/g, bar)',
'foo.replace(/\\u{61}/g, bar)',
'foo.replace(/\\u{61}/gu, bar)',
'foo.replace(/\\u{61}/gv, bar)',
'foo.replace(/]/g, "bar")',
// Extra flag
'foo.replace(/a/gi, bar)',
'foo.replace(/a/gui, bar)',
'foo.replace(/a/uig, bar)',
'foo.replace(/a/vig, bar)',
// Variables
'const pattern = new RegExp("foo", "g"); foo.replace(pattern, bar)',
'foo.replace(new RegExp("foo", "g"), bar)',
Expand All @@ -99,9 +101,11 @@ test.snapshot({
'foo.replace(/\\u{1f600}/gu, _)',
'foo.replace(/\\n/g, _)',
'foo.replace(/\\u{20}/gu, _)',
'foo.replace(/\\u{20}/gv, _)',

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

// Invalid RegExp #2010
Expand Down
2 changes: 2 additions & 0 deletions test/prefer-string-starts-ends-with.mjs
Expand Up @@ -219,6 +219,8 @@ test.snapshot({
'/a$/.test(a ??= b)',
'/^a/.test(a || b)',
'/^a/.test(a && b)',
'/^a/u.test("string")',
'/^a/v.test("string")',
// eslint-disable-next-line no-template-curly-in-string
'/a$/.test(`${unknown}`)',
'/a$/.test(String(unknown))',
Expand Down
32 changes: 32 additions & 0 deletions test/snapshots/prefer-regexp-test.mjs.md
Expand Up @@ -927,3 +927,35 @@ Generated by [AVA](https://avajs.dev).
1 | let re = new RegExp('foo', 'g');␊
2 | if(re.test(str));␊
`

## Invalid #57
1 | !/a/u.exec(foo)

> Output
`␊
1 | !/a/u.test(foo)␊
`

> Error 1/1
`␊
> 1 | !/a/u.exec(foo)␊
| ^^^^ Prefer \`.test(…)\` over \`.exec(…)\`.␊
`

## Invalid #58
1 | !/a/v.exec(foo)

> Output
`␊
1 | !/a/v.test(foo)␊
`

> Error 1/1
`␊
> 1 | !/a/v.exec(foo)␊
| ^^^^ Prefer \`.test(…)\` over \`.exec(…)\`.␊
`
Binary file modified test/snapshots/prefer-regexp-test.mjs.snap
Binary file not shown.

0 comments on commit 28e7498

Please sign in to comment.