Skip to content

Commit

Permalink
feat: require-unicode-regexp support v flag (#17402)
Browse files Browse the repository at this point in the history
* feat: implement RegExp literal

* test: add tests for RegExp literal

* feat: implement for RegExp constructor

* test: add tests for RegExp constructor

* test: add invalid tests

* feat: implement replacement for u and v

* test: add tests

* docs: update docs for `v` flag

* fix: address review

* Update lib/rules/require-unicode-regexp.js

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* Update docs/src/rules/require-unicode-regexp.md

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* Update tests/lib/rules/require-unicode-regexp.js

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* Update tests/lib/rules/require-unicode-regexp.js

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

---------

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
  • Loading branch information
sosukesuzuki and mdjermanovic committed Jul 28, 2023
1 parent 4d474e3 commit 8a93438
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 5 deletions.
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.

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

0 comments on commit 8a93438

Please sign in to comment.