Repository: ota-meshi/eslint-plugin-regexp
Pull request #586 (2023-09-09 08:30:18)

Improve `regexp/require-unicode-regexp` rule to allow patterns with v flag
related to https://github.com/ota-meshi/eslint-plugin-regexp/issues/545

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

Latest commit: 50423cc19d2a11ca2ea40179daa4e3559e2c303a

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/require-unicode-regexp?filename=.changeset/loud-flowers-search.md&value=---%0A%22eslint-plugin-regexp%22%3A%20patch%0A---%0A%0AImprove%20%60regexp%2Frequire-unicode-regexp%60%20rule%20to%20allow%20patterns%20with%20v%20flag%0A)



Patch:
diff --git a/.changeset/curvy-shoes-kiss.md b/.changeset/curvy-shoes-kiss.md
new file mode 100644
index 0000000..3a14b6f
--- /dev/null
+++ b/.changeset/curvy-shoes-kiss.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-regexp": minor
+---
+
+Add support for v flag to `regexp/prefer-d` rule
diff --git a/.changeset/heavy-rings-reply.md b/.changeset/heavy-rings-reply.md
new file mode 100644
index 0000000..621402b
--- /dev/null
+++ b/.changeset/heavy-rings-reply.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-regexp": minor
+---
+
+Add support for v flag to `regexp/negation` rule
diff --git a/.changeset/loud-flowers-search.md b/.changeset/loud-flowers-search.md
new file mode 100644
index 0000000..745d7b8
--- /dev/null
+++ b/.changeset/loud-flowers-search.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-regexp": minor
+---
+
+Improve `regexp/require-unicode-regexp` rule to allow patterns with v flag
diff --git a/.changeset/pre.json b/.changeset/pre.json
index aa3970a..c9df192 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -5,7 +5,9 @@
     "eslint-plugin-regexp": "1.15.0"
   },
   "changesets": [
+    "brown-dragons-dance",
     "calm-onions-yell",
+    "clever-jobs-clap",
     "cyan-waves-notice",
     "hungry-spoons-mix",
     "khaki-colts-tell",
diff --git a/.changeset/six-squids-look.md b/.changeset/six-squids-look.md
new file mode 100644
index 0000000..cbb4599
--- /dev/null
+++ b/.changeset/six-squids-look.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-regexp": minor
+---
+
+Add support for v flag to `regexp/no-useless-escape` rule
diff --git a/.changeset/smart-chefs-poke.md b/.changeset/smart-chefs-poke.md
new file mode 100644
index 0000000..aeabb6d
--- /dev/null
+++ b/.changeset/smart-chefs-poke.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-regexp": minor
+---
+
+Add support for v flag to `regexp/no-misleading-unicode-character` rule
diff --git a/.changeset/tasty-penguins-cheat.md b/.changeset/tasty-penguins-cheat.md
new file mode 100644
index 0000000..cc52915
--- /dev/null
+++ b/.changeset/tasty-penguins-cheat.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-regexp": minor
+---
+
+`prefer-w`: Add support for `v` flag
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5a456a..aac4fcb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # eslint-plugin-regexp
 
+## 2.0.0-next.4
+
+### Minor Changes
+
+-   Add support for v flag to `regexp/no-non-standard-flag` rule ([#596](https://github.com/ota-meshi/eslint-plugin-regexp/pull/596))
+
+-   Improve `regexp/strict` rule to ignore patterns with v flag ([#591](https://github.com/ota-meshi/eslint-plugin-regexp/pull/591))
+
 ## 2.0.0-next.3
 
 ### Minor Changes
diff --git a/docs/rules/prefer-d.md b/docs/rules/prefer-d.md
index 6e0f003..51f5cd2 100644
--- a/docs/rules/prefer-d.md
+++ b/docs/rules/prefer-d.md
@@ -53,6 +53,7 @@ var foo = /[^0-9]/;
 This option control how character class element equivalent to `\d` will be treated.
 
 *Note:* This option does not affect character classes equivalent to `\d`. E.g. `[\d]`, `[0-9]`, and `[0123456789]` are unaffected.
+It also does not affect expression non-nested operands equivalent to `\d`. E.g. `[\d&&x]`, and `[\d--x]` are unaffected.
 
 - `insideCharacterClass: "d"` (*default*)
 
@@ -88,6 +89,11 @@ This option control how character class element equivalent to `\d` will be treat
   /* ✗ BAD */
   var foo = /[\da-z]/;
   var foo = /[0-9]/;
+
+  /* Ignore */
+  var foo = /[\d--0]/v;
+  /* ✗ BAD */
+  var foo = /[[\da-z]--0]/v;
   ```
 
   </eslint-code-block>
@@ -107,6 +113,7 @@ This option control how character class element equivalent to `\d` will be treat
 
   /* ✗ BAD */
   var foo = /[0-9]/;
+  var foo = /[[0-9a-z]--0]/v;
   ```
 
   </eslint-code-block>
diff --git a/lib/rules/negation.ts b/lib/rules/negation.ts
index 2cd3aa5..bea46c5 100644
--- a/lib/rules/negation.ts
+++ b/lib/rules/negation.ts
@@ -1,4 +1,4 @@
-import { toCharSet } from "regexp-ast-analysis"
+import { toCharSet, toUnicodeSet } from "regexp-ast-analysis"
 import type {
     EscapeCharacterSet,
     UnicodePropertyCharacterSet,
@@ -39,8 +39,18 @@ export default createRule("negation", {
                     if (element.type !== "CharacterSet") {
                         return
                     }
+                    if (element.kind === "property" && element.strings) {
+                        // Unicode property escape with property of strings.
+                        // Actually the pattern passing through this branch is an invalid pattern,
+                        // but it has to be checked because of the type guards.
+                        return
+                    }
 
-                    if (flags.ignoreCase && element.kind === "property") {
+                    if (
+                        flags.ignoreCase &&
+                        !flags.unicodeSets &&
+                        element.kind === "property"
+                    ) {
                         // The ignore case canonicalization affects negated
                         // Unicode property escapes in a weird way. In short,
                         // /\p{Foo}/i is not the same as /[^\P{Foo}]/i if
@@ -49,14 +59,9 @@ export default createRule("negation", {
                         // Note: This only affects Unicode property escapes.
                         // All other character sets are either case-invariant
                         // (/./, /\s/, /\d/) or inconsistent (/\w/).
-
-                        // FIXME: TS Error
-                        // @ts-expect-error -- FIXME
-                        const ccSet = toCharSet(ccNode, flags)
+                        const ccSet = toUnicodeSet(ccNode, flags)
 
                         const negatedElementSet = toCharSet(
-                            // FIXME: TS Error
-                            // @ts-expect-error -- FIXME
                             {
                                 ...element,
                                 negate: !element.negate,
diff --git a/lib/rules/no-misleading-unicode-character.ts b/lib/rules/no-misleading-unicode-character.ts
index 136b659..80c5f57 100644
--- a/lib/rules/no-misleading-unicode-character.ts
+++ b/lib/rules/no-misleading-unicode-character.ts
@@ -35,7 +35,11 @@ function getProblem(grapheme: string, flags: ReadonlyFlags): Problem | null {
         (grapheme.length === 2 && !startsWithSurrogate(grapheme))
     ) {
         return "Multi"
-    } else if (!flags.unicode && startsWithSurrogate(grapheme)) {
+    } else if (
+        !flags.unicode &&
+        !flags.unicodeSets &&
+        startsWithSurrogate(grapheme)
+    ) {
         return "Surrogate"
     }
     return null
@@ -84,6 +88,13 @@ function getGraphemeProblems(
 ): GraphemeProblem[] {
     let offset = cc.negate ? 2 : 1
 
+    const ignoreElements = cc.elements.filter(
+        (element) =>
+            element.type === "CharacterClass" || // Nesting CharacterClass
+            element.type === "ExpressionCharacterClass" || // Nesting ExpressionCharacterClass
+            element.type === "ClassStringDisjunction",
+    )
+
     const graphemes = splitter.splitGraphemes(cc.raw.slice(offset, -1))
     const problems: GraphemeProblem[] = []
 
@@ -93,6 +104,14 @@ function getGraphemeProblems(
             const start = offset + cc.start
             const end = start + grapheme.length
 
+            if (
+                ignoreElements.some(
+                    (ignore) => ignore.start <= start && end <= ignore.end,
+                )
+            ) {
+                continue
+            }
+
             problems.push({
                 grapheme,
                 problem,
@@ -113,6 +132,7 @@ function getGraphemeProblems(
 function getGraphemeProblemsFix(
     problems: readonly GraphemeProblem[],
     cc: CharacterClass,
+    flags: ReadonlyFlags,
 ): string | null {
     if (cc.negate) {
         // we can't fix a negated character class
@@ -131,10 +151,7 @@ function getGraphemeProblemsFix(
     }
 
     // The prefix of graphemes
-    const prefix = problems
-        .map((p) => p.grapheme)
-        .sort((a, b) => b.length - a.length)
-        .join("|")
+    const prefixGraphemes = problems.map((p) => p.grapheme)
 
     // The rest of the character class
     let ccRaw = cc.raw
@@ -142,10 +159,17 @@ function getGraphemeProblemsFix(
         const { start, end } = problems[i]
         ccRaw = ccRaw.slice(0, start - cc.start) + ccRaw.slice(end - cc.start)
     }
+
+    if (flags.unicodeSets) {
+        const prefix = prefixGraphemes.join("|")
+        return `[\\q{${prefix}}${ccRaw.slice(1, -1)}]`
+    }
+
     if (ccRaw.startsWith("[^")) {
         ccRaw = `[\\${ccRaw.slice(1)}`
     }
 
+    const prefix = prefixGraphemes.sort((a, b) => b.length - a.length).join("|")
     let fix = prefix
     let singleAlternative = problems.length === 1
     if (ccRaw !== "[]") {
@@ -238,8 +262,7 @@ export default createRule("no-misleading-unicode-character", {
                         start: problems[0].start,
                         end: problems[problems.length - 1].end,
                     }
-
-                    const fix = getGraphemeProblemsFix(problems, ccNode)
+                    const fix = getGraphemeProblemsFix(problems, ccNode, flags)
 
                     const graphemes = problems
                         .map((p) => mention(p.grapheme))
diff --git a/lib/rules/no-useless-escape.ts b/lib/rules/no-useless-escape.ts
index 779c99f..b47a217 100644
--- a/lib/rules/no-useless-escape.ts
+++ b/lib/rules/no-useless-escape.ts
@@ -1,5 +1,9 @@
 import type { RegExpVisitor } from "@eslint-community/regexpp/visitor"
-import type { Character } from "@eslint-community/regexpp/ast"
+import type {
+    Character,
+    CharacterClass,
+    ExpressionCharacterClass,
+} from "@eslint-community/regexpp/ast"
 import type { RegExpContext } from "../utils"
 import {
     createRule,
@@ -21,6 +25,19 @@ import {
     CP_PIPE,
     CP_MINUS,
     canUnwrapped,
+    CP_HASH,
+    CP_PERCENT,
+    CP_BAN,
+    CP_AMP,
+    CP_COMMA,
+    CP_COLON,
+    CP_SEMI,
+    CP_LT,
+    CP_EQ,
+    CP_GT,
+    CP_AT,
+    CP_TILDE,
+    CP_BACKTICK,
 } from "../utils"
 
 const REGEX_CHAR_CLASS_ESCAPES = new Set([
@@ -28,6 +45,18 @@ const REGEX_CHAR_CLASS_ESCAPES = new Set([
     CP_CLOSING_BRACKET, // ]
     CP_MINUS, // -
 ])
+const REGEX_CLASS_SET_CHAR_CLASS_ESCAPE = new Set([
+    CP_BACK_SLASH, // \\
+    CP_SLASH, // /
+    CP_OPENING_BRACKET, // [
+    CP_CLOSING_BRACKET, // ]
+    CP_OPENING_BRACE, // {
+    CP_CLOSING_BRACE, // }
+    CP_PIPE, // |
+    CP_OPENING_PAREN, // (
+    CP_CLOSING_PAREN, // )
+    CP_MINUS, // -,
+])
 const REGEX_ESCAPES = new Set([
     CP_BACK_SLASH, // \\
     CP_SLASH, // /
@@ -47,6 +76,33 @@ const REGEX_ESCAPES = new Set([
 ])
 
 const POTENTIAL_ESCAPE_SEQUENCE = new Set("uxkpP")
+const POTENTIAL_ESCAPE_SEQUENCE_FOR_CHAR_CLASS = new Set([
+    ...POTENTIAL_ESCAPE_SEQUENCE,
+    "q",
+])
+// A single character set of ClassSetReservedDoublePunctuator.
+// && !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~ are ClassSetReservedDoublePunctuator
+const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR = new Set([
+    CP_BAN, // !
+    CP_HASH, // #
+    CP_DOLLAR, // $
+    CP_PERCENT, // %
+    CP_AMP, // &
+    CP_STAR, // *
+    CP_PLUS, // +
+    CP_COMMA, // ,
+    CP_DOT, // .
+    CP_COLON, // :
+    CP_SEMI, // ;
+    CP_LT, // <
+    CP_EQ, // =
+    CP_GT, // >
+    CP_QUESTION, // ?
+    CP_AT, // @
+    CP_CARET, // ^
+    CP_BACKTICK, // `
+    CP_TILDE, // ~
+])
 
 export default createRule("no-useless-escape", {
     meta: {
@@ -65,6 +121,8 @@ export default createRule("no-useless-escape", {
     create(context) {
         function createVisitor({
             node,
+            flags,
+            pattern,
             getRegexpLocation,
             fixReplaceNode,
         }: RegExpContext): RegExpVisitor.Handlers {
@@ -85,37 +143,85 @@ export default createRule("no-useless-escape", {
                 })
             }
 
-            let inCharacterClass = false
+            const characterClassStack: (
+                | CharacterClass
+                | ExpressionCharacterClass
+            )[] = []
             return {
-                onCharacterClassEnter() {
-                    inCharacterClass = true
-                },
-                onCharacterClassLeave() {
-                    inCharacterClass = false
-                },
+                onCharacterClassEnter: (characterClassNode) =>
+                    characterClassStack.unshift(characterClassNode),
+                onCharacterClassLeave: () => characterClassStack.shift(),
+                onExpressionCharacterClassEnter: (characterClassNode) =>
+                    characterClassStack.unshift(characterClassNode),
+                onExpressionCharacterClassLeave: () =>
+                    characterClassStack.shift(),
                 onCharacterEnter(cNode) {
                     if (cNode.raw.startsWith("\\")) {
                         // escapes
                         const char = cNode.raw.slice(1)
-                        if (char === String.fromCodePoint(cNode.value)) {
-                            const allowedEscapes = inCharacterClass
-                                ? REGEX_CHAR_CLASS_ESCAPES
-                                : REGEX_ESCAPES
+                        const escapedChar = String.fromCodePoint(cNode.value)
+                        if (char === escapedChar) {
+                            let allowedEscapes: Set<number>
+                            if (characterClassStack.length) {
+                                allowedEscapes = flags.unicodeSets
+                                    ? REGEX_CLASS_SET_CHAR_CLASS_ESCAPE
+                                    : REGEX_CHAR_CLASS_ESCAPES
+                            } else {
+                                allowedEscapes = REGEX_ESCAPES
+                            }
                             if (allowedEscapes.has(cNode.value)) {
                                 return
                             }
-                            if (inCharacterClass && cNode.value === CP_CARET) {
-                                const target =
-                                    cNode.parent.type === "CharacterClassRange"
-                                        ? cNode.parent
-                                        : cNode
-                                const parent = target.parent
-                                if (parent.type === "CharacterClass") {
-                                    if (parent.elements.indexOf(target) === 0) {
+                            if (characterClassStack.length) {
+                                const characterClassNode =
+                                    characterClassStack[0]
+                                if (cNode.value === CP_CARET) {
+                                    if (
+                                        characterClassNode.start + 1 ===
+                                        cNode.start
+                                    ) {
                                         // e.g. /[\^]/
                                         return
                                     }
                                 }
+                                if (flags.unicodeSets) {
+                                    if (
+                                        REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(
+                                            cNode.value,
+                                        )
+                                    ) {
+                                        if (
+                                            pattern[cNode.end] === escapedChar
+                                        ) {
+                                            // Escaping is valid if it is a ClassSetReservedDoublePunctuator.
+                                            return
+                                        }
+                                        const prevIndex = cNode.start - 1
+                                        if (
+                                            pattern[prevIndex] === escapedChar
+                                        ) {
+                                            if (escapedChar !== "^") {
+                                                // e.g. [&\&]
+                                                //        ^ // If it's the second character, it's a valid escape.
+                                                return
+                                            }
+                                            const elementStartIndex =
+                                                characterClassNode.start +
+                                                1 + // opening bracket(`[`)
+                                                (characterClassNode.negate
+                                                    ? 1 // `negate` caret(`^`)
+                                                    : 0)
+                                            if (
+                                                elementStartIndex <= prevIndex
+                                            ) {
+                                                // [^^\^], [_^\^]
+                                                //    ^       ^ // If it's the second caret(`^`) character, it's a valid escape.
+                                                // But [^\^] is unnecessary escape.
+                                                return
+                                            }
+                                        }
+                                    }
+                                }
                             }
                             if (!canUnwrapped(cNode, char)) {
                                 return
@@ -124,7 +230,11 @@ export default createRule("no-useless-escape", {
                                 cNode,
                                 0,
                                 char,
-                                !POTENTIAL_ESCAPE_SEQUENCE.has(char),
+                                !(
+                                    characterClassStack.length
+                                        ? POTENTIAL_ESCAPE_SEQUENCE_FOR_CHAR_CLASS
+                                        : POTENTIAL_ESCAPE_SEQUENCE
+                                ).has(char),
                             )
                         }
                     }
diff --git a/lib/rules/prefer-d.ts b/lib/rules/prefer-d.ts
index 464e896..23a9ed5 100644
--- a/lib/rules/prefer-d.ts
+++ b/lib/rules/prefer-d.ts
@@ -6,12 +6,14 @@ import {
     CP_DIGIT_ZERO,
     CP_DIGIT_NINE,
 } from "../utils"
-import { Chars, toCharSet } from "regexp-ast-analysis"
+import { Chars, toUnicodeSet } from "regexp-ast-analysis"
 import { mention } from "../utils/mention"
 import type {
+    CharacterClass,
     CharacterClassElement,
     CharacterClassRange,
     EscapeCharacterSet,
+    ExpressionCharacterClass,
 } from "@eslint-community/regexpp/ast"
 
 /**
@@ -66,61 +68,66 @@ export default createRule("prefer-d", {
             getRegexpLocation,
             fixReplaceNode,
         }: RegExpContext): RegExpVisitor.Handlers {
-            return {
-                onCharacterClassEnter(ccNode) {
-                    // FIXME: TS Error
-                    // @ts-expect-error -- FIXME
-                    const charSet = toCharSet(ccNode, flags)
+            function verifyCharacterClass(
+                ccNode: CharacterClass | ExpressionCharacterClass,
+            ) {
+                const charSet = toUnicodeSet(ccNode, flags)
 
-                    let predefined: string | undefined = undefined
-                    if (charSet.equals(Chars.digit(flags))) {
-                        predefined = "\\d"
-                    } else if (charSet.equals(Chars.digit(flags).negate())) {
-                        predefined = "\\D"
-                    }
+                let predefined: string | undefined = undefined
+                if (charSet.equals(Chars.digit(flags))) {
+                    predefined = "\\d"
+                } else if (charSet.equals(Chars.digit(flags).negate())) {
+                    predefined = "\\D"
+                }
+
+                if (predefined) {
+                    context.report({
+                        node,
+                        loc: getRegexpLocation(ccNode),
+                        messageId: "unexpected",
+                        data: {
+                            type: "character class",
+                            expr: mention(ccNode),
+                            instead: predefined,
+                        },
+                        fix: fixReplaceNode(ccNode, predefined),
+                    })
+                    return
+                }
 
-                    if (predefined) {
+                if (
+                    insideCharacterClass === "ignore" ||
+                    ccNode.type !== "CharacterClass"
+                ) {
+                    return
+                }
+
+                const expected = insideCharacterClass === "d" ? "\\d" : "0-9"
+
+                // check the elements in this character class
+                for (const e of ccNode.elements) {
+                    if (isDigits(e) && e.raw !== expected) {
                         context.report({
                             node,
-                            loc: getRegexpLocation(ccNode),
+                            loc: getRegexpLocation(e),
                             messageId: "unexpected",
                             data: {
-                                type: "character class",
-                                expr: mention(ccNode),
-                                instead: predefined,
+                                type:
+                                    e.type === "CharacterSet"
+                                        ? "character set"
+                                        : "character class range",
+                                expr: mention(e),
+                                instead: expected,
                             },
-                            fix: fixReplaceNode(ccNode, predefined),
+                            fix: fixReplaceNode(e, expected),
                         })
-                        return
-                    }
-
-                    if (insideCharacterClass === "ignore") {
-                        return
                     }
+                }
+            }
 
-                    const expected =
-                        insideCharacterClass === "d" ? "\\d" : "0-9"
-
-                    // check the elements in this character class
-                    for (const e of ccNode.elements) {
-                        if (isDigits(e) && e.raw !== expected) {
-                            context.report({
-                                node,
-                                loc: getRegexpLocation(e),
-                                messageId: "unexpected",
-                                data: {
-                                    type:
-                                        e.type === "CharacterSet"
-                                            ? "character set"
-                                            : "character class range",
-                                    expr: mention(e),
-                                    instead: expected,
-                                },
-                                fix: fixReplaceNode(e, expected),
-                            })
-                        }
-                    }
-                },
+            return {
+                onCharacterClassEnter: verifyCharacterClass,
+                onExpressionCharacterClassEnter: verifyCharacterClass,
             }
         }
 
diff --git a/lib/rules/prefer-w.ts b/lib/rules/prefer-w.ts
index 6db0c70..61097e3 100644
--- a/lib/rules/prefer-w.ts
+++ b/lib/rules/prefer-w.ts
@@ -16,7 +16,7 @@ import {
     CP_DIGIT_NINE,
     CP_LOW_LINE,
 } from "../utils"
-import { Chars, toCharSet } from "regexp-ast-analysis"
+import { Chars, toUnicodeSet } from "regexp-ast-analysis"
 import { mention } from "../utils/mention"
 
 function isSmallLetterRange(node: CharacterClassElement) {
@@ -40,7 +40,7 @@ function isDigitRangeOrSet(node: CharacterClassElement) {
         (node.type === "CharacterClassRange" &&
             node.min.value === CP_DIGIT_ZERO &&
             node.max.value === CP_DIGIT_NINE) ||
-        (node.type === "CharacterSet" && node.kind === "digit")
+        (node.type === "CharacterSet" && node.kind === "digit" && !node.negate)
     )
 }
 
@@ -73,14 +73,13 @@ export default createRule("prefer-w", {
         }: RegExpContext): RegExpVisitor.Handlers {
             return {
                 onCharacterClassEnter(ccNode: CharacterClass) {
-                    // FIXME: TS Error
-                    // @ts-expect-error -- FIXME
-                    const charSet = toCharSet(ccNode, flags)
+                    const charSet = toUnicodeSet(ccNode, flags)
 
                     let predefined: string | undefined = undefined
-                    if (charSet.equals(Chars.word(flags))) {
+                    const word = Chars.word(flags)
+                    if (charSet.equals(word)) {
                         predefined = "\\w"
-                    } else if (charSet.equals(Chars.word(flags).negate())) {
+                    } else if (charSet.equals(word.negate())) {
                         predefined = "\\W"
                     }
 
diff --git a/lib/rules/require-unicode-regexp.ts b/lib/rules/require-unicode-regexp.ts
index 5f7e7db..18505a5 100644
--- a/lib/rules/require-unicode-regexp.ts
+++ b/lib/rules/require-unicode-regexp.ts
@@ -15,8 +15,8 @@ import type { ReadonlyFlags } from "regexp-ast-analysis"
 import {
     hasSomeDescendant,
     toCache,
-    toCharSet,
     getFirstCharAfter,
+    toUnicodeSet,
 } from "regexp-ast-analysis"
 
 const UTF16_MAX = 0xffff
@@ -146,9 +146,7 @@ function isCompatibleCharLike(
     flags: ReadonlyFlags,
     uFlags: ReadonlyFlags,
 ): boolean {
-    // FIXME: TS Error
-    // @ts-expect-error -- FIXME
-    const cs = toCharSet(char, flags)
+    const cs = toUnicodeSet(char, flags)
     if (!cs.isDisjointWith(SURROGATES)) {
         // If the character (class/set) contains high or low
         // surrogates, then we won't be able to guarantee that the
@@ -156,12 +154,10 @@ function isCompatibleCharLike(
         return false
     }
 
-    // FIXME: TS Error
-    // @ts-expect-error -- FIXME
-    const uCs = toCharSet(char, uFlags)
+    const uCs = toUnicodeSet(char, uFlags)
 
     // Compare the ranges.
-    return rangeEqual(cs.ranges, uCs.ranges)
+    return rangeEqual(cs.chars.ranges, uCs.chars.ranges)
 }
 
 /**
@@ -203,23 +199,19 @@ function isCompatibleQuantifier(
         return undefined
     }
 
-    // FIXME: TS Error
-    // @ts-expect-error -- FIXME
-    const cs = toCharSet(q.element, flags)
+    const cs = toUnicodeSet(q.element, flags)
     if (!cs.isSupersetOf(SURROGATES)) {
         // failed condition 1
         return false
     }
 
-    // FIXME: TS Error
-    // @ts-expect-error -- FIXME
-    const uCs = toCharSet(q.element, uFlags)
+    const uCs = toUnicodeSet(q.element, uFlags)
     if (!uCs.isSupersetOf(SURROGATES) || !uCs.isSupersetOf(ASTRAL)) {
         // failed condition 2
         return false
     }
 
-    if (!rangeEqual(cs.ranges, uCs.without(ASTRAL).ranges)) {
+    if (!rangeEqual(cs.chars.ranges, uCs.without(ASTRAL).chars.ranges)) {
         // failed condition 3
         return false
     }
@@ -353,7 +345,7 @@ export default createRule("require-unicode-regexp", {
                 return {}
             }
 
-            if (!flags.unicode) {
+            if (!flags.unicode && !flags.unicodeSets) {
                 context.report({
                     node,
                     loc: getFlagsLocation(),
diff --git a/lib/utils/unicode.ts b/lib/utils/unicode.ts
index 9ba1443..91c1467 100644
--- a/lib/utils/unicode.ts
+++ b/lib/utils/unicode.ts
@@ -8,15 +8,23 @@ export const CP_FF = 12
 export const CP_CR = 13
 export const CP_SPACE = " ".codePointAt(0)!
 export const CP_BAN = "!".codePointAt(0)!
+export const CP_HASH = "#".codePointAt(0)
 export const CP_DOLLAR = "$".codePointAt(0)!
+export const CP_PERCENT = "%".codePointAt(0)!
+export const CP_AMP = "&".codePointAt(0)!
 export const CP_OPENING_PAREN = "(".codePointAt(0)!
 export const CP_CLOSING_PAREN = ")".codePointAt(0)!
 export const CP_STAR = "*".codePointAt(0)!
 export const CP_PLUS = "+".codePointAt(0)!
+export const CP_COMMA = ",".codePointAt(0)!
 export const CP_MINUS = "-".codePointAt(0)!
 export const CP_DOT = ".".codePointAt(0)!
 export const CP_SLASH = "/".codePointAt(0)!
 export const CP_COLON = ":".codePointAt(0)!
+export const CP_SEMI = ";".codePointAt(0)!
+export const CP_LT = "<".codePointAt(0)!
+export const CP_EQ = "=".codePointAt(0)!
+export const CP_GT = ">".codePointAt(0)!
 export const CP_QUESTION = "?".codePointAt(0)!
 export const CP_AT = "@".codePointAt(0)!
 export const CP_OPENING_BRACKET = "[".codePointAt(0)!
diff --git a/package.json b/package.json
index 2e7cb42..8ffebdc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "eslint-plugin-regexp",
-    "version": "2.0.0-next.3",
+    "version": "2.0.0-next.4",
     "description": "ESLint plugin for finding RegExp mistakes and RegExp style guide violations.",
     "engines": {
         "node": "^18 || >=20"


Test patch:
diff --git a/tests/lib/rules/negation.ts b/tests/lib/rules/negation.ts
index dafe63f..de4cb82 100644
--- a/tests/lib/rules/negation.ts
+++ b/tests/lib/rules/negation.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/negation"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -14,6 +14,8 @@ tester.run("negation", rule as any, {
         String.raw`/[^\d\s]/`,
         String.raw`/[^\p{ASCII}]/iu`,
         String.raw`/[^\P{Ll}]/iu`,
+        String.raw`/[\p{Basic_Emoji}]/v`,
+        String.raw`/[^\P{Lowercase_Letter}]/iu`,
     ],
     invalid: [
         {
@@ -140,5 +142,12 @@ tester.run("negation", rule as any, {
             output: null,
             errors: ["Unexpected negated character class. Use '\\W' instead."],
         },
+        {
+            code: String.raw`/[^\P{Lowercase_Letter}]/iv`,
+            output: String.raw`/\p{Lowercase_Letter}/iv`,
+            errors: [
+                "Unexpected negated character class. Use '\\p{Lowercase_Letter}' instead.",
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/no-misleading-unicode-character.ts b/tests/lib/rules/no-misleading-unicode-character.ts
index 6c871af..0fe3ada 100644
--- a/tests/lib/rules/no-misleading-unicode-character.ts
+++ b/tests/lib/rules/no-misleading-unicode-character.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/no-misleading-unicode-character"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -46,6 +46,13 @@ tester.run("no-misleading-unicode-character", rule as any, {
 
         // Ignore escaped symbols because it's obvious they aren't together
         `/[\\uD83D\\uDC4D]/`,
+
+        // ES2024
+        "var r = /[👍]/v",
+        String.raw`var r = /^[\q{👶🏻}]$/v`,
+        String.raw`var r = /[🇯\q{abc}🇵]/v`,
+        "var r = /[🇯[A]🇵]/v",
+        "var r = /[🇯[A--B]🇵]/v",
     ],
     invalid: [
         {
@@ -262,5 +269,58 @@ tester.run("no-misleading-unicode-character", rule as any, {
             options: [{ fixable: true }],
             errors: [{ messageId: "characterClass" }],
         },
+
+        // ES2024
+        {
+            code: String.raw`/[[👶🏻]]/v`,
+            output: String.raw`/[[\q{👶🏻}]]/v`,
+            options: [{ fixable: true }],
+            errors: [{ messageId: "characterClass" }],
+        },
+        {
+            code: String.raw`/[👶🏻[👨‍👩‍👦]]/v`,
+            output: String.raw`/[\q{👶🏻}[👨‍👩‍👦]]/v`,
+            options: [{ fixable: true }],
+            errors: [
+                { messageId: "characterClass", column: 3 },
+                { messageId: "characterClass", column: 8 },
+            ],
+        },
+        {
+            code: String.raw`/[👶🏻👨‍👩‍👦]/v`,
+            output: String.raw`/[\q{👶🏻|👨‍👩‍👦}]/v`,
+            options: [{ fixable: true }],
+            errors: [{ messageId: "characterClass" }],
+        },
+        {
+            code: String.raw`/[👶🏻&👨‍👩‍👦]/v`,
+            output: String.raw`/[\q{👶🏻|👨‍👩‍👦}&]/v`,
+            options: [{ fixable: true }],
+            errors: [{ messageId: "characterClass" }],
+        },
+        {
+            code: String.raw`/[^👶🏻&👨‍👩‍👦]/v`,
+            output: null,
+            options: [{ fixable: true }],
+            errors: [{ messageId: "characterClass" }],
+        },
+        {
+            code: String.raw`/[^👨‍👩‍👦]/v`,
+            output: null,
+            options: [{ fixable: true }],
+            errors: [{ messageId: "characterClass" }],
+        },
+        {
+            code: String.raw`new RegExp("[👨‍👩‍👦]", "v")`,
+            output: String.raw`new RegExp("[\\q{👨‍👩‍👦}]", "v")`,
+            options: [{ fixable: true }],
+            errors: [{ messageId: "characterClass" }],
+        },
+        {
+            code: `/👨‍👩‍👦+/v`,
+            output: `/(?:👨‍👩‍👦)+/v`,
+            options: [{ fixable: true }],
+            errors: [{ messageId: "quantifierMulti" }],
+        },
     ],
 })
diff --git a/tests/lib/rules/no-useless-escape.ts b/tests/lib/rules/no-useless-escape.ts
index 8328c1b..f369682 100644
--- a/tests/lib/rules/no-useless-escape.ts
+++ b/tests/lib/rules/no-useless-escape.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/no-useless-escape"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -65,6 +65,60 @@ tester.run("no-useless-escape", rule as any, {
         //
         String.raw`/()\1\8/; /()\1\9/`,
         "/[\\^-`]/",
+
+        // ES2024
+        String.raw`/[\q{abc}]/v`,
+        String.raw`/[\(]/v`,
+        String.raw`/[\)]/v`,
+        String.raw`/[\{]/v`,
+        String.raw`/[\]]/v`,
+        String.raw`/[\}]/v`,
+        String.raw`/[\/]/v`,
+        String.raw`/[\-]/v`,
+        String.raw`/[\|]/v`,
+        String.raw`/[\$$]/v`,
+        String.raw`/[\&&]/v`,
+        String.raw`/[\!!]/v`,
+        String.raw`/[\##]/v`,
+        String.raw`/[\%%]/v`,
+        String.raw`/[\**]/v`,
+        String.raw`/[\++]/v`,
+        String.raw`/[\,,]/v`,
+        String.raw`/[\..]/v`,
+        String.raw`/[\::]/v`,
+        String.raw`/[\;;]/v`,
+        String.raw`/[\<<]/v`,
+        String.raw`/[\==]/v`,
+        String.raw`/[\>>]/v`,
+        String.raw`/[\??]/v`,
+        String.raw`/[\@@]/v`,
+        "/[\\``]/v",
+        String.raw`/[\~~]/v`,
+        String.raw`/[^\^^]/v`,
+        String.raw`/[_\^^]/v`,
+        String.raw`/[$\$]/v`,
+        String.raw`/[&\&]/v`,
+        String.raw`/[!\!]/v`,
+        String.raw`/[#\#]/v`,
+        String.raw`/[%\%]/v`,
+        String.raw`/[*\*]/v`,
+        String.raw`/[+\+]/v`,
+        String.raw`/[,\,]/v`,
+        String.raw`/[.\.]/v`,
+        String.raw`/[:\:]/v`,
+        String.raw`/[;\;]/v`,
+        String.raw`/[<\<]/v`,
+        String.raw`/[=\=]/v`,
+        String.raw`/[>\>]/v`,
+        String.raw`/[?\?]/v`,
+        String.raw`/[@\@]/v`,
+        "/[`\\`]/v",
+        String.raw`/[~\~]/v`,
+        String.raw`/[^^\^]/v`,
+        String.raw`/[_^\^]/v`,
+        String.raw`/[\&&&\&]/v`,
+        String.raw`/[[\-]\-]/v`,
+        String.raw`/[\^]/v`,
     ],
     invalid: [
         {
@@ -128,5 +182,373 @@ tester.run("no-useless-escape", rule as any, {
                 "Unnecessary escape character: \\P.",
             ],
         },
+        {
+            code: String.raw`/[\q{abc}]/;`, // Missing v flag
+            output: null,
+            errors: ["Unnecessary escape character: \\q."],
+        },
+
+        // ES2024
+        {
+            code: String.raw`/[\$]/v`,
+            output: String.raw`/[$]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    endColumn: 4,
+                    message: "Unnecessary escape character: \\$.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\&\&]/v`,
+            output: String.raw`/[&\&]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\&.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\!\!]/v`,
+            output: String.raw`/[!\!]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\!.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\#\#]/v`,
+            output: String.raw`/[#\#]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\#.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\%\%]/v`,
+            output: String.raw`/[%\%]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\%.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\*\*]/v`,
+            output: String.raw`/[*\*]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\*.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\+\+]/v`,
+            output: String.raw`/[+\+]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\+.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\,\,]/v`,
+            output: String.raw`/[,\,]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\,.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\.\.]/v`,
+            output: String.raw`/[.\.]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\..",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\:\:]/v`,
+            output: String.raw`/[:\:]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\:.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\;\;]/v`,
+            output: String.raw`/[;\;]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\;.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\<\<]/v`,
+            output: String.raw`/[<\<]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\<.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\=\=]/v`,
+            output: String.raw`/[=\=]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\=.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\>\>]/v`,
+            output: String.raw`/[>\>]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\>.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\?\?]/v`,
+            output: String.raw`/[?\?]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\?.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\@\@]/v`,
+            output: String.raw`/[@\@]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\@.",
+                },
+            ],
+        },
+        {
+            code: "/[\\`\\`]/v",
+            output: "/[`\\`]/v",
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\`.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\~\~]/v`,
+            output: String.raw`/[~\~]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\~.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[^\^\^]/v`,
+            output: String.raw`/[^^\^]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 4,
+                    message: "Unnecessary escape character: \\^.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[_\^\^]/v`,
+            output: String.raw`/[_^\^]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 4,
+                    message: "Unnecessary escape character: \\^.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[^\^]/v`,
+            output: String.raw`/[^^]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 4,
+                    message: "Unnecessary escape character: \\^.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\&\&&\&]/v`,
+            output: String.raw`/[&\&&\&]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\&.",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\p{ASCII}--\.]/v`,
+            output: String.raw`/[\p{ASCII}--.]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 14,
+                    message: "Unnecessary escape character: \\..",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\p{ASCII}&&\.]/v`,
+            output: String.raw`/[\p{ASCII}&&.]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 14,
+                    message: "Unnecessary escape character: \\..",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\.--[.&]]/v`,
+            output: String.raw`/[.--[.&]]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\..",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\.&&[.&]]/v`,
+            output: String.raw`/[.&&[.&]]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\..",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\.--\.--\.]/v`,
+            output: String.raw`/[.--.--.]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\..",
+                },
+                {
+                    line: 1,
+                    column: 7,
+                    message: "Unnecessary escape character: \\..",
+                },
+                {
+                    line: 1,
+                    column: 11,
+                    message: "Unnecessary escape character: \\..",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\.&&\.&&\.]/v`,
+            output: String.raw`/[.&&.&&.]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 3,
+                    message: "Unnecessary escape character: \\..",
+                },
+                {
+                    line: 1,
+                    column: 7,
+                    message: "Unnecessary escape character: \\..",
+                },
+                {
+                    line: 1,
+                    column: 11,
+                    message: "Unnecessary escape character: \\..",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[[\.&]--[\.&]]/v`,
+            output: String.raw`/[[.&]--[.&]]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 4,
+                    message: "Unnecessary escape character: \\..",
+                },
+                {
+                    line: 1,
+                    column: 11,
+                    message: "Unnecessary escape character: \\..",
+                },
+            ],
+        },
+        {
+            code: String.raw`/[[\.&]&&[\.&]]/v`,
+            output: String.raw`/[[.&]&&[.&]]/v`,
+            errors: [
+                {
+                    line: 1,
+                    column: 4,
+                    message: "Unnecessary escape character: \\..",
+                },
+                {
+                    line: 1,
+                    column: 11,
+                    message: "Unnecessary escape character: \\..",
+                },
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/prefer-d.ts b/tests/lib/rules/prefer-d.ts
index bdae490..e01e1e2 100644
--- a/tests/lib/rules/prefer-d.ts
+++ b/tests/lib/rules/prefer-d.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/prefer-d"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -28,6 +28,14 @@ tester.run("prefer-d", rule as any, {
             code: String.raw`/[\da-z]/`,
             options: [{ insideCharacterClass: "d" }],
         },
+        String.raw`/\d/v`,
+        {
+            code: String.raw`/[\d--0]/v`,
+            options: [{ insideCharacterClass: "range" }],
+        },
+        String.raw`/[\q{0|1|2|3|4|5|6|7|8}]/v`,
+        String.raw`/[\q{0|1|2|3|4|5|6|7|8|9|a}]/v`,
+        String.raw`/[\q{0|1|2|3|4|5|6|7|8|9|abc}]/v`,
     ],
     invalid: [
         {
@@ -99,5 +107,79 @@ tester.run("prefer-d", rule as any, {
             options: [{ insideCharacterClass: "range" }],
             errors: ["Unexpected character set '\\d'. Use '0-9' instead."],
         },
+        {
+            code: "/[0-9]/v",
+            output: String.raw`/\d/v`,
+            errors: [
+                {
+                    message:
+                        "Unexpected character class '[0-9]'. Use '\\d' instead.",
+                    column: 2,
+                    endColumn: 7,
+                },
+            ],
+        },
+        {
+            code: "/[[0-9]--[0-7]]/v",
+            output: String.raw`/[\d--[0-7]]/v`,
+            errors: [
+                {
+                    message:
+                        "Unexpected character class '[0-9]'. Use '\\d' instead.",
+                    column: 3,
+                    endColumn: 8,
+                },
+            ],
+        },
+        {
+            code: "/[[0-:]--:]/v",
+            output: String.raw`/\d/v`,
+            errors: [
+                {
+                    message:
+                        "Unexpected character class '[[0-:]--:]'. Use '\\d' instead.",
+                    column: 2,
+                    endColumn: 12,
+                },
+            ],
+        },
+        {
+            code: String.raw`/[[\da-z]--0]/v`,
+            output: String.raw`/[[0-9a-z]--0]/v`,
+            options: [{ insideCharacterClass: "range" }],
+            errors: [
+                {
+                    message:
+                        "Unexpected character set '\\d'. Use '0-9' instead.",
+                    column: 4,
+                    endColumn: 6,
+                },
+            ],
+        },
+        {
+            code: String.raw`/[[0-9a-z]--0]/v`,
+            output: String.raw`/[[\da-z]--0]/v`,
+            options: [{ insideCharacterClass: "d" }],
+            errors: [
+                {
+                    message:
+                        "Unexpected character class range '0-9'. Use '\\d' instead.",
+                    column: 4,
+                    endColumn: 7,
+                },
+            ],
+        },
+        {
+            code: String.raw`/[\q{0|1|2|3|4|5|6|7|8|9}]/v`,
+            output: String.raw`/\d/v`,
+            errors: [
+                {
+                    message:
+                        "Unexpected character class '[\\q{0|1|2|3|4|5|6|7|8|9}]'. Use '\\d' instead.",
+                    column: 2,
+                    endColumn: 27,
+                },
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/prefer-w.ts b/tests/lib/rules/prefer-w.ts
index d7d03cf..873ddd5 100644
--- a/tests/lib/rules/prefer-w.ts
+++ b/tests/lib/rules/prefer-w.ts
@@ -3,13 +3,13 @@ import rule from "../../../lib/rules/prefer-w"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
 
 tester.run("prefer-w", rule as any, {
-    valid: ["/\\w/"],
+    valid: ["/\\w/", "/[\\Da-zA-Z_#]/", "/\\w/v", "/[\\Da-zA-Z_#]/v"],
     invalid: [
         {
             code: "/[0-9a-zA-Z_]/",
@@ -35,6 +35,20 @@ tester.run("prefer-w", rule as any, {
                 },
             ],
         },
+        {
+            code: "/[\\da-zA-Z_#]/",
+            output: "/[\\w#]/",
+            errors: [
+                "Unexpected character class ranges '[\\da-zA-Z_]'. Use '\\w' instead.",
+            ],
+        },
+        {
+            code: "/[0-9a-z_[\\s&&\\p{ASCII}]]/iv",
+            output: "/[\\w[\\s&&\\p{ASCII}]]/iv",
+            errors: [
+                "Unexpected character class ranges '[0-9a-z_]'. Use '\\w' instead.",
+            ],
+        },
         {
             code: "/[0-9a-z_]/i",
             output: "/\\w/i",
diff --git a/tests/lib/rules/require-unicode-regexp.ts b/tests/lib/rules/require-unicode-regexp.ts
index aee0415..a3025e9 100644
--- a/tests/lib/rules/require-unicode-regexp.ts
+++ b/tests/lib/rules/require-unicode-regexp.ts
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/require-unicode-regexp"
 
 const tester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: "latest",
         sourceType: "module",
     },
 })
@@ -28,6 +28,8 @@ tester.run("require-unicode-regexp", rule as any, {
         String.raw`const flags = 'u'; new globalThis.RegExp('', flags)`,
         String.raw`const flags = 'g'; new globalThis.RegExp('', flags + 'u')`,
         String.raw`const flags = 'gimu'; new globalThis.RegExp('foo', flags[3])`,
+        String.raw`/foo/v`,
+        String.raw`new RegExp('foo', 'v')`,
     ],
     invalid: [
         {