Repository: ota-meshi/eslint-plugin-regexp
Pull request #169 (2021-04-20 15:00:13)

Deprecate `regexp/prefer-t` rule.
Related to #168, #165


Patch:
diff --git a/README.md b/README.md
index 0c51ec9..60d5a4e 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ and either use one of the two configurations available (`recommended` or `all`)
 ### The recommended configuration
 
 The `plugin:regexp/recommended` config enables a subset of [the rules](#white_check_mark-rules) that should be most useful to most users.
-*See [https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/recommended.ts](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/recommended.ts) for more details.*
+*See [lib/configs/recommended.ts](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/recommended.ts) for more details.*
 
 ```js
 // .eslintrc.js
@@ -87,7 +87,7 @@ module.exports = {
 ### Using `"plugin:regexp/all"`
 
 The `plugin:regexp/all` config enables all rules. It's meant for testing, not for production use because it changes with every minor and major version of the plugin. Use it at your own risk.
-*See [https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/all.ts](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/all.ts) for more details.*
+*See [lib/configs/all.ts](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/all.ts) for more details.*
 
 <!--USAGE_SECTION_END-->
 
@@ -111,6 +111,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
 | [regexp/no-empty-group](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-group.html) | disallow empty group | :star: |
 | [regexp/no-empty-lookarounds-assertion](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-lookarounds-assertion.html) | disallow empty lookahead assertion or empty lookbehind assertion | :star: |
 | [regexp/no-escape-backspace](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-escape-backspace.html) | disallow escape backspace (`[\b]`) | :star: |
+| [regexp/no-invalid-regexp](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-invalid-regexp.html) | disallow invalid regular expression strings in `RegExp` constructors |  |
 | [regexp/no-lazy-ends](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-lazy-ends.html) | disallow lazy quantifiers at the end of an expression |  |
 | [regexp/no-optional-assertion](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-optional-assertion.html) | disallow optional assertions |  |
 | [regexp/no-potentially-useless-backreference](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-potentially-useless-backreference.html) | disallow backreferences that reference a group that might not be matched |  |
@@ -173,13 +174,21 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
 | [regexp/prefer-plus-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-plus-quantifier.html) | enforce using `+` quantifier | :star::wrench: |
 | [regexp/prefer-question-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-question-quantifier.html) | enforce using `?` quantifier | :star::wrench: |
 | [regexp/prefer-star-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-star-quantifier.html) | enforce using `*` quantifier | :star::wrench: |
-| [regexp/prefer-t](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-t.html) | enforce using `\t` | :star::wrench: |
 | [regexp/prefer-unicode-codepoint-escapes](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-unicode-codepoint-escapes.html) | enforce use of unicode codepoint escapes | :wrench: |
 | [regexp/prefer-w](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-w.html) | enforce using `\w` | :star::wrench: |
 | [regexp/sort-character-class-elements](https://ota-meshi.github.io/eslint-plugin-regexp/rules/sort-character-class-elements.html) | enforces elements order in character class | :wrench: |
 | [regexp/sort-flags](https://ota-meshi.github.io/eslint-plugin-regexp/rules/sort-flags.html) | require regex flags to be sorted | :wrench: |
 | [regexp/unicode-escape](https://ota-meshi.github.io/eslint-plugin-regexp/rules/unicode-escape.html) | enforce consistent usage of unicode escape or unicode codepoint escape | :wrench: |
 
+### Deprecated
+
+- :warning: We're going to remove deprecated rules in the next major release. Please migrate to successor/new rules.
+- :innocent: We don't fix bugs which are in deprecated rules since we don't have enough resources.
+
+| Rule ID | Replaced by |
+|:--------|:------------|
+| [regexp/prefer-t](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-t.html) | [regexp/control-character-escape](https://ota-meshi.github.io/eslint-plugin-regexp/rules/control-character-escape.html.md) |
+
 <!--RULES_TABLE_END-->
 <!--RULES_SECTION_END-->
 
diff --git a/docs/rules/README.md b/docs/rules/README.md
index e5e8402..69dab44 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -20,6 +20,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
 | [regexp/no-empty-group](./no-empty-group.md) | disallow empty group | :star: |
 | [regexp/no-empty-lookarounds-assertion](./no-empty-lookarounds-assertion.md) | disallow empty lookahead assertion or empty lookbehind assertion | :star: |
 | [regexp/no-escape-backspace](./no-escape-backspace.md) | disallow escape backspace (`[\b]`) | :star: |
+| [regexp/no-invalid-regexp](./no-invalid-regexp.md) | disallow invalid regular expression strings in `RegExp` constructors |  |
 | [regexp/no-lazy-ends](./no-lazy-ends.md) | disallow lazy quantifiers at the end of an expression |  |
 | [regexp/no-optional-assertion](./no-optional-assertion.md) | disallow optional assertions |  |
 | [regexp/no-potentially-useless-backreference](./no-potentially-useless-backreference.md) | disallow backreferences that reference a group that might not be matched |  |
@@ -82,9 +83,17 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
 | [regexp/prefer-plus-quantifier](./prefer-plus-quantifier.md) | enforce using `+` quantifier | :star::wrench: |
 | [regexp/prefer-question-quantifier](./prefer-question-quantifier.md) | enforce using `?` quantifier | :star::wrench: |
 | [regexp/prefer-star-quantifier](./prefer-star-quantifier.md) | enforce using `*` quantifier | :star::wrench: |
-| [regexp/prefer-t](./prefer-t.md) | enforce using `\t` | :star::wrench: |
 | [regexp/prefer-unicode-codepoint-escapes](./prefer-unicode-codepoint-escapes.md) | enforce use of unicode codepoint escapes | :wrench: |
 | [regexp/prefer-w](./prefer-w.md) | enforce using `\w` | :star::wrench: |
 | [regexp/sort-character-class-elements](./sort-character-class-elements.md) | enforces elements order in character class | :wrench: |
 | [regexp/sort-flags](./sort-flags.md) | require regex flags to be sorted | :wrench: |
 | [regexp/unicode-escape](./unicode-escape.md) | enforce consistent usage of unicode escape or unicode codepoint escape | :wrench: |
+
+### Deprecated
+
+- :warning: We're going to remove deprecated rules in the next major release. Please migrate to successor/new rules.
+- :innocent: We don't fix bugs which are in deprecated rules since we don't have enough resources.
+
+| Rule ID | Replaced by |
+|:--------|:------------|
+| [regexp/prefer-t](./prefer-t.md) | [regexp/control-character-escape](./control-character-escape.md.md) |
diff --git a/docs/rules/no-invalid-regexp.md b/docs/rules/no-invalid-regexp.md
new file mode 100644
index 0000000..a5b588b
--- /dev/null
+++ b/docs/rules/no-invalid-regexp.md
@@ -0,0 +1,64 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "regexp/no-invalid-regexp"
+description: "disallow invalid regular expression strings in `RegExp` constructors"
+---
+# regexp/no-invalid-regexp
+
+> disallow invalid regular expression strings in `RegExp` constructors
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+
+## :book: Rule Details
+
+This rule reports invalid regular expression patterns given to `RegExp` constructors.
+
+<eslint-code-block>
+
+```js
+/* eslint regexp/no-invalid-regexp: "error" */
+
+/* ✓ GOOD */
+RegExp('foo')
+RegExp('[a' + ']')
+
+/* ✗ BAD */
+RegExp('\\')
+RegExp('[a-Z]*')
+RegExp('\\p{Foo}', 'u')
+
+const space = '\\s*'
+RegExp('=' + space + '+(\\w+)', 'u')
+```
+
+</eslint-code-block>
+
+### Differences to ESLint's `no-invalid-regexp` rule
+
+This rule is almost functionally equivalent to ESLint's [no-invalid-regexp] rule. The only difference is that this rule doesn't valid flags (see [no-non-standard-flag](./no-non-standard-flag.html)).
+
+There are two reasons we provide this rule:
+
+1. Better error reporting.
+
+    Instead of reporting the whole invalid string, this rule will try to report the exact position of the syntax error.
+
+2. Better support for complex constructor calls.
+
+    ESLint's rule only validates `RegExp` constructors called with simple string literals. This rule also supports operations (e.g. string concatenation) and variables to some degree.
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further reading
+
+- [no-invalid-regexp]
+
+[no-invalid-regexp]: https://eslint.org/docs/rules/no-invalid-regexp
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-invalid-regexp.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-invalid-regexp.ts)
diff --git a/docs/rules/no-useless-flag.md b/docs/rules/no-useless-flag.md
index 2aef476..b48ae41 100644
--- a/docs/rules/no-useless-flag.md
+++ b/docs/rules/no-useless-flag.md
@@ -157,13 +157,16 @@ No other flags will be checked.
 {
   "regexp/no-useless-flag": ["error",
     {
-      "ignore": [] // An array of "i", "m", "s", "g" and "y".
+      "ignore": [], // An array of "i", "m", "s", "g" and "y".
+      "strictTypes": true
     }
   ]
 }
 ```
 
 - `ignore` ... An array of flags to ignore from the check.
+- `strictTypes` ... If `true`, strictly check the type of object to determine if the regex instance was used in `search()` and `split()`. Default is `true`. This option is only effective for verifying the `g` and `y` flags.  
+  This option is always on when using TypeScript.
 
 ### `"ignore": ["s", "g"]`
 
@@ -183,6 +186,32 @@ var foo = /\w/m;
 
 </eslint-code-block>
 
+### `"strictTypes": false`
+
+<eslint-code-block fix>
+
+```js
+/* eslint regexp/no-useless-flag: ["error", { "strictTypes": false }] */
+
+/* ✓ GOOD */
+const notStr = {}
+notStr.split(/foo/g);
+
+/** @param {object} obj */
+function fn1 (obj) {
+    obj.search(/foo/g);
+}
+
+/* ✗ BAD */
+maybeStr.split(/foo/g); // The type of "maybeStr" cannot be tracked.
+
+function fn2 (maybeStr) {
+    maybeStr.search(/foo/g);
+}
+```
+
+</eslint-code-block>
+
 ## :heart: Compatibility
 
 This rule is compatible with [clean-regex/no-unnecessary-flag] rule.
diff --git a/docs/rules/prefer-t.md b/docs/rules/prefer-t.md
index 56c7123..8699c7a 100644
--- a/docs/rules/prefer-t.md
+++ b/docs/rules/prefer-t.md
@@ -9,7 +9,7 @@ since: "v0.1.0"
 
 > enforce using `\t`
 
-- :gear: This rule is included in `"plugin:regexp/recommended"`.
+- :warning: This rule was **deprecated** and replaced by [regexp/control-character-escape](control-character-escape.md) rule.
 - :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
 
 ## :book: Rule Details
diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md
index 50f8516..e780d0f 100644
--- a/docs/user-guide/README.md
+++ b/docs/user-guide/README.md
@@ -23,7 +23,7 @@ and either use one of the two configurations available (`recommended` or `all`)
 ### The recommended configuration
 
 The `plugin:regexp/recommended` config enables a subset of [the rules](../rules/README.md) that should be most useful to most users.
-*See [https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/recommended.ts](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/recommended.ts) for more details.*
+*See [lib/configs/recommended.ts](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/recommended.ts) for more details.*
 
 ```js
 // .eslintrc.js
@@ -59,7 +59,7 @@ module.exports = {
 ### Using `"plugin:regexp/all"`
 
 The `plugin:regexp/all` config enables all rules. It's meant for testing, not for production use because it changes with every minor and major version of the plugin. Use it at your own risk.
-*See [https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/all.ts](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/all.ts) for more details.*
+*See [lib/configs/all.ts](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/all.ts) for more details.*
 
 <!--USAGE_SECTION_END-->
 
diff --git a/lib/configs/recommended.ts b/lib/configs/recommended.ts
index 4606691..db7831e 100644
--- a/lib/configs/recommended.ts
+++ b/lib/configs/recommended.ts
@@ -26,7 +26,6 @@ export = {
         "regexp/prefer-plus-quantifier": "error",
         "regexp/prefer-question-quantifier": "error",
         "regexp/prefer-star-quantifier": "error",
-        "regexp/prefer-t": "error",
         "regexp/prefer-w": "error",
     },
 }
diff --git a/lib/rules/control-character-escape.ts b/lib/rules/control-character-escape.ts
index 257c4c8..ebaf5b2 100644
--- a/lib/rules/control-character-escape.ts
+++ b/lib/rules/control-character-escape.ts
@@ -10,6 +10,7 @@ import {
     defineRegexpVisitor,
 } from "../utils"
 import { isRegexpLiteral } from "../utils/ast-utils/utils"
+import { mentionChar } from "../utils/mention"
 
 const CONTROL_CHARS = new Map<number, string>([
     [0, "\\0"],
@@ -33,7 +34,7 @@ export default createRule("control-character-escape", {
         schema: [],
         messages: {
             unexpected:
-                "Unexpected control character escape '{{actual}}' ({{cp}}). Use '{{expected}}' instead.",
+                "Unexpected control character escape {{actual}}. Use '{{expected}}' instead.",
         },
         type: "suggestion", // "problem",
     },
@@ -77,10 +78,7 @@ export default createRule("control-character-escape", {
                         loc: getRegexpLocation(cNode),
                         messageId: "unexpected",
                         data: {
-                            actual: cNode.raw,
-                            cp: `U+${cNode.value
-                                .toString(16)
-                                .padStart(4, "0")}`,
+                            actual: mentionChar(cNode),
                             expected: expectedRaw,
                         },
                         fix: fixReplaceNode(cNode, expectedRaw),
diff --git a/lib/rules/no-dupe-characters-character-class.ts b/lib/rules/no-dupe-characters-character-class.ts
index dcfb8c1..7ceffad 100644
--- a/lib/rules/no-dupe-characters-character-class.ts
+++ b/lib/rules/no-dupe-characters-character-class.ts
@@ -21,6 +21,7 @@ import { JS } from "refa"
 // eslint-disable-next-line no-restricted-imports -- there's no way around it
 import { toCharSet as uncachedToCharSet } from "regexp-ast-analysis"
 import type { Rule } from "eslint"
+import { mentionChar } from "../utils/mention"
 
 interface Grouping {
     duplicates: {
@@ -160,25 +161,6 @@ function fixRemove(
     })
 }
 
-/**
- * Creates a string that mentions the given element.
- */
-function mention(element: CharacterClassElement): string {
-    /** Creates a "U+FFFF" string */
-    function unicode(value: number): string {
-        return `U+${value.toString(16).padStart(4, "0")}`
-    }
-
-    if (element.type === "Character") {
-        return `'${element.raw}' (${unicode(element.value)})`
-    } else if (element.type === "CharacterClassRange") {
-        return `'${element.raw}' (${unicode(element.min.value)} - ${unicode(
-            element.max.value,
-        )})`
-    }
-    return `'${element.raw}'`
-}
-
 export default createRule("no-dupe-characters-character-class", {
     meta: {
         type: "suggestion",
@@ -218,7 +200,7 @@ export default createRule("no-dupe-characters-character-class", {
                     loc: getRegexpLocation(duplicate),
                     messageId: "duplicate",
                     data: {
-                        duplicate: mention(duplicate),
+                        duplicate: mentionChar(duplicate),
                     },
                     fix: fixRemove(regexpContext, duplicate),
                 })
@@ -228,8 +210,8 @@ export default createRule("no-dupe-characters-character-class", {
                     loc: getRegexpLocation(duplicate),
                     messageId: "duplicateNonObvious",
                     data: {
-                        duplicate: mention(duplicate),
-                        element: mention(element),
+                        duplicate: mentionChar(duplicate),
+                        element: mentionChar(element),
                     },
                     fix: fixRemove(regexpContext, duplicate),
                 })
@@ -250,8 +232,8 @@ export default createRule("no-dupe-characters-character-class", {
                 loc: getRegexpLocation(element),
                 messageId: "overlap",
                 data: {
-                    elementA: mention(element),
-                    elementB: mention(intersectElement),
+                    elementA: mentionChar(element),
+                    elementB: mentionChar(intersectElement),
                     overlap,
                 },
             })
@@ -274,8 +256,8 @@ export default createRule("no-dupe-characters-character-class", {
                 loc: getRegexpLocation(subsetElement),
                 messageId: "subset",
                 data: {
-                    subsetElement: mention(subsetElement),
-                    element: mention(element),
+                    subsetElement: mentionChar(subsetElement),
+                    element: mentionChar(element),
                 },
                 fix: fixRemove(regexpContext, subsetElement),
             })
@@ -296,10 +278,10 @@ export default createRule("no-dupe-characters-character-class", {
                 loc: getRegexpLocation(subsetElement),
                 messageId: "subsetOfMany",
                 data: {
-                    subsetElement: mention(subsetElement),
+                    subsetElement: mentionChar(subsetElement),
                     elements: `'${elements
                         .map((e) => e.raw)
-                        .join("")}' (${elements.map(mention).join(", ")})`,
+                        .join("")}' (${elements.map(mentionChar).join(", ")})`,
                 },
                 fix: fixRemove(regexpContext, subsetElement),
             })
