Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support RegExp v flag #2195

Merged
merged 7 commits into from Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.