Repository: ota-meshi/eslint-plugin-regexp
Pull request #637 (2023-10-03 09:16:41)

Add support for v flag to `regexp/no-empty-character-class`
related to https://github.com/ota-meshi/eslint-plugin-regexp/issues/545

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 98cff44c45c4cd01319241977fcd81c99a8d9bef

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/ota-meshi/eslint-plugin-regexp/new/no-empty-char-class?filename=.changeset/purple-suns-carry.md&value=---%0A%22eslint-plugin-regexp%22%3A%20patch%0A---%0A%0AAdd%20support%20for%20v%20flag%20to%20%60regexp%2Fno-empty-character-class%60%0A)



Patch:
diff --git a/.changeset/purple-suns-carry.md b/.changeset/purple-suns-carry.md
new file mode 100644
index 0000000..12488cb
--- /dev/null
+++ b/.changeset/purple-suns-carry.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-regexp": minor
+---
+
+Add support for v flag to `regexp/no-empty-character-class`
diff --git a/lib/rules/no-empty-character-class.ts b/lib/rules/no-empty-character-class.ts
index 4ecb959..2ce8637 100644
--- a/lib/rules/no-empty-character-class.ts
+++ b/lib/rules/no-empty-character-class.ts
@@ -35,6 +35,15 @@ export default createRule("no-empty-character-class", {
                         })
                     }
                 },
+                onExpressionCharacterClassEnter(ccNode) {
+                    if (matchesNoCharacters(ccNode, flags)) {
+                        context.report({
+                            node,
+                            loc: getRegexpLocation(ccNode),
+                            messageId: "cannotMatchAny",
+                        })
+                    }
+                },
             }
         }
 