diff --git a/lib/rules/no-invalid-regexp.ts b/lib/rules/no-invalid-regexp.ts
new file mode 100644
index 0000000..2820b0c
--- /dev/null
+++ b/lib/rules/no-invalid-regexp.ts
@@ -0,0 +1,64 @@
+import type { RegExpContextForInvalid } from "../utils"
+import { createRule, defineRegexpVisitor } from "../utils"
+
+/** Returns the position of the error */
+function getErrorIndex(error: SyntaxError): number | null {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- x
+    const index = (error as any).index
+    if (typeof index === "number") {
+        return index
+    }
+    return null
+}
+
+export default createRule("no-invalid-regexp", {
+    meta: {
+        docs: {
+            description:
+                "disallow invalid regular expression strings in `RegExp` constructors",
+            category: "Possible Errors",
+            // TODO Switch to recommended in the major version.
+            // TODO When setting `recommended: true`, do not forget to disable ESLint's no-invalid-regexp in recommended.ts
+            // recommended: true,
+            recommended: false,
+        },
+        schema: [],
+        messages: {
+            error: "{{message}}",
+        },
+        type: "problem",
+    },
+    create(context) {
+        /** Visit invalid regexes */
+        function visitInvalid(regexpContext: RegExpContextForInvalid): void {
+            const { node, error, patternSource } = regexpContext
+
+            let loc = undefined
+
+            const index = getErrorIndex(error)
+            if (
+                index !== null &&
+                index >= 0 &&
+                index <= patternSource.value.length
+            ) {
+                // The error index regexpp reports is a little weird.
+                // It's either spot on or one character to the right.
+                // Since we can't know which index is correct, we will report
+                // both positions.
+                loc = patternSource.getAstLocation({
+                    start: Math.max(index - 1, 0),
+                    end: Math.min(index + 1, patternSource.value.length),
+                })
+            }
+
+            context.report({
+                node,
+                loc: loc ?? undefined,
+                messageId: "error",
+                data: { message: error.message },
+            })
+        }
+
+        return defineRegexpVisitor(context, { visitInvalid })
+    },
+})
diff --git a/lib/rules/no-non-standard-flag.ts b/lib/rules/no-non-standard-flag.ts
index b3d662f..4b87958 100644
--- a/lib/rules/no-non-standard-flag.ts
+++ b/lib/rules/no-non-standard-flag.ts
@@ -1,5 +1,4 @@
-import type { RegExpVisitor } from "regexpp/visitor"
-import type { RegExpContext } from "../utils"
+import type { RegExpContext, UnparsableRegExpContext } from "../utils"
 import { createRule, defineRegexpVisitor } from "../utils"
 
 const STANDARD_FLAGS = "dgimsuy"
