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

feat: require-unicode-regexp support v flag #17402

Merged
merged 13 commits into from Jul 28, 2023
37 changes: 36 additions & 1 deletion docs/src/rules/require-unicode-regexp.md
Expand Up @@ -21,7 +21,37 @@ RegExp `u` flag has two effects:

The `u` flag disables the recovering logic Annex B defined. As a result, you can find errors early. This is similar to [the strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode).

Therefore, the `u` flag lets us work better with regular expressions.
The RegExp `v` flag, introduced in ECMAScript 2024, is a superset of the `u` flag, and offers two more features:

1. **Unicode properties of strings**

With the Unicode property escape, you can use properties of strings.

```js
const re = /^\p{RGI_Emoji}$/v;

// Match an emoji that consists of just 1 code point:
re.test('⚽'); // '\u26BD'
// → true ✅

// Match an emoji that consists of multiple code points:
re.test('👨🏾‍⚕️'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F'
// → true ✅
```

2. **Set notation**

It allows for set operations between character classes.

```js
const re = /[\p{White_Space}&&\p{ASCII}]/v;
re.test('\n'); // → true
re.test('\u2028'); // → false
```

Please see <https://github.com/tc39/proposal-regexp-v-flag> and <https://v8.dev/features/regexp-v-flag> for more details.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These links should go in a "Further Reading" section at the bottom of the page instead of here. See the semi rule as an example: https://eslint.org/docs/latest/rules/semi#further-reading


Therefore, the `u` and `v` flags let us work better with regular expressions.

## Rule Details

Expand Down Expand Up @@ -54,6 +84,11 @@ const b = /bbb/giu
const c = new RegExp("ccc", "u")
const d = new RegExp("ddd", "giu")

const e = /aaa/v
const f = /bbb/giv
const g = new RegExp("ccc", "v")
const h = new RegExp("ddd", "giv")

// This rule ignores RegExp calls if the flags could not be evaluated to a static value.
function f(flags) {
return new RegExp("eee", flags)
Expand Down
6 changes: 3 additions & 3 deletions lib/rules/require-unicode-regexp.js
Expand Up @@ -28,7 +28,7 @@ module.exports = {
type: "suggestion",

docs: {
description: "Enforce the use of `u` flag on RegExp",
description: "Enforce the use of `u` or `v` flag on RegExp",
recommended: false,
url: "https://eslint.org/docs/latest/rules/require-unicode-regexp"
},
Expand All @@ -51,7 +51,7 @@ module.exports = {
"Literal[regex]"(node) {
const flags = node.regex.flags || "";

if (!flags.includes("u")) {
if (!flags.includes("u") && !flags.includes("v")) {
context.report({
messageId: "requireUFlag",
node,
Expand Down Expand Up @@ -85,7 +85,7 @@ module.exports = {
const pattern = getStringIfConstant(patternNode, scope);
const flags = getStringIfConstant(flagsNode, scope);

if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) {
if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) {
context.report({
messageId: "requireUFlag",
node: refNode,
Expand Down
15 changes: 14 additions & 1 deletion tests/lib/rules/require-unicode-regexp.js
Expand Up @@ -45,7 +45,20 @@ ruleTester.run("require-unicode-regexp", rule, {
{ code: "const flags = 'u'; new globalThis.RegExp('', flags)", env: { es2020: true } },
{ code: "const flags = 'g'; new globalThis.RegExp('', flags + 'u')", env: { es2020: true } },
{ code: "const flags = 'gimu'; new globalThis.RegExp('foo', flags[3])", env: { es2020: true } },
{ code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } }
{ code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } },

// for v flag
{ code: "/foo/v", parserOptions: { ecmaVersion: 2024 } },
{ code: "/foo/gimvy", parserOptions: { ecmaVersion: 2024 } },
{ code: "RegExp('', 'v')", parserOptions: { ecmaVersion: 2024 } },
{ code: "RegExp('', `v`)", parserOptions: { ecmaVersion: 2024 } },
{ code: "new RegExp('', 'v')", parserOptions: { ecmaVersion: 2024 } },
{ code: "RegExp('', 'gimvy')", parserOptions: { ecmaVersion: 2024 } },
{ code: "RegExp('', `gimvy`)", parserOptions: { ecmaVersion: 2024 } },
{ code: "new RegExp('', 'gimvy')", parserOptions: { ecmaVersion: 2024 } },
{ code: "const flags = 'v'; new RegExp('', flags)", parserOptions: { ecmaVersion: 2024 } },
{ code: "const flags = 'g'; new RegExp('', flags + 'v')", parserOptions: { ecmaVersion: 2024 } },
{ code: "const flags = 'gimv'; new RegExp('foo', flags[3])", parserOptions: { ecmaVersion: 2024 } }
],
invalid: [
{
Expand Down