Test patch:
diff --git a/tests/lib/rules/no-empty-capturing-group.ts b/tests/lib/rules/no-empty-capturing-group.ts
index b66b6d8..ab8c395 100644
--- a/tests/lib/rules/no-empty-capturing-group.ts
+++ b/tests/lib/rules/no-empty-capturing-group.ts
@@ -3,13 +3,13 @@ import rule from "../../../lib/rules/no-empty-capturing-group"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
 
 tester.run("no-empty-capturing-group", rule as any, {
-    valid: ["/(a)/", "/a(\\bb)/", "/a(\\b|b)/"],
+    valid: ["/(a)/", "/a(\\bb)/", "/a(\\b|b)/", String.raw`/a([\q{a}])/v`],
     invalid: [
         {
             code: "/a(\\b)/",
@@ -55,5 +55,14 @@ tester.run("no-empty-capturing-group", rule as any, {
             code: "/(\\b\\b|(?:\\B|$))a/",
             errors: ["Unexpected capture empty."],
         },
+        {
+            code: String.raw`/a([\q{}])/v`,
+            errors: [
+                {
+                    message: "Unexpected capture empty.",
+                    line: 1,
+                },
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/no-empty-character-class.ts b/tests/lib/rules/no-empty-character-class.ts
index 8b5e229..5dfa401 100644
--- a/tests/lib/rules/no-empty-character-class.ts
+++ b/tests/lib/rules/no-empty-character-class.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/no-empty-character-class"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -21,6 +21,8 @@ tester.run("no-empty-character-class", rule as any, {
         `/[ ]/`,
         String.raw`/[\s\S]/`,
         String.raw`/[\da-zA-Z_\W]/`,
+        String.raw`/a[[[ab]&&b]]/v`,
+        String.raw`/a[[ab]&&b]/v`,
     ],
     invalid: [
         {
@@ -75,5 +77,33 @@ tester.run("no-empty-character-class", rule as any, {
             code: String.raw`/[^\da-zA-Z_\W]/`,
             errors: ["This character class cannot match any characters."],
         },
+        {
+            code: String.raw`/a[[a&&b]]/v`,
+            errors: [
+                {
+                    message:
+                        "This character class cannot match any characters.",
+                    line: 1,
+                    column: 3,
+                },
+                {
+                    message:
+                        "This character class cannot match any characters.",
+                    line: 1,
+                    column: 4,
+                },
+            ],
+        },
+        {
+            code: String.raw`/a[a&&b]/v`,
+            errors: [
+                {
+                    message:
+                        "This character class cannot match any characters.",
+                    line: 1,
+                    column: 3,
+                },
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/no-invalid-regexp.ts b/tests/lib/rules/no-invalid-regexp.ts
index 27626f4..0b887d0 100644
--- a/tests/lib/rules/no-invalid-regexp.ts
+++ b/tests/lib/rules/no-invalid-regexp.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/no-invalid-regexp"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -63,5 +63,15 @@ tester.run("no-invalid-regexp", rule as any, {
                 },
             ],
         },
+        {
+            code: "new RegExp('[A&&&]', 'v');",
+            errors: [
+                {
+                    message:
+                        "Invalid regular expression: /[A&&&]/v: Invalid character in character class",
+                    column: 16,
+                },
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/no-useless-dollar-replacements.ts b/tests/lib/rules/no-useless-dollar-replacements.ts
index ebff3fa..b63a843 100644
--- a/tests/lib/rules/no-useless-dollar-replacements.ts
+++ b/tests/lib/rules/no-useless-dollar-replacements.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/no-useless-dollar-replacements"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -91,6 +91,10 @@ tester.run("no-useless-dollar-replacements", rule as any, {
         regexp = 'a'
         "abc".replaceAll(regexp, 'foo');
         `,
+        String.raw`
+        const str = 'John Smith';
+        var newStr = str.replace(/(\w+)\s(\w+)/v, '$2, $1');
+        `,
     ],
     invalid: [
         {
@@ -171,5 +175,19 @@ tester.run("no-useless-dollar-replacements", rule as any, {
                 "'$<a>' replacement will insert '$<a>' because named capturing group does not found. Use '$$' if you want to escape '$'.",
             ],
         },
+        {
+            code: String.raw`
+            const str = 'John Smith';
+            var newStr = str.replace(/([[\w]]+)[\s](\w+)/v, '$3, $1 $2');
+            `,
+            errors: [
+                {
+                    message:
+                        "'$3' replacement will insert '$3' because there are less than 3 capturing groups. Use '$$' if you want to escape '$'.",
+                    line: 3,
+                    column: 62,
+                },
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/prefer-escape-replacement-dollar-char.ts b/tests/lib/rules/prefer-escape-replacement-dollar-char.ts
index c3bb752..e5dc182 100644
--- a/tests/lib/rules/prefer-escape-replacement-dollar-char.ts
+++ b/tests/lib/rules/prefer-escape-replacement-dollar-char.ts
@@ -19,6 +19,7 @@ tester.run("prefer-escape-replacement-dollar-char", rule as any, {
         `'abc'.replace(/./, "$\`$'");`,
         `'abc'.replace(/(.)/, '$1');`,
         `'abc'.replace(/(?<foo>.)/, '$<foo>');`,
+        String.raw`'€1,234'.replace(/[\q{€}]/v, '$$'); // "$1,234"`,
     ],
     invalid: [
         {
@@ -67,5 +68,11 @@ tester.run("prefer-escape-replacement-dollar-char", rule as any, {
                 "Unexpected replacement `$` character without escaping. Use `$$` instead.",
             ],
         },
+        {
+            code: String.raw`'€1,234'.replace(/[\q{€}]/v, '$'); // "$1,234"`,
+            errors: [
+                "Unexpected replacement `$` character without escaping. Use `$$` instead.",
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/prefer-regexp-exec.ts b/tests/lib/rules/prefer-regexp-exec.ts
index fdc2cc8..6adb281 100644
--- a/tests/lib/rules/prefer-regexp-exec.ts
+++ b/tests/lib/rules/prefer-regexp-exec.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/prefer-regexp-exec"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -19,6 +19,9 @@ tester.run("prefer-regexp-exec", rule as any, {
         const search = /thing/;
         search.exec(text);
         `,
+        `
+        /thin[[g]]/v.exec('something');
+        `,
     ],
     invalid: [
         {
@@ -72,5 +75,17 @@ tester.run("prefer-regexp-exec", rule as any, {
                 },
             ],
         },
+        {
+            code: `
+            'something'.match(/thin[[g]]/v);
+            `,
+            errors: [
+                {
+                    message: "Use the `RegExp#exec()` method instead.",
+                    line: 2,
+                    column: 13,
+                },
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/prefer-regexp-test.ts b/tests/lib/rules/prefer-regexp-test.ts
index f3d466a..ff90882 100644
--- a/tests/lib/rules/prefer-regexp-test.ts
+++ b/tests/lib/rules/prefer-regexp-test.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/prefer-regexp-test"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -33,6 +33,11 @@ tester.run("prefer-regexp-test", rule as any, {
         const a = pattern.exec(text)
         const b = text.match(pattern)
         `,
+        `
+        const text = 'something';
+        const pattern = /thin[[g]]/v;
+        if (pattern.test(text)) {}
+        `,
     ],
     invalid: [
         {
@@ -186,5 +191,37 @@ tester.run("prefer-regexp-test", rule as any, {
                 "Use the `RegExp#test()` method instead of `String#match`, if you need a boolean.",
             ],
         },
+        {
+            code: `
+            const text = 'something';
+            const pattern = /thin[[g]]/v;
+            if (pattern.exec(text)) {}
+            if (text.match(pattern)) {}
+            `,
+            output: `
+            const text = 'something';
+            const pattern = /thin[[g]]/v;
+            if (pattern.test(text)) {}
+            if (pattern.test(text)) {}
+            `,
+            errors: [
+                {
+                    message:
+                        "Use the `RegExp#test()` method instead of `RegExp#exec`, if you need a boolean.",
+                    line: 4,
+                    column: 25,
+                    endLine: 4,
+                    endColumn: 29,
+                },
+                {
+                    message:
+                        "Use the `RegExp#test()` method instead of `String#match`, if you need a boolean.",
+                    line: 5,
+                    column: 17,
+                    endLine: 5,
+                    endColumn: 36,
+                },
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/prefer-result-array-groups.ts b/tests/lib/rules/prefer-result-array-groups.ts
index b00d2bc..8d62b14 100644
--- a/tests/lib/rules/prefer-result-array-groups.ts
+++ b/tests/lib/rules/prefer-result-array-groups.ts
@@ -4,7 +4,7 @@ import rule from "../../../lib/rules/prefer-result-array-groups"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -51,6 +51,14 @@ tester.run("prefer-result-array-groups", rule as any, {
             // ...
         }
         `,
+        `
+        const regex = /reg[[exp]]/v
+        let match
+        while (match = regex.exec(foo)) {
+            const match = match[0]
+            // ...
+        }
+        `,
         // String.prototype.match()
         `
         const arr = "str".match(/regexp/)
@@ -231,6 +239,32 @@ tester.run("prefer-result-array-groups", rule as any, {
                 },
             ],
         },
+        {
+            code: `
+            const regex = /a(?<foo>[[b]])c/v
+            let match
+            while (match = regex.exec(foo)) {
+                const p1 = match[1]
+                // ...
+            }
+            `,
+            output: `
+            const regex = /a(?<foo>[[b]])c/v
+            let match
+            while (match = regex.exec(foo)) {
+                const p1 = match.groups.foo
+                // ...
+            }
+            `,
+            errors: [
+                {
+                    message:
+                        "Unexpected indexed access for the named capturing group 'foo' from regexp result array.",
+                    line: 5,
+                    column: 28,
+                },
+            ],
+        },
         // String.prototype.match()
         {
             code: `