@@ -20,14 +19,12 @@ export default createRule("no-non-standard-flag", {
         type: "suggestion", // "problem",
     },
     create(context) {
-        /**
-         * Create visitor
-         */
-        function createVisitor({
-            node,
+        /** The logic of this rule */
+        function visit({
+            regexpNode,
             getFlagsLocation,
             flagsString,
-        }: RegExpContext): RegExpVisitor.Handlers {
+        }: RegExpContext | UnparsableRegExpContext) {
             if (flagsString) {
                 const nonStandard = [...flagsString].filter(
                     (f) => !STANDARD_FLAGS.includes(f),
@@ -35,18 +32,22 @@ export default createRule("no-non-standard-flag", {
 
                 if (nonStandard.length > 0) {
                     context.report({
-                        node,
+                        node: regexpNode,
                         loc: getFlagsLocation(),
                         messageId: "unexpected",
                         data: { flag: nonStandard[0] },
                     })
                 }
             }
-            return {}
         }
 
         return defineRegexpVisitor(context, {
-            createVisitor,
+            createVisitor(regexpContext) {
+                visit(regexpContext)
+                return {}
+            },
+            visitInvalid: visit,
+            visitUnknown: visit,
         })
     },
 })
diff --git a/lib/rules/no-obscure-range.ts b/lib/rules/no-obscure-range.ts
index 3eacd17..5385b03 100644
--- a/lib/rules/no-obscure-range.ts
+++ b/lib/rules/no-obscure-range.ts
@@ -13,6 +13,7 @@ import {
     isUseHexEscape,
     isOctalEscape,
 } from "../utils"
+import { mentionChar } from "../utils/mention"
 
 export default createRule("no-obscure-range", {
     meta: {
@@ -34,7 +35,7 @@ export default createRule("no-obscure-range", {
         ],
         messages: {
             unexpected:
-                "Unexpected obscure character range. The characters of '{{range}}' ({{unicode}}) are not obvious.",
+                "Unexpected obscure character range. The characters of {{range}} are not obvious.",
         },
         type: "suggestion", // "problem",
     },
@@ -84,16 +85,12 @@ export default createRule("no-obscure-range", {
                         return
                     }
 
-                    const uMin = `U+${min.value.toString(16).padStart(4, "0")}`
-                    const uMax = `U+${max.value.toString(16).padStart(4, "0")}`
-
                     context.report({
                         node,
                         loc: getRegexpLocation(rNode),
                         messageId: "unexpected",
                         data: {
-                            range: rNode.raw,
-                            unicode: `${uMin} - ${uMax}`,
+                            range: mentionChar(rNode),
                         },
                     })
                 },
diff --git a/lib/rules/no-useless-flag.ts b/lib/rules/no-useless-flag.ts
index 58921b6..55c508d 100644
--- a/lib/rules/no-useless-flag.ts
+++ b/lib/rules/no-useless-flag.ts
@@ -34,7 +34,19 @@ type CodePathId = {
     loopNode: Statement | undefined
 }
 
+type RegExpMethodKind =
+    | "search"
+    | "split"
+    | "exec"
+    | "test"
+    | "match"
+    | "matchAll"
+    | "replace"
+    | "replaceAll"
+
 class RegExpReference {
+    public regExpContext: RegExpContext
+
     public readonly defineNode: RegExpExpression
 
     public defineId?: CodePathId
@@ -43,8 +55,6 @@ class RegExpReference {
         Expression,
         {
             marked?: boolean
-            usedInSearch?: boolean
-            usedInSplit?: boolean
             usedInExec?: { id: CodePathId }
             usedInTest?: { id: CodePathId }
         }
@@ -52,23 +62,18 @@ class RegExpReference {
 
     private readonly state: {
         track: boolean
-        usedIn: {
-            search?: boolean
-            split?: boolean
-            exec?: boolean
-            test?: boolean
-            match?: boolean
-            matchAll?: boolean
-            replace?: boolean
-            replaceAll?: boolean
-        }
+        usedNodes: Map<RegExpMethodKind, Expression[]>
         hasUnusedExpression?: boolean
     } = {
-        usedIn: {},
+        usedNodes: new Map(),
         track: true,
     }
 
-    public constructor(defineNode: RegExpExpression) {
+    public constructor(
+        regExpContext: RegExpContext,
+        defineNode: RegExpExpression,
+    ) {
+        this.regExpContext = regExpContext
         this.defineNode = defineNode
     }
 
@@ -84,18 +89,16 @@ class RegExpReference {
         const exprState = this.readNodes.get(node)
         if (exprState) {
             exprState.marked = true
-            exprState.usedInSearch = true
         }
-        this.state.usedIn.search = true
+        this.addUsedNode("search", node)
     }
 
     public markAsUsedInSplit(node: Expression) {
         const exprState = this.readNodes.get(node)
         if (exprState) {
             exprState.marked = true
-            exprState.usedInSplit = true
         }
-        this.state.usedIn.split = true
+        this.addUsedNode("split", node)
     }
 
     public markAsUsedInExec(
@@ -108,7 +111,7 @@ class RegExpReference {
             exprState.marked = true
             exprState.usedInExec = { id: { codePathId, loopNode } }
         }
-        this.state.usedIn.exec = true
+        this.addUsedNode("exec", node)
     }
 
     public markAsUsedInTest(
@@ -121,23 +124,12 @@ class RegExpReference {
             exprState.marked = true
             exprState.usedInTest = { id: { codePathId, loopNode } }
         }
-        this.state.usedIn.test = true
+        this.addUsedNode("test", node)
     }
 
-    public isUsed(
-        kinds: (
-            | "search"
-            | "split"
-            | "exec"
-            | "test"
-            | "match"
-            | "matchAll"
-            | "replace"
-            | "replaceAll"
-        )[],
-    ) {
+    public isUsed(kinds: RegExpMethodKind[]) {
         for (const kind of kinds) {
-            if (this.state.usedIn[kind]) {
+            if (this.state.usedNodes.has(kind)) {
                 return true
             }
         }
@@ -148,13 +140,29 @@ class RegExpReference {
         return !this.state.track
     }
 
-    public markAsUsed(kind: "match" | "matchAll" | "replace" | "replaceAll") {
-        this.state.usedIn[kind] = true
+    public markAsUsed(
+        kind: "match" | "matchAll" | "replace" | "replaceAll",
+        exprNode: Expression,
+    ) {
+        this.addUsedNode(kind, exprNode)
     }
 
     public markAsCannotTrack() {
         this.state.track = false
     }
+
+    public getUsedNodes() {
+        return this.state.usedNodes
+    }
+
+    private addUsedNode(kind: RegExpMethodKind, exprNode: Expression) {
+        const list = this.state.usedNodes.get(kind)
+        if (list) {
+            list.push(exprNode)
+        } else {
+            this.state.usedNodes.set(kind, [exprNode])
+        }
+    }
 }
 
 /**
@@ -187,7 +195,7 @@ function getFlagLocation(
  */
 function fixRemoveFlag(
     { flagsString, fixReplaceFlags }: RegExpContext,
-    flag: "i" | "m" | "s" | "g",
+    flag: "i" | "m" | "s" | "g" | "y",
 ) {
     if (flagsString) {
         return fixReplaceFlags(flagsString.replace(flag, ""))
@@ -333,34 +341,69 @@ function createUselessDotAllFlagVisitor(context: Rule.RuleContext) {
 /**
  * Create visitor for verify unnecessary g flag
  */
-function createUselessGlobalFlagVisitor(context: Rule.RuleContext) {
+function createUselessGlobalFlagVisitor(
+    context: Rule.RuleContext,
+    strictTypes: boolean,
+) {
+    const enum ReportKind {
+        // It is used only in "split".
+        usedOnlyInSplit,
+        // It is used only in "search".
+        usedOnlyInSearch,
+        // It is used only once in "exec".
+        usedOnlyOnceInExec,
+        // It is used only once in "test".
+        usedOnlyOnceInTest,
+        // The global flag is given, but it is not used.
+        unused,
+    }
+
+    type ReportData = { kind: ReportKind; fixable?: boolean }
+
     /**
      * Report for useless global flag
      */
-    function reportUselessGlobalFlag(regExpReference: RegExpReference) {
+    function reportUselessGlobalFlag(
+        regExpReference: RegExpReference,
+        data: ReportData,
+    ) {
         const node = regExpReference.defineNode
         context.report({
             node,
             loc: getFlagLocation(context, node, "g"),
-            messageId: "uselessGlobalFlag",
+            messageId:
+                data.kind === ReportKind.usedOnlyInSplit
+                    ? "uselessGlobalFlagForSplit"
+                    : data.kind === ReportKind.usedOnlyInSearch
+                    ? "uselessGlobalFlagForSearch"
+                    : data.kind === ReportKind.usedOnlyOnceInTest
+                    ? "uselessGlobalFlagForTest"
+                    : data.kind === ReportKind.usedOnlyOnceInExec
+                    ? "uselessGlobalFlagForExec"
+                    : "uselessGlobalFlag",
+            fix: data.fixable
+                ? fixRemoveFlag(regExpReference.regExpContext, "g")
+                : null,
         })
     }
 
     /**
-     * Checks Check whether the flag needs to be reported.
+     * Checks if it needs to be reported and returns the report data if it needs to be reported.
      */
-    function isNeedReport(regExpReference: RegExpReference): boolean {
+    function getReportData(
+        regExpReference: RegExpReference,
+    ): null | ReportData {
         let countOfUsedInExecOrTest = 0
         for (const readData of regExpReference.readNodes.values()) {
             if (!readData.marked) {
                 // There is expression node whose usage is unknown.
-                return false
+                return null
             }
             const usedInExecOrTest = readData.usedInExec || readData.usedInTest
             if (usedInExecOrTest) {
                 if (!regExpReference.defineId) {
                     // The definition scope is unknown. Probably broken.
-                    return false
+                    return null
                 }
                 if (
                     regExpReference.defineId.codePathId ===
@@ -371,25 +414,53 @@ function createUselessGlobalFlagVisitor(context: Rule.RuleContext) {
                     countOfUsedInExecOrTest++
                     if (countOfUsedInExecOrTest > 1) {
                         // Multiple `exec` or `test` have been used.
-                        return false
+                        return null
                     }
                     continue
                 } else {
                     // Used `exec` or` test` in different scopes. It may be called multiple times.
-                    return false
+                    return null
                 }
             }
         }
         // Need to report
-        return true
+        return buildReportData(regExpReference)
+    }
+
+    /**
+     * Build the report data.
+     */
+    function buildReportData(regExpReference: RegExpReference) {
+        const usedNodes = regExpReference.getUsedNodes()
+        if (usedNodes.size === 1) {
+            const [[method, nodes]] = usedNodes
+            // Make it fixable only if the regex is used directly.
+            const fixable =
+                nodes.length === 1 && nodes.includes(regExpReference.defineNode)
+            if (method === "split") {
+                return {
+                    kind: ReportKind.usedOnlyInSplit,
+                    fixable,
+                }
+            }
+            if (method === "search") {
+                return { kind: ReportKind.usedOnlyInSearch, fixable }
+            }
+            if (method === "exec" && nodes.length === 1)
+                return { kind: ReportKind.usedOnlyOnceInExec, fixable }
+            if (method === "test" && nodes.length === 1)
+                return { kind: ReportKind.usedOnlyOnceInTest, fixable }
+        }
+        return { kind: ReportKind.unused }
     }
 
     return createRegExpReferenceExtractVisitor(context, {
         flag: "global",
         exit(regExpReferenceList) {
             for (const regExpReference of regExpReferenceList) {
-                if (isNeedReport(regExpReference)) {
-                    reportUselessGlobalFlag(regExpReference)
+                const report = getReportData(regExpReference)
+                if (report != null) {
+                    reportUselessGlobalFlag(regExpReference, report)
                 }
             }
         },
@@ -401,45 +472,78 @@ function createUselessGlobalFlagVisitor(context: Rule.RuleContext) {
                 "replaceAll",
             ])
         },
+        strictTypes,
     })
 }
 
 /**
  * Create visitor for verify unnecessary y flag
  */
-function createUselessStickyFlagVisitor(context: Rule.RuleContext) {
+function createUselessStickyFlagVisitor(
+    context: Rule.RuleContext,
+    strictTypes: boolean,
+) {
+    type ReportData = { fixable?: boolean }
+
     /**
      * Report for useless sticky flag
      */
-    function reportUselessGlobalFlag(regExpReference: RegExpReference) {
+    function reportUselessStickyFlag(
+        regExpReference: RegExpReference,
+        data: ReportData,
+    ) {
         const node = regExpReference.defineNode
         context.report({
             node,
             loc: getFlagLocation(context, node, "y"),
             messageId: "uselessStickyFlag",
+            fix: data.fixable
+                ? fixRemoveFlag(regExpReference.regExpContext, "y")
+                : null,
         })
     }
 
     /**
-     * Checks Check whether the flag needs to be reported.
+     * Checks if it needs to be reported and returns the report data if it needs to be reported.
      */
-    function isNeedReport(regExpReference: RegExpReference): boolean {
+    function getReportData(
+        regExpReference: RegExpReference,
+    ): null | ReportData {
         for (const readData of regExpReference.readNodes.values()) {
             if (!readData.marked) {
                 // There is expression node whose usage is unknown.
-                return false
+                return null
+            }
+        } // Need to report
+        return buildReportData(regExpReference)
+    }
+
+    /**
+     * Build the report data.
+     */
+    function buildReportData(regExpReference: RegExpReference) {
+        const usedNodes = regExpReference.getUsedNodes()
+        if (usedNodes.size === 1) {
+            const [[method, nodes]] = usedNodes
+            // Make it fixable only if the regex is used directly.
+            const fixable =
+                nodes.length === 1 && nodes.includes(regExpReference.defineNode)
+            if (method === "split") {
+                return {
+                    fixable,
+                }
             }
         }
-        // Need to report
-        return true
+        return {}
     }
 
     return createRegExpReferenceExtractVisitor(context, {
         flag: "sticky",
         exit(regExpReferenceList) {
             for (const regExpReference of regExpReferenceList) {
-                if (isNeedReport(regExpReference)) {
-                    reportUselessGlobalFlag(regExpReference)
+                const report = getReportData(regExpReference)
+                if (report != null) {
+                    reportUselessStickyFlag(regExpReference, report)
                 }
             }
         },
@@ -454,6 +558,7 @@ function createUselessStickyFlagVisitor(context: Rule.RuleContext) {
                 "replaceAll",
             ])
         },
+        strictTypes,
     })
 }
 
@@ -466,10 +571,12 @@ function createRegExpReferenceExtractVisitor(
         flag,
         exit,
         isUsedShortCircuit,
+        strictTypes,
     }: {
         flag: "global" | "sticky"
         exit: (list: RegExpReference[]) => void
         isUsedShortCircuit: (regExpReference: RegExpReference) => boolean
+        strictTypes: boolean
     },
 ) {
     const typeTracer = createTypeTracker(context)
@@ -488,7 +595,11 @@ function createRegExpReferenceExtractVisitor(
         if (regExpReference == null || isUsedShortCircuit(regExpReference)) {
             return
         }
-        if (!typeTracer.isString(node.callee.object)) {
+        if (
+            strictTypes
+                ? !typeTracer.isString(node.callee.object)
+                : !typeTracer.maybeString(node.callee.object)
+        ) {
             regExpReference.markAsCannotTrack()
             return
         }
@@ -526,9 +637,13 @@ function createRegExpReferenceExtractVisitor(
 
     return compositingVisitors(
         defineRegexpVisitor(context, {
-            createVisitor({ flags, regexpNode }: RegExpContext) {
+            createVisitor(regExpContext: RegExpContext) {
+                const { flags, regexpNode } = regExpContext
                 if (flags[flag]) {
-                    const regExpReference = new RegExpReference(regexpNode)
+                    const regExpReference = new RegExpReference(
+                        regExpContext,
+                        regexpNode,
+                    )
                     regExpReferenceList.push(regExpReference)
                     regExpReferenceMap.set(regexpNode, regExpReference)
                     for (const ref of extractExpressionReferences(
@@ -647,13 +762,44 @@ function createRegExpReferenceExtractVisitor(
                     const regExpReference = regExpReferenceMap.get(
                         node.arguments[0],
                     )
-                    regExpReference?.markAsUsed(node.callee.property.name)
+                    regExpReference?.markAsUsed(
+                        node.callee.property.name,
+                        node.arguments[0],
+                    )
                 }
             },
         },
     )
 }
 
+/**
+ * Parse option
+ */
+function parseOption(
+    userOption:
+        | {
+              ignore?: ("i" | "m" | "s" | "g" | "y")[]
+              strictTypes?: boolean
+          }
+        | undefined,
+) {
+    const ignore = new Set<"i" | "m" | "s" | "g" | "y">()
+    let strictTypes = true
+    if (userOption) {
+        for (const i of userOption.ignore ?? []) {
+            ignore.add(i)
+        }
+        if (userOption.strictTypes != null) {
+            strictTypes = userOption.strictTypes
+        }
+    }
+
+    return {
+        ignore,
+        strictTypes,
+    }
+}
+
 export default createRule("no-useless-flag", {
     meta: {
         docs: {
@@ -676,6 +822,7 @@ export default createRule("no-useless-flag", {
                         },
                         uniqueItems: true,
                     },
+                    strictTypes: { type: "boolean" },
                 },
                 additionalProperties: false,
             },
@@ -688,16 +835,22 @@ export default createRule("no-useless-flag", {
             uselessDotAllFlag:
                 "The 's' flag is unnecessary because the pattern does not contain dots (.).",
             uselessGlobalFlag:
-                "The 'g' flag is unnecessary because not using global testing.",
+                "The 'g' flag is unnecessary because the regex does not use global search.",
+            uselessGlobalFlagForTest:
+                "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.test'.",
+            uselessGlobalFlagForExec:
+                "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.exec'.",
+            uselessGlobalFlagForSplit:
+                "The 'g' flag is unnecessary because 'String.prototype.split' ignores the 'g' flag.",
+            uselessGlobalFlagForSearch:
+                "The 'g' flag is unnecessary because 'String.prototype.search' ignores the 'g' flag.",
             uselessStickyFlag:
-                "The 'y' flag is unnecessary because not using sticky search.",
+                "The 'y' flag is unnecessary because 'String.prototype.split' ignores the 'y' flag.",
         },
         type: "suggestion", // "problem",
     },
     create(context) {
-        const ignore = new Set<"i" | "m" | "s" | "g" | "y">(
-            context.options[0]?.ignore ?? [],
-        )
+        const { ignore, strictTypes } = parseOption(context.options[0])
 
         let visitor: RuleListener = {}
         if (!ignore.has("i")) {
@@ -721,13 +874,13 @@ export default createRule("no-useless-flag", {
         if (!ignore.has("g")) {
             visitor = compositingVisitors(
                 visitor,
-                createUselessGlobalFlagVisitor(context),
+                createUselessGlobalFlagVisitor(context, strictTypes),
             )
         }
         if (!ignore.has("y")) {
             visitor = compositingVisitors(
                 visitor,
-                createUselessStickyFlagVisitor(context),
+                createUselessStickyFlagVisitor(context, strictTypes),
             )
         }
         return visitor
diff --git a/lib/rules/prefer-t.ts b/lib/rules/prefer-t.ts
index 29b8739..a91fce8 100644
--- a/lib/rules/prefer-t.ts
+++ b/lib/rules/prefer-t.ts
@@ -7,7 +7,8 @@ export default createRule("prefer-t", {
         docs: {
             description: "enforce using `\\t`",
             category: "Stylistic Issues",
-            recommended: true,
+            recommended: false,
+            replacedBy: ["control-character-escape"],
         },
         fixable: "code",
         schema: [],
@@ -15,6 +16,7 @@ export default createRule("prefer-t", {
             unexpected: "Unexpected character '{{expr}}'. Use '\\t' instead.",
         },
         type: "suggestion", // "problem",
+        deprecated: true,
     },
     create(context) {
         /**
@@ -22,7 +24,7 @@ export default createRule("prefer-t", {
          */
         function createVisitor(
             regexpContext: RegExpContext,
-            arrows: string[],
+            allows: string[],
         ): RegExpVisitor.Handlers {
             const { node, getRegexpLocation, fixReplaceNode } = regexpContext
             return {
@@ -30,7 +32,7 @@ export default createRule("prefer-t", {
                     if (
                         cNode.value === CP_TAB &&
                         cNode.raw !== "\\t" &&
-                        !arrows.includes(cNode.raw)
+                        !allows.includes(cNode.raw)
                     ) {
                         context.report({
                             node,
diff --git a/lib/rules/sort-character-class-elements.ts b/lib/rules/sort-character-class-elements.ts
index f03a642..565e3be 100644
--- a/lib/rules/sort-character-class-elements.ts
+++ b/lib/rules/sort-character-class-elements.ts
@@ -11,6 +11,7 @@ import {
     createRule,
     defineRegexpVisitor,
 } from "../utils"
+import { mention } from "../utils/mention"
 
 type CharacterClassElementKind = "\\w" | "\\d" | "\\s" | "\\p" | "*"
 const DEFAULT_ORDER: CharacterClassElementKind[] = [
@@ -61,7 +62,7 @@ export default createRule("sort-character-class-elements", {
         ],
         messages: {
             sortElements:
-                "Expected character class elements to be in ascending order. '{{next}}' should be before '{{prev}}'.",
+                "Expected character class elements to be in ascending order. {{next}} should be before {{prev}}.",
         },
         type: "layout",
     },
@@ -107,8 +108,8 @@ export default createRule("sort-character-class-elements", {
                                     loc: getRegexpLocation(next),
                                     messageId: "sortElements",
                                     data: {
-                                        next: next.raw,
-                                        prev: moveTarget.raw,
+                                        next: mention(next),
+                                        prev: mention(moveTarget),
                                     },
                                     *fix(fixer) {
                                         const nextRange = patternSource.getReplaceRange(
diff --git a/lib/rules/sort-flags.ts b/lib/rules/sort-flags.ts
index 645c8ad..20f5969 100644
--- a/lib/rules/sort-flags.ts
+++ b/lib/rules/sort-flags.ts
@@ -1,5 +1,4 @@
-import type { RegExpVisitor } from "regexpp/visitor"
-import type { RegExpContext } from "../utils"
+import type { RegExpContext, UnparsableRegExpContext } from "../utils"
 import { createRule, defineRegexpVisitor } from "../utils"
 
 export default createRule("sort-flags", {
@@ -15,7 +14,7 @@ export default createRule("sort-flags", {
         schema: [],
         messages: {
             sortFlags:
-                "The flags '{{flags}}' should in the order '{{sortedFlags}}'.",
+                "The flags '{{flags}}' should be in the order '{{sortedFlags}}'.",
         },
         type: "suggestion", // "problem",
     },
@@ -29,16 +28,14 @@ export default createRule("sort-flags", {
                 .join("")
         }
 
-        /**
-         * Create visitor
-         */
-        function createVisitor({
+        /** The logic of this rule */
+        function visit({
             regexpNode,
             flagsString,
             ownsFlags,
             getFlagsLocation,
             fixReplaceFlags,
-        }: RegExpContext): RegExpVisitor.Handlers {
+        }: RegExpContext | UnparsableRegExpContext) {
             if (flagsString && ownsFlags) {
                 const sortedFlags = sortFlags(flagsString)
                 if (flagsString !== sortedFlags) {
@@ -51,12 +48,15 @@ export default createRule("sort-flags", {
                     })
                 }
             }
-
-            return {} // not visit RegExpNodes
         }
 
         return defineRegexpVisitor(context, {
-            createVisitor,
+            createVisitor(regexpContext) {
+                visit(regexpContext)
+                return {}
+            },
+            visitInvalid: visit,
+            visitUnknown: visit,
         })
     },
 })
diff --git a/lib/utils/ast-utils/pattern-source.ts b/lib/utils/ast-utils/pattern-source.ts
index a0d7820..bb6aeb6 100644
--- a/lib/utils/ast-utils/pattern-source.ts
+++ b/lib/utils/ast-utils/pattern-source.ts
@@ -24,7 +24,7 @@ export interface PatternRange {
 export class PatternReplaceRange {
     public range: AST.Range
 
-    public type: "RegExp" | "String"
+    public type: "RegExp" | "SingleQuotedString" | "DoubleQuotedString"
 
     public constructor(range: AST.Range, type: PatternReplaceRange["type"]) {
         if (!range || range[0] < 0 || range[0] > range[1]) {
@@ -60,7 +60,11 @@ export class PatternReplaceRange {
             const astRange = getStringValueRange(sourceCode, node, start, end)
 
             if (astRange) {
-                return new PatternReplaceRange(astRange, "String")
+                const quote = sourceCode.text[node.range[0]]
+                return new PatternReplaceRange(
+                    astRange,
+                    quote === "'" ? "SingleQuotedString" : "DoubleQuotedString",
+                )
             }
         }
         return null
@@ -71,15 +75,23 @@ export class PatternReplaceRange {
     }
 
     public escape(text: string): string {
-        if (this.type === "String") {
-            return text
+        if (
+            this.type === "DoubleQuotedString" ||
+            this.type === "SingleQuotedString"
+        ) {
+            const base = text
                 .replace(/\\/g, "\\\\")
                 .replace(/\n/g, "\\n")
                 .replace(/\r/g, "\\r")
                 .replace(/\t/g, "\\t")
+
+            if (this.type === "DoubleQuotedString") {
+                return base.replace(/"/g, '\\"')
+            }
+            return base.replace(/'/g, "\\'")
         }
 
-        return text
+        return text.replace(/\n/g, "\\n").replace(/\r/g, "\\r")
     }
 
     public replace(fixer: Rule.RuleFixer, text: string): Rule.Fix {
diff --git a/lib/utils/index.ts b/lib/utils/index.ts
index 7f7369a..852c870 100644
--- a/lib/utils/index.ts
+++ b/lib/utils/index.ts
@@ -22,7 +22,8 @@ export type ToCharSet = (
     node: ToCharSetElement,
     flags?: ReadonlyFlags,
 ) => CharSet
-type RegExpHelpersBase = {
+
+type RegExpContextBase = {
     toCharSet: ToCharSet
 
     /**
@@ -83,22 +84,53 @@ type RegExpHelpersBase = {
 
     flags: ReadonlyFlags
 }
-
 export type RegExpContextForLiteral = {
     node: ESTree.RegExpLiteral
     flagsString: string
     ownsFlags: true
     regexpNode: ESTree.RegExpLiteral
-} & RegExpHelpersBase
+} & RegExpContextBase
 export type RegExpContextForSource = {
     node: ESTree.Expression
     flagsString: string | null
     ownsFlags: boolean
-    regexpNode: ESTree.NewExpression | ESTree.CallExpression
-} & RegExpHelpersBase
+    regexpNode: ESTree.CallExpression
+} & RegExpContextBase
 export type RegExpContext = RegExpContextForLiteral | RegExpContextForSource
 
-type RegexpRule = {
+type UnparsableRegExpContextBase = {
+    node: ESTree.Expression
+    regexpNode: ESTree.RegExpLiteral | ESTree.CallExpression
+
+    flags: ReadonlyFlags
+    flagsString: string | null
+    ownsFlags: boolean
+
+    /**
+     * Creates SourceLocation from the regexp flags
+     *
+     * @returns The SourceLocation
+     */
+    getFlagsLocation: () => AST.SourceLocation
+
+    fixReplaceFlags: (
+        newFlags: string | (() => string | null),
+    ) => (fixer: Rule.RuleFixer) => Rule.Fix[] | Rule.Fix | null
+}
+export type RegExpContextForInvalid = {
+    pattern: string
+    patternSource: PatternSource
+    error: SyntaxError
+} & UnparsableRegExpContextBase
+export type RegExpContextForUnknown = {
+    pattern: null
+    patternSource: null
+} & UnparsableRegExpContextBase
+export type UnparsableRegExpContext =
+    | RegExpContextForInvalid
+    | RegExpContextForUnknown
+
+type ParsableRegexpRule = {
     createLiteralVisitor?: (
         context: RegExpContextForLiteral,
     ) => RegExpVisitor.Handlers
@@ -106,6 +138,12 @@ type RegexpRule = {
         context: RegExpContextForSource,
     ) => RegExpVisitor.Handlers
 }
+type UnparsableRegexpRule = {
+    visitInvalid?: (context: RegExpContextForInvalid) => void
+    visitUnknown?: (context: RegExpContextForUnknown) => void
+}
+type RegexpRule = ParsableRegexpRule & UnparsableRegexpRule
+
 const regexpRules = new WeakMap<ESTree.Program, RegexpRule[]>()
 
 export const FLAG_GLOBAL = "g"
@@ -161,16 +199,20 @@ export function createRule(
     }
 }
 
+type DefineRegexpVisitorRule = UnparsableRegexpRule &
+    (
+        | ParsableRegexpRule
+        | {
+              createVisitor: (context: RegExpContext) => RegExpVisitor.Handlers
+          }
+    )
+
 /**
  * Define the RegExp visitor rule.
  */
 export function defineRegexpVisitor(
     context: Rule.RuleContext,
-    rule:
-        | RegexpRule
-        | {
-              createVisitor?: (context: RegExpContext) => RegExpVisitor.Handlers
-          },
+    rule: DefineRegexpVisitorRule,
 ): RuleListener {
     const programNode = context.getSourceCode().ast
 
@@ -186,20 +228,22 @@ export function defineRegexpVisitor(
         visitor = {}
     }
 
-    const createLiteralVisitor =
-        "createVisitor" in rule
-            ? rule.createVisitor
-            : "createLiteralVisitor" in rule
-            ? rule.createLiteralVisitor
-            : undefined
-    const createSourceVisitor =
-        "createVisitor" in rule
-            ? rule.createVisitor
-            : "createSourceVisitor" in rule
-            ? rule.createSourceVisitor
-            : undefined
+    let createLiteralVisitor = undefined
+    let createSourceVisitor = undefined
+    if ("createVisitor" in rule) {
+        createLiteralVisitor = rule.createVisitor
+        createSourceVisitor = rule.createVisitor
+    } else {
+        createLiteralVisitor = rule.createLiteralVisitor
+        createSourceVisitor = rule.createSourceVisitor
+    }
 
-    rules.push({ createLiteralVisitor, createSourceVisitor })
+    rules.push({
+        createLiteralVisitor,
+        createSourceVisitor,
+        visitInvalid: rule.visitInvalid,
+        visitUnknown: rule.visitUnknown,
+    })
 
     return visitor
 }
@@ -214,20 +258,38 @@ function buildRegexpVisitor(
 
     /**
      * Verify a given regular expression.
-     * @param pattern The regular expression pattern to verify.
+     * @param patternNode The regular expression pattern to verify.
      * @param flags The flags of the regular expression.
      */
     function verify(
-        exprNode: ESTree.Expression,
+        patternNode: ESTree.Expression,
         regexpNode: ESTree.RegExpLiteral | ESTree.CallExpression,
-        patternSource: PatternSource,
-        flags: ReadonlyFlags,
-        createVisitors: (
-            helpers: RegExpHelpersBase,
-        ) => IterableIterator<RegExpVisitor.Handlers>,
+        patternSource: PatternSource | null,
+        flagsString: string | null,
+        ownsFlags: boolean,
+        createVisitor: (helpers: RegExpContextBase) => RegExpVisitor.Handlers,
     ) {
-        let parsedPattern
+        const flags = parseFlags(flagsString || "")
 
+        if (!patternSource) {
+            visitUnknownForRules(rules, {
+                pattern: null,
+                patternSource: null,
+
+                ...buildUnparsableRegExpContextBase({
+                    patternSource,
+                    patternNode,
+                    regexpNode,
+                    context,
+                    flags,
+                    flagsString,
+                    ownsFlags,
+                }),
+            })
+            return
+        }
+
+        let parsedPattern
         try {
             parsedPattern = parser.parsePattern(
                 patternSource.value,
@@ -235,46 +297,37 @@ function buildRegexpVisitor(
                 patternSource.value.length,
                 flags.unicode,
             )
-        } catch {
-            // Ignore regular expressions with syntax errors
+        } catch (error: unknown) {
+            if (error instanceof SyntaxError) {
+                // regex with syntax error
+                visitInvalidForRules(rules, {
+                    pattern: patternSource.value,
+                    patternSource,
+                    error,
+
+                    ...buildUnparsableRegExpContextBase({
+                        patternSource,
+                        patternNode,
+                        regexpNode,
+                        context,
+                        flags,
+                        flagsString,
+                        ownsFlags,
+                    }),
+                })
+            }
             return
         }
 
-        const helpers = buildRegExpHelperBase({
+        const helpers = buildRegExpContextBase({
             patternSource,
-            exprNode,
             regexpNode,
             context,
             flags,
             parsedPattern,
         })
 
-        const handler: RegExpVisitor.Handlers = {}
-        for (const visitor of createVisitors(helpers)) {
-            for (const [key, fn] of Object.entries(visitor) as [
-                keyof RegExpVisitor.Handlers,
-                RegExpVisitor.Handlers[keyof RegExpVisitor.Handlers],
-            ][]) {
-                const orig = handler[key]
-                if (orig) {
-                    handler[key] = (
-                        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
-                        node: any,
-                    ) => {
-                        orig(node)
-                        fn!(node)
-                    }
-                } else {
-                    // @ts-expect-error -- ignore
-                    handler[key] = fn
-                }
-            }
-        }
-        if (Object.keys(handler).length === 0) {
-            return
-        }
-
-        visitRegExpAST(parsedPattern, handler)
+        visitRegExpAST(parsedPattern, createVisitor(helpers))
     }
 
     return {
@@ -284,21 +337,15 @@ function buildRegexpVisitor(
                 return
             }
             const flagsString = node.regex.flags
-            const flags = parseFlags(flagsString)
             const patternSource = PatternSource.fromRegExpLiteral(context, node)
-            verify(node, node, patternSource, flags, function* (helpers) {
-                const regexpContext: RegExpContextForLiteral = {
+            verify(node, node, patternSource, flagsString, true, (base) => {
+                return createLiteralVisitorFromRules(rules, {
                     node,
                     flagsString,
                     ownsFlags: true,
                     regexpNode: node,
-                    ...helpers,
-                }
-                for (const rule of rules) {
-                    if (rule.createLiteralVisitor) {
-                        yield rule.createLiteralVisitor(regexpContext)
-                    }
-                }
+                    ...base,
+                })
             })
         },
         Program() {
@@ -308,18 +355,16 @@ function buildRegexpVisitor(
             // E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
             //       `const {RegExp: a} = window; new a()`, etc...
             const regexpDataList: {
-                newOrCall: ESTree.NewExpression | ESTree.CallExpression
+                call: ESTree.CallExpression
                 patternNode: ESTree.Expression
-                patternSource: PatternSource
+                patternSource: PatternSource | null
                 flagsString: string | null
                 ownsFlags: boolean
             }[] = []
             for (const { node } of tracker.iterateGlobalReferences({
                 RegExp: { [CALL]: true, [CONSTRUCT]: true },
             })) {
-                const newOrCall = node as
-                    | ESTree.NewExpression
-                    | ESTree.CallExpression
+                const newOrCall = node as ESTree.CallExpression
                 const [patternNode, flagsNode] = newOrCall.arguments
                 if (!patternNode || patternNode.type === "SpreadElement") {
                     continue
@@ -329,54 +374,47 @@ function buildRegexpVisitor(
                     context,
                     patternNode,
                 )
-                if (!patternSource) {
-                    continue
-                }
 
                 let flagsString = null
                 let ownsFlags = false
                 if (flagsNode && flagsNode.type !== "SpreadElement") {
                     flagsString = getStringIfConstant(context, flagsNode)
                     ownsFlags = isStringLiteral(flagsNode)
-                } else if (patternSource.regexpValue) {
+                } else if (patternSource && patternSource.regexpValue) {
                     flagsString = patternSource.regexpValue.flags
                     ownsFlags = Boolean(patternSource.regexpValue.ownedNode)
                 }
 
                 regexpDataList.push({
-                    newOrCall,
+                    call: newOrCall,
                     patternNode,
                     patternSource,
                     flagsString,
                     ownsFlags,
                 })
             }
+
             for (const {
-                newOrCall,
+                call,
                 patternNode,
                 patternSource,
                 flagsString,
                 ownsFlags,
             } of regexpDataList) {
-                const flags = parseFlags(flagsString || "")
                 verify(
                     patternNode,
-                    newOrCall,
+                    call,
                     patternSource,
-                    flags,
-                    function* (helpers) {
-                        const regexpContext: RegExpContextForSource = {
+                    flagsString,
+                    ownsFlags,
+                    (base) => {
+                        return createSourceVisitorFromRules(rules, {
                             node: patternNode,
                             flagsString,
                             ownsFlags,
-                            regexpNode: newOrCall,
-                            ...helpers,
-                        }
-                        for (const rule of rules) {
-                            if (rule.createSourceVisitor) {
-                                yield rule.createSourceVisitor(regexpContext)
-                            }
-                        }
+                            regexpNode: call,
+                            ...base,
+                        })
                     },
                 )
             }
@@ -384,6 +422,86 @@ function buildRegexpVisitor(
     }
 }
 
+/** Create a visitor handler from the given rules */
+function createLiteralVisitorFromRules(
+    rules: Iterable<RegexpRule>,
+    context: RegExpContextForLiteral,
+): RegExpVisitor.Handlers {
+    const handlers: RegExpVisitor.Handlers[] = []
+    for (const rule of rules) {
+        if (rule.createLiteralVisitor) {
+            handlers.push(rule.createLiteralVisitor(context))
+        }
+    }
+    return composeRegExpVisitors(handlers)
+}
+
+/** Create a visitor handler from the given rules */
+function createSourceVisitorFromRules(
+    rules: Iterable<RegexpRule>,
+    context: RegExpContextForSource,
+): RegExpVisitor.Handlers {
+    const handlers: RegExpVisitor.Handlers[] = []
+    for (const rule of rules) {
+        if (rule.createSourceVisitor) {
+            handlers.push(rule.createSourceVisitor(context))
+        }
+    }
+    return composeRegExpVisitors(handlers)
+}
+
+/** Calls a visit function for all the given rules */
+function visitInvalidForRules(
+    rules: Iterable<RegexpRule>,
+    context: RegExpContextForInvalid,
+): void {
+    for (const rule of rules) {
+        rule.visitInvalid?.(context)
+    }
+}
+
+/** Calls a visit function for all the given rules */
+function visitUnknownForRules(
+    rules: Iterable<RegexpRule>,
+    context: RegExpContextForUnknown,
+): void {
+    for (const rule of rules) {
+        rule.visitUnknown?.(context)
+    }
+}
+
+/** Returns a single visitor handler that executes all the given handlers. */
+function composeRegExpVisitors(
+    handlers: Iterable<RegExpVisitor.Handlers>,
+): RegExpVisitor.Handlers {
+    const handler: RegExpVisitor.Handlers = {}
+
+    for (const visitor of handlers) {
+        const entries = Object.entries(visitor) as [
+            keyof RegExpVisitor.Handlers,
+            RegExpVisitor.Handlers[keyof RegExpVisitor.Handlers],
+        ][]
+
+        for (const [key, fn] of entries) {
+            const orig = handler[key]
+            if (orig) {
+                handler[key] = (
+                    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
+                    node: any,
+                ) => {
+                    orig(node)
+                    fn!(node)
+                }
+            } else {
+                // @ts-expect-error -- ignore
+                handler[key] = fn
+            }
+        }
+    }
+
+    return handler
+}
+
 /**
  * Composite all given visitors.
  */
@@ -410,23 +528,21 @@ export function compositingVisitors(
 }
 
 /**
- * Build RegExpHelperBase
+ * Build RegExpContextBase
  */
-function buildRegExpHelperBase({
+function buildRegExpContextBase({
     patternSource,
-    exprNode,
     regexpNode,
     context,
     flags,
     parsedPattern,
 }: {
     patternSource: PatternSource
-    exprNode: ESTree.Expression
     regexpNode: ESTree.CallExpression | ESTree.RegExpLiteral
     context: Rule.RuleContext
     flags: ReadonlyFlags
     parsedPattern: Pattern
-}): RegExpHelpersBase {
+}): RegExpContextBase {
     const sourceCode = context.getSourceCode()
 
     const cacheCharSet = new WeakMap<ToCharSetElement, CharSet>()
@@ -447,9 +563,6 @@ function buildRegExpHelperBase({
             return charSet
         },
         getRegexpLocation: (range, offsets) => {
-            if (!patternSource) {
-                return exprNode.loc!
-            }
             if (offsets) {
                 return patternSource.getAstLocation({
                     start: range.start + offsets[0],
@@ -460,21 +573,12 @@ function buildRegExpHelperBase({
         },
         getFlagsLocation: () => getFlagsLocation(sourceCode, regexpNode),
         fixReplaceNode: (node, replacement) => {
-            if (!patternSource) {
-                return () => null
-            }
             return fixReplaceNode(patternSource, node, replacement)
         },
         fixReplaceQuant: (qNode, replacement) => {
-            if (!patternSource) {
-                return () => null
-            }
             return fixReplaceQuant(patternSource, qNode, replacement)
         },
         fixReplaceFlags: (newFlags) => {
-            if (!patternSource) {
-                return () => null
-            }
             return fixReplaceFlags(patternSource, regexpNode, newFlags)
         },
         getUsageOfPattern: () =>
@@ -487,6 +591,47 @@ function buildRegExpHelperBase({
     }
 }
 
+/**
+ * Build UnparsableRegExpContextBase
+ */
+function buildUnparsableRegExpContextBase({
+    patternSource,
+    patternNode,
+    regexpNode,
+    context,
+    flags,
+    flagsString,
+    ownsFlags,
+}: {
+    patternSource: PatternSource | null
+    patternNode: ESTree.Expression
+    regexpNode: ESTree.CallExpression | ESTree.RegExpLiteral
+    context: Rule.RuleContext
+    flags: ReadonlyFlags
+    flagsString: string | null
+    ownsFlags: boolean
+}): UnparsableRegExpContextBase {
+    const sourceCode = context.getSourceCode()
+
+    return {
+        regexpNode,
+        node: patternNode,
+
+        flags,
+        flagsString,
+        ownsFlags,
+
+        getFlagsLocation: () => getFlagsLocation(sourceCode, regexpNode),
+
+        fixReplaceFlags: (newFlags) => {
+            if (!patternSource) {
+                return () => null
+            }
+            return fixReplaceFlags(patternSource, regexpNode, newFlags)
+        },
+    }
+}
+
 function getFlagsRange(regexpNode: ESTree.RegExpLiteral): AST.Range
 function getFlagsRange(
     regexpNode: ESTree.CallExpression | ESTree.RegExpLiteral,
diff --git a/lib/utils/mention.ts b/lib/utils/mention.ts
new file mode 100644
index 0000000..efa8da0
--- /dev/null
+++ b/lib/utils/mention.ts
@@ -0,0 +1,64 @@
+import type { CharacterClassElement, Node } from "regexpp/ast"
+
+/** Formats the given Unicode code point as `U+0000`. */
+function formatCodePoint(value: number): string {
+    return `U+${value.toString(16).padStart(4, "0")}`
+}
+
+/**
+ * Creates a string that mentions the given character class element.
+ *
+ * This is a specialized version of {@link mention} that will add
+ * character-related information if possible.
+ */
+export function mentionChar(element: CharacterClassElement): string {
+    if (element.type === "Character") {
+        const value = formatCodePoint(element.value)
+        return `'${escape(element.raw)}' (${value})`
+    }
+    if (element.type === "CharacterClassRange") {
+        const min = formatCodePoint(element.min.value)
+        const max = formatCodePoint(element.max.value)
+        return `'${escape(element.raw)}' (${min} - ${max})`
+    }
+
+    return mention(element)
+}
+
+/**
+ * Creates a string that mentions the given character class element.
+ */
+export function mention(element: Node): string {
+    return `'${escape(element.raw)}'`
+}
+
+/** Escape control characters in the given string */
+function escape(value: string): string {
+    // Escaping has to be done in 2 phases:
+    //  1. Escape all backslash-escaped control characters
+    //  2. Escape all non-escape control character
+    return (
+        value
+            .replace(/\\([\s\S])/g, (m, char) => {
+                if (char.charCodeAt(0) < 0x20) {
+                    return escapeControl(char)
+                }
+                return m
+            })
+            // eslint-disable-next-line no-control-regex -- x
+            .replace(/[\0-\x1f]/g, escapeControl)
+    )
+}
+
+/**
+ * Assuming that the given character is a control character, this function will
+ * return an escape sequence for that character.
+ */
+function escapeControl(control: string): string {
+    // we will allow tabs
+    if (control === "\t") return control
+
+    if (control === "\n") return "\\n"
+    if (control === "\r") return "\\r"
+    return `\\x${control.charCodeAt(0).toString(16).padStart(2, "0")}`
+}
diff --git a/lib/utils/rules.ts b/lib/utils/rules.ts
index 3e5ed9b..f391fcd 100644
--- a/lib/utils/rules.ts
+++ b/lib/utils/rules.ts
@@ -13,6 +13,7 @@ import noEmptyCapturingGroup from "../rules/no-empty-capturing-group"
 import noEmptyGroup from "../rules/no-empty-group"
 import noEmptyLookaroundsAssertion from "../rules/no-empty-lookarounds-assertion"
 import noEscapeBackspace from "../rules/no-escape-backspace"
+import noInvalidRegexp from "../rules/no-invalid-regexp"
 import noInvisibleCharacter from "../rules/no-invisible-character"
 import noLazyEnds from "../rules/no-lazy-ends"
 import noLegacyFeatures from "../rules/no-legacy-features"
@@ -80,6 +81,7 @@ export const rules = [
     noEmptyGroup,
     noEmptyLookaroundsAssertion,
     noEscapeBackspace,
+    noInvalidRegexp,
     noInvisibleCharacter,
     noLazyEnds,
     noLegacyFeatures,
diff --git a/lib/utils/type-tracker/index.ts b/lib/utils/type-tracker/index.ts
index ffe04ad..257294d 100644
--- a/lib/utils/type-tracker/index.ts
+++ b/lib/utils/type-tracker/index.ts
@@ -46,6 +46,7 @@ const ts: typeof import("typescript") = (() => {
 
 type TypeTracker = {
     isString: (node: ES.Expression) => boolean
+    maybeString: (node: ES.Expression) => boolean
     isRegExp: (node: ES.Expression) => boolean
     getTypes: (node: ES.Expression) => string[]
 }
@@ -73,6 +74,7 @@ export function createTypeTracker(context: Rule.RuleContext): TypeTracker {
 
     const tracker: TypeTracker = {
         isString,
+        maybeString,
         isRegExp,
         getTypes,
     }
@@ -86,6 +88,19 @@ export function createTypeTracker(context: Rule.RuleContext): TypeTracker {
         return hasType(getType(node), "String")
     }
 
+    /**
+     * Checks if the given node is maybe string.
+     */
+    function maybeString(node: ES.Expression): boolean {
+        if (isString(node)) {
+            return true
+        }
+        if (availableTS) {
+            return false
+        }
+        return getType(node) == null
+    }
+
     /**
      * Checks if the given node is RegExp.
      */


Test patch:
diff --git a/tests/lib/rules/no-invalid-regexp.ts b/tests/lib/rules/no-invalid-regexp.ts
new file mode 100644
index 0000000..7b545b3
--- /dev/null
+++ b/tests/lib/rules/no-invalid-regexp.ts
@@ -0,0 +1,48 @@
+import { RuleTester } from "eslint"
+import rule from "../../../lib/rules/no-invalid-regexp"
+
+const tester = new RuleTester({
+    parserOptions: {
+        ecmaVersion: 2020,
+        sourceType: "module",
+    },
+})
+
+tester.run("no-invalid-regexp", rule as any, {
+    valid: [`/regexp/`, `RegExp("(" + ")")`],
+    invalid: [
+        {
+            code: `RegExp("(")`,
+            errors: [
+                {
+                    message:
+                        "Invalid regular expression: /(/: Unterminated group",
+                    column: 9,
+                    endColumn: 10,
+                },
+            ],
+        },
+        {
+            code: `RegExp("(" + "(")`,
+            errors: [
+                {
+                    message:
+                        "Invalid regular expression: /((/: Unterminated group",
+                    column: 15,
+                    endColumn: 16,
+                },
+            ],
+        },
+        {
+            code: `RegExp("[a-Z] some valid stuff")`,
+            errors: [
+                {
+                    message:
+                        "Invalid regular expression: /[a-Z] some valid stuff/: Range out of order in character class",
+                    column: 12,
+                    endColumn: 14,
+                },
+            ],
+        },
+    ],
+})
diff --git a/tests/lib/rules/no-non-standard-flag.ts b/tests/lib/rules/no-non-standard-flag.ts
index e84347c..5488168 100644
--- a/tests/lib/rules/no-non-standard-flag.ts
+++ b/tests/lib/rules/no-non-standard-flag.ts
@@ -24,5 +24,34 @@ tester.run("no-non-standard-flag", rule as any, {
                 },
             ],
         },
+        {
+            code: `RegExp("foo", "l")`,
+            errors: [
+                {
+                    message: "Unexpected non-standard flag 'l'.",
+                    column: 16,
+                },
+            ],
+        },
+        {
+            // unknown pattern
+            code: `RegExp(someVariable, "l")`,
+            errors: [
+                {
+                    message: "Unexpected non-standard flag 'l'.",
+                    column: 23,
+                },
+            ],
+        },
+        {
+            // invalid pattern
+            code: `RegExp("(", "l")`,
+            errors: [
+                {
+                    message: "Unexpected non-standard flag 'l'.",
+                    column: 14,
+                },
+            ],
+        },
     ],
 })
diff --git a/tests/lib/rules/no-useless-character-class.ts b/tests/lib/rules/no-useless-character-class.ts
index 0f17d47..5bd0b00 100644
--- a/tests/lib/rules/no-useless-character-class.ts
+++ b/tests/lib/rules/no-useless-character-class.ts
@@ -145,5 +145,11 @@ tester.run("no-useless-character-class", rule as any, {
             options: [{ ignores: [] }],
             errors: 18,
         },
+        {
+            code: String.raw`RegExp("[\"]" + '[\']')`,
+            output: String.raw`RegExp("\"" + '\'')`,
+            options: [{ ignores: [] }],
+            errors: 2,
+        },
     ],
 })
diff --git a/tests/lib/rules/no-useless-flag.ts b/tests/lib/rules/no-useless-flag.ts
index 3fae3c2..e5fb339 100644
--- a/tests/lib/rules/no-useless-flag.ts
+++ b/tests/lib/rules/no-useless-flag.ts
@@ -106,6 +106,12 @@ tester.run("no-useless-flag", rule as any, {
         const regex = /foo/g;
         unknown.exec(regex)
         `,
+        `
+        const notStr = {}
+        notStr.split(/foo/g);
+        
+        maybeStr.split(/foo/g);
+        `,
 
         // y
         `
@@ -330,11 +336,38 @@ tester.run("no-useless-flag", rule as any, {
 
             str.search(/foo/g);
             `,
-            output: null,
+            output: `
+            /* ✓ GOOD */
+            const regex1 = /foo/g;
+            const str = 'table football, foosball';
+            while ((array = regex1.exec(str)) !== null) {
+              //
+            }
+
+            const regex2 = /foo/g;
+            regex2.test(string);
+            regex2.test(string);
+
+            str.replace(/foo/g, 'bar');
+            str.replaceAll(/foo/g, 'bar');
+
+            /* ✗ BAD */
+            /foo/.test(string);
+            const regex3 = /foo/g;
+            regex3.test(string); // You have used it only once.
+
+            /foo/.exec(string);
+            const regex4 = /foo/g;
+            regex4.exec(string); // You have used it only once.
+
+            new RegExp('foo', '').test(string);
+
+            str.search(/foo/);
+            `,
             errors: [
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.test'.",
                     line: 17,
                     column: 18,
                     endLine: 17,
@@ -342,7 +375,7 @@ tester.run("no-useless-flag", rule as any, {
                 },
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.test'.",
                     line: 18,
                     column: 33,
                     endLine: 18,
@@ -350,7 +383,7 @@ tester.run("no-useless-flag", rule as any, {
                 },
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.exec'.",
                     line: 21,
                     column: 18,
                     endLine: 21,
@@ -358,7 +391,7 @@ tester.run("no-useless-flag", rule as any, {
                 },
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.exec'.",
                     line: 22,
                     column: 33,
                     endLine: 22,
@@ -366,7 +399,7 @@ tester.run("no-useless-flag", rule as any, {
                 },
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.test'.",
                     line: 25,
                     column: 31,
                     endLine: 25,
@@ -374,7 +407,7 @@ tester.run("no-useless-flag", rule as any, {
                 },
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because 'String.prototype.search' ignores the 'g' flag.",
                     line: 27,
                     column: 29,
                     endLine: 27,
@@ -386,11 +419,13 @@ tester.run("no-useless-flag", rule as any, {
             code: `
             /foo/g.test(bar);
             `,
-            output: null,
+            output: `
+            /foo/.test(bar);
+            `,
             errors: [
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.test'.",
                     line: 2,
                     column: 18,
                 },
@@ -400,11 +435,13 @@ tester.run("no-useless-flag", rule as any, {
             code: `
             /foo/g.exec(bar);
             `,
-            output: null,
+            output: `
+            /foo/.exec(bar);
+            `,
             errors: [
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.exec'.",
                     line: 2,
                     column: 18,
                 },
@@ -414,11 +451,13 @@ tester.run("no-useless-flag", rule as any, {
             code: `
             new RegExp('foo', 'g').test(bar);
             `,
-            output: null,
+            output: `
+            new RegExp('foo', '').test(bar);
+            `,
             errors: [
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.test'.",
                     line: 2,
                     column: 31,
                 },
@@ -428,11 +467,13 @@ tester.run("no-useless-flag", rule as any, {
             code: `
             "foo foo".search(/foo/g);
             `,
-            output: null,
+            output: `
+            "foo foo".search(/foo/);
+            `,
             errors: [
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because 'String.prototype.search' ignores the 'g' flag.",
                     line: 2,
                     column: 35,
                 },
@@ -447,7 +488,7 @@ tester.run("no-useless-flag", rule as any, {
             errors: [
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.test'.",
                     line: 2,
                     column: 32,
                 },
@@ -464,7 +505,7 @@ tester.run("no-useless-flag", rule as any, {
             errors: [
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.test'.",
                     line: 3,
                     column: 36,
                 },
@@ -484,12 +525,80 @@ tester.run("no-useless-flag", rule as any, {
             errors: [
                 {
                     message:
-                        "The 'g' flag is unnecessary because not using global testing.",
+                        "The 'g' flag is unnecessary because the regex does not use global search.",
                     line: 2,
                     column: 28,
                 },
             ],
         },
+        {
+            code: `
+            const a = /foo/g;
+            'str'.split(a)
+            `,
+            output: null,
+            errors: [
+                {
+                    message:
+                        "The 'g' flag is unnecessary because 'String.prototype.split' ignores the 'g' flag.",
+                    line: 2,
+                    column: 28,
+                },
+            ],
+        },
+        {
+            code: `
+            const a = /foo/g;
+            'str'.split(a)
+            'str'.split(a)
+            `,
+            output: null,
+            errors: [
+                "The 'g' flag is unnecessary because 'String.prototype.split' ignores the 'g' flag.",
+            ],
+        },
+        {
+            code: `
+            const notStr = {}
+            notStr.split(/foo/g);
+
+            maybeStr.split(/foo/g);
+            `,
+            output: `
+            const notStr = {}
+            notStr.split(/foo/g);
+
+            maybeStr.split(/foo/);
+            `,
+            options: [{ strictTypes: false }],
+            errors: [
+                "The 'g' flag is unnecessary because 'String.prototype.split' ignores the 'g' flag.",
+            ],
+        },
+        {
+            code: `
+            /** @param {object} obj */
+            function fn1 (obj) {
+                obj.search(/foo/g);
+            }
+            function fn2 (maybeStr) {
+                maybeStr.split(/foo/g);
+            }
+            `,
+            output: `
+            /** @param {object} obj */
+            function fn1 (obj) {
+                obj.search(/foo/g);
+            }
+            function fn2 (maybeStr) {
+                maybeStr.split(/foo/);
+            }
+            `,
+            options: [{ strictTypes: false }],
+            errors: [
+                "The 'g' flag is unnecessary because 'String.prototype.split' ignores the 'g' flag.",
+            ],
+        },
 
         // y
         {
@@ -513,11 +622,30 @@ tester.run("no-useless-flag", rule as any, {
             /* ✗ BAD */
             str.split(/foo/y);
             `,
-            output: null,
+            output: `
+            /* ✓ GOOD */
+            const regex1 = /foo/y;
+            const str = 'table football, foosball';
+            regex1.lastIndex = 6
+            var array = regex1.exec(str)
+            
+            const regex2 = /foo/y;
+            regex2.test(string);
+            regex2.test(string);
+            
+            str.replace(/foo/y, 'bar');
+            str.replaceAll(/foo/gy, 'bar');
+
+            const regexp3 = /foo/y
+            str.search(regexp3)
+            
+            /* ✗ BAD */
+            str.split(/foo/);
+            `,
             errors: [
                 {
                     message:
-                        "The 'y' flag is unnecessary because not using sticky search.",
+                        "The 'y' flag is unnecessary because 'String.prototype.split' ignores the 'y' flag.",
                     line: 19,
                     column: 28,
                 },
@@ -527,11 +655,13 @@ tester.run("no-useless-flag", rule as any, {
             code: `
             "str".split(/foo/y)
             `,
-            output: null,
+            output: `
+            "str".split(/foo/)
+            `,
             errors: [
                 {
                     message:
-                        "The 'y' flag is unnecessary because not using sticky search.",
+                        "The 'y' flag is unnecessary because 'String.prototype.split' ignores the 'y' flag.",
                     line: 2,
                     column: 30,
                 },
@@ -541,11 +671,13 @@ tester.run("no-useless-flag", rule as any, {
             code: `
             "str".split(new RegExp('foo', 'y'));
             `,
-            output: null,
+            output: `
+            "str".split(new RegExp('foo', ''));
+            `,
             errors: [
                 {
                     message:
-                        "The 'y' flag is unnecessary because not using sticky search.",
+                        "The 'y' flag is unnecessary because 'String.prototype.split' ignores the 'y' flag.",
                     line: 2,
                     column: 43,
                 },
@@ -563,12 +695,30 @@ tester.run("no-useless-flag", rule as any, {
             errors: [
                 {
                     message:
-                        "The 'y' flag is unnecessary because not using sticky search.",
+                        "The 'y' flag is unnecessary because 'String.prototype.split' ignores the 'y' flag.",
                     line: 2,
                     column: 28,
                 },
             ],
         },
+        {
+            code: `
+            const notStr = {}
+            notStr.split(/foo/y);
+
+            maybeStr.split(/foo/y);
+            `,
+            output: `
+            const notStr = {}
+            notStr.split(/foo/y);
+
+            maybeStr.split(/foo/);
+            `,
+            options: [{ strictTypes: false }],
+            errors: [
+                "The 'y' flag is unnecessary because 'String.prototype.split' ignores the 'y' flag.",
+            ],
+        },
 
         // test for RegExp constructor with RegExp arguments
         {
diff --git a/tests/lib/rules/sort-character-class-elements.ts b/tests/lib/rules/sort-character-class-elements.ts
index e97b1ac..1ef4681 100644
--- a/tests/lib/rules/sort-character-class-elements.ts
+++ b/tests/lib/rules/sort-character-class-elements.ts
@@ -217,13 +217,17 @@ tester.run("sort-character-class-elements", rule as any, {
             ],
         },
         {
-            code: String.raw`const s = "[\\d"+"\\w]"
-            new RegExp(s, 'u')`,
-            output: String.raw`const s = "[\\w\\d"+"]"
-            new RegExp(s, 'u')`,
-            options: [{ order: [] }],
-            errors: [
-                "Expected character class elements to be in ascending order. '\\w' should be before '\\d'.",
+            code: String.raw`
+            const jsxWhitespaceChars = " \n\r\t";
+            const matchJsxWhitespaceRegex = new RegExp("([" + jsxWhitespaceChars + "]+)");
+            `,
+            output: String.raw`
+            const jsxWhitespaceChars = "\n \r\t";
+            const matchJsxWhitespaceRegex = new RegExp("([" + jsxWhitespaceChars + "]+)");
+            `,
+            errors: [
+                "Expected character class elements to be in ascending order. '\\n' should be before ' '.",
+                "Expected character class elements to be in ascending order. '\t' should be before ' '.",
             ],
         },
     ],
diff --git a/tests/lib/rules/sort-flags.ts b/tests/lib/rules/sort-flags.ts
index 0f331cf..cabb6fe 100644
--- a/tests/lib/rules/sort-flags.ts
+++ b/tests/lib/rules/sort-flags.ts
@@ -31,7 +31,8 @@ tester.run("sort-flags", rule as any, {
             output: String.raw`/\w/gimsuy`,
             errors: [
                 {
-                    message: "The flags 'yusimg' should in the order 'gimsuy'.",
+                    message:
+                        "The flags 'yusimg' should be in the order 'gimsuy'.",
                     column: 5,
                 },
             ],
@@ -41,7 +42,8 @@ tester.run("sort-flags", rule as any, {
             output: String.raw`new RegExp("\\w", "gimsuy")`,
             errors: [
                 {
-                    message: "The flags 'yusimg' should in the order 'gimsuy'.",
+                    message:
+                        "The flags 'yusimg' should be in the order 'gimsuy'.",
                     column: 20,
                 },
             ],
@@ -52,10 +54,21 @@ tester.run("sort-flags", rule as any, {
             errors: [
                 {
                     message:
-                        "The flags 'yusimgd' should in the order 'dgimsuy'.",
+                        "The flags 'yusimgd' should be in the order 'dgimsuy'.",
                     column: 20,
                 },
             ],
         },
+        {
+            // sort flags even on invalid patterns
+            code: String.raw`new RegExp("\\w)", "ui")`,
+            output: String.raw`new RegExp("\\w)", "iu")`,
+            errors: [
+                {
+                    message: "The flags 'ui' should be in the order 'iu'.",
+                    column: 21,
+                },
+            ],
+        },
     ],
 })