Repository: ota-meshi/eslint-plugin-regexp
Pull request #237 (2021-06-10 01:32:29)

Add `regexp/sort-character-class-elements` rule that same `regexp/order-in-character-class` rule
This PR adds the `regexp/sort-character-class-elements` rule that has the same functionality as the `regexp/order-in-character-class` rule.

related to #236.



Patch:
diff --git a/README.md b/README.md
index 9752a8f..a453454 100644
--- a/README.md
+++ b/README.md
@@ -169,6 +169,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
 | [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: |
 
diff --git a/docs/rules/README.md b/docs/rules/README.md
index eea0228..53656e6 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -83,5 +83,6 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
 | [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: |
diff --git a/docs/rules/order-in-character-class.md b/docs/rules/order-in-character-class.md
index 00cf25f..2f976b9 100644
--- a/docs/rules/order-in-character-class.md
+++ b/docs/rules/order-in-character-class.md
@@ -13,41 +13,10 @@ since: "v0.4.0"
 
 ## :book: Rule Details
 
-This rule checks elements of character classes are sorted.
+This rule is the same as the [regexp/sort-character-class-elements] rule. Use [regexp/sort-character-class-elements] instead.
+Replaced by [regexp/sort-character-class-elements] in v1.0.0, this rule will be marked as **deprecated**.
 
-<eslint-code-block fix>
-
-```js
-/* eslint regexp/order-in-character-class: "error" */
-
-/* ✓ GOOD */
-var foo = /[abcdef]/
-var foo = /[ab-f]/
-
-/* ✗ BAD */
-var foo = /[bcdefa]/
-var foo = /[b-fa]/
-```
-
-</eslint-code-block>
-
-## :wrench: Options
-
-```json5
-{
-  "regexp/order-in-character-class": ["error", {
-    "order": [
-      "\\s", // \s or \S
-      "\\w", // \w or \W
-      "\\d", // \d or \D
-      "\\p", // \p{...} or \P{...}
-      "*", // Others (A character or range of characters or an element you did not specify.)
-    ]
-  }]
-}
-```
-
-- `"order"` ... An array of your preferred order. The default is `["\\s", "\\w", "\\d", "\\p", "*",]`.
+[regexp/sort-character-class-elements]: no-useless-lazy.md
 
 ## :rocket: Version
 
diff --git a/docs/rules/sort-character-class-elements.md b/docs/rules/sort-character-class-elements.md
new file mode 100644
index 0000000..f6973e8
--- /dev/null
+++ b/docs/rules/sort-character-class-elements.md
@@ -0,0 +1,55 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "regexp/sort-character-class-elements"
+description: "enforces elements order in character class"
+---
+# regexp/sort-character-class-elements
+
+> enforces elements order in character class
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- :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
+
+This rule checks elements of character classes are sorted.
+
+<eslint-code-block fix>
+
+```js
+/* eslint regexp/sort-character-class-elements: "error" */
+
+/* ✓ GOOD */
+var foo = /[abcdef]/
+var foo = /[ab-f]/
+
+/* ✗ BAD */
+var foo = /[bcdefa]/
+var foo = /[b-fa]/
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+```json5
+{
+  "regexp/sort-character-class-elements": ["error", {
+    "order": [
+      "\\s", // \s or \S
+      "\\w", // \w or \W
+      "\\d", // \d or \D
+      "\\p", // \p{...} or \P{...}
+      "*", // Others (A character or range of characters or an element you did not specify.)
+    ]
+  }]
+}
+```
+
+- `"order"` ... An array of your preferred order. The default is `["\\s", "\\w", "\\d", "\\p", "*",]`.
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/sort-character-class-elements.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/sort-character-class-elements.ts)
diff --git a/lib/rules/no-unused-capturing-group.ts b/lib/rules/no-unused-capturing-group.ts
index aa1a2a1..b17e263 100644
--- a/lib/rules/no-unused-capturing-group.ts
+++ b/lib/rules/no-unused-capturing-group.ts
@@ -3,24 +3,20 @@ import type {
     CallExpression,
     Expression,
     FunctionExpression,
-    MemberExpression,
-    Node as ESTreeNode,
-    Pattern,
-    Property,
 } from "estree"
 import type { RegExpVisitor } from "regexpp/visitor"
 import type { RegExpContext } from "../utils"
 import { createRule, defineRegexpVisitor, compositingVisitors } from "../utils"
-import type { KnownMethodCall } from "../utils/ast-utils"
+import type { KnownMethodCall, PropertyReference } from "../utils/ast-utils"
 import {
-    findVariable,
     isKnownMethodCall,
     getStaticValue,
+    extractExpressionReferences,
+    extractPropertyReferences,
 } from "../utils/ast-utils"
 import { createTypeTracker } from "../utils/type-tracker"
 import type { CapturingGroup } from "regexpp/ast"
 import { parseReplacementsForString } from "../utils/replacements-utils"
-import type { Rule } from "eslint"
 
 class CapturingData {
     private readonly unused = new Set<CapturingGroup>()
@@ -141,154 +137,6 @@ class CapturingData {
     }
 }
 
-/**
- * Get property from given node
- */
-function getProperty(node: MemberExpression | Property): null | string {
-    if (node.type === "MemberExpression") {
-        if (node.computed) {
-            if (node.property.type === "Literal") {
-                if (
-                    typeof node.property.value === "string" ||
-                    typeof node.property.value === "number"
-                )
-                    return String(node.property.value)
-            }
-        } else if (node.property.type === "Identifier") {
-            return node.property.name
-        }
-        return null
-    }
-    if (node.type === "Property") {
-        if (node.computed) {
-            if (node.key.type === "Literal") {
-                if (
-                    typeof node.key.value === "string" ||
-                    typeof node.key.value === "number"
-                )
-                    return String(node.key.value)
-            }
-        } else if (node.key.type === "Identifier") {
-            return node.key.name
-        }
-        return null
-    }
-    return null
-}
-
-type UsedReference = {
-    ref: string
-    node: Expression | Pattern
-    getSubReferences: () => UsedReference[] | null
-}
-
-/**
- * Extract used references
- */
-function extractUsedReferencesFromPattern(
-    node: Pattern,
-    context: Rule.RuleContext,
-): UsedReference[] | null {
-    const references: UsedReference[] = []
-
-    if (node.type === "ArrayPattern") {
-        for (let index = 0; index < node.elements.length; index++) {
-            const element = node.elements[index]
-            if (!element) {
-                continue
-            }
-            if (element.type === "RestElement") {
-                return null
-            }
-            references.push({
-                ref: String(index),
-                node: element,
-                getSubReferences: () =>
-                    extractUsedReferencesFromPattern(element, context),
-            })
-        }
-        return references
-    }
-    if (node.type === "ObjectPattern") {
-        for (const prop of node.properties) {
-            if (prop.type === "RestElement") {
-                return null
-            }
-            const property = getProperty(prop)
-            if (property == null) return null
-            references.push({
-                ref: property,
-                node: prop.value,
-                getSubReferences: () =>
-                    extractUsedReferencesFromPattern(prop.value, context),
-            })
-        }
-        return references
-    }
-    if (node.type === "AssignmentPattern") {
-        return extractUsedReferencesFromPattern(node.left, context)
-    }
-    if (node.type === "Identifier") {
-        // Track vars
-        const variable = findVariable(context, node)
-        if (!variable) {
-            return null
-        }
-        for (const reference of variable.references) {
-            if (reference.isRead()) {
-                const res = extractUsedReferencesFromExpression(
-                    reference.identifier,
-                    context,
-                )
-                if (res == null) {
-                    return null
-                }
-                references.push(...res)
-            }
-        }
-        return references
-    }
-
-    return null
-}
-
-/**
- * Extract used references
- */
-function extractUsedReferencesFromExpression(
-    node: Expression,
-    context: Rule.RuleContext,
-): UsedReference[] | null {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
-    const parent: ESTreeNode = (node as any).parent
-    if (parent.type === "MemberExpression") {
-        if (parent.object !== node) {
-            return null
-        }
-        const property = getProperty(parent)
-        if (property == null) return null
-        return [
-            {
-                ref: property,
-                node: parent,
-                getSubReferences: () =>
-                    extractUsedReferencesFromExpression(parent, context),
-            },
-        ]
-    } else if (parent.type === "AssignmentExpression") {
-        if (parent.right !== node || parent.operator !== "=") {
-            return null
-        }
-        return extractUsedReferencesFromPattern(parent.left, context)
-    } else if (parent.type === "VariableDeclarator") {
-        if (parent.init !== node) {
-            return null
-        }
-        return extractUsedReferencesFromPattern(parent.id, context)
-    }
-    return null
-}
-
 export default createRule("no-unused-capturing-group", {
     meta: {
         docs: {
@@ -310,31 +158,6 @@ export default createRule("no-unused-capturing-group", {
         const typeTracer = createTypeTracker(context)
         const capturingDataMap = new Map<Expression, CapturingData>()
 
-        /**
-         * Get CapturingData
-         */
-        function getCapturingData(node: Expression): CapturingData | null {
-            const re = capturingDataMap.get(node)
-            if (re) {
-                return re
-            }
-            if (node.type === "Identifier") {
-                const variable = findVariable(context, node)
-                if (variable && variable.defs.length === 1) {
-                    const def = variable.defs[0]
-                    if (
-                        def.type === "Variable" &&
-                        def.parent.kind === "const" &&
-                        def.node.init
-                    ) {
-                        return getCapturingData(def.node.init)
-                    }
-                }
-            }
-
-            return null
-        }
-
         /**
          * Report for unused
          */
@@ -366,7 +189,7 @@ export default createRule("no-unused-capturing-group", {
 
         /** Verify for String.prototype.match() */
         function verifyForMatch(node: KnownMethodCall) {
-            const capturingData = getCapturingData(node.arguments[0])
+            const capturingData = capturingDataMap.get(node.arguments[0])
             if (capturingData == null || capturingData.isAllUsed()) {
                 return
             }
@@ -385,7 +208,7 @@ export default createRule("no-unused-capturing-group", {
 
         /** Verify for String.prototype.search() */
         function verifyForSearch(node: KnownMethodCall) {
-            const capturingData = getCapturingData(node.arguments[0])
+            const capturingData = capturingDataMap.get(node.arguments[0])
             if (capturingData == null || capturingData.isAllUsed()) {
                 return
             }
@@ -399,7 +222,7 @@ export default createRule("no-unused-capturing-group", {
 
         /** Verify for RegExp.prototype.test() */
         function verifyForTest(node: KnownMethodCall) {
-            const capturingData = getCapturingData(node.callee.object)
+            const capturingData = capturingDataMap.get(node.callee.object)
             if (capturingData == null || capturingData.isAllUsed()) {
                 return
             }
@@ -409,7 +232,7 @@ export default createRule("no-unused-capturing-group", {
 
         /** Verify for String.prototype.replace() and String.prototype.replaceAll() */
         function verifyForReplace(node: KnownMethodCall) {
-            const capturingData = getCapturingData(node.arguments[0])
+            const capturingData = capturingDataMap.get(node.arguments[0])
             if (capturingData == null || capturingData.isAllUsed()) {
                 return
             }
@@ -481,7 +304,7 @@ export default createRule("no-unused-capturing-group", {
 
         /** Verify for RegExp.prototype.exec() */
         function verifyForExec(node: KnownMethodCall) {
-            const capturingData = getCapturingData(node.callee.object)
+            const capturingData = capturingDataMap.get(node.callee.object)
             if (capturingData == null || capturingData.isAllUsed()) {
                 return
             }
@@ -494,31 +317,30 @@ export default createRule("no-unused-capturing-group", {
             node: KnownMethodCall,
             capturingData: CapturingData,
         ) {
-            const refs = extractUsedReferencesFromExpression(node, context)
-            if (refs == null) {
-                capturingData.markAsCannotTrack()
-                return
-            }
-            for (const ref of refs) {
-                if (ref.ref === "groups") {
-                    const sub = ref.getSubReferences()
-                    if (sub == null) {
-                        // unknown used as name
-                        capturingData.usedAllNames()
-                    } else {
-                        for (const namedRef of sub) {
-                            capturingData.usedName(namedRef.ref)
+            for (const ref of extractPropertyReferences(node, context)) {
+                if (hasNameRef(ref)) {
+                    if (ref.name === "groups") {
+                        for (const namedRef of ref.extractPropertyReferences()) {
+                            if (hasNameRef(namedRef)) {
+                                capturingData.usedName(namedRef.name)
+                            } else {
+                                // unknown used as name
+                                capturingData.usedAllNames()
+                            }
                         }
+                    } else {
+                        capturingData.usedIndex(Number(ref.name))
                     }
                 } else {
-                    capturingData.usedIndex(Number(ref.ref))
+                    capturingData.markAsCannotTrack()
+                    return
                 }
             }
         }
 
         /** Verify for String.prototype.matchAll() */
         function verifyForMatchAll(node: KnownMethodCall) {
-            const capturingData = getCapturingData(node.arguments[0])
+            const capturingData = capturingDataMap.get(node.arguments[0])
             if (capturingData == null || capturingData.isAllUsed()) {
                 return
             }
@@ -527,93 +349,45 @@ export default createRule("no-unused-capturing-group", {
                 return
             }
             capturingData.markAsUsed()
-            const refs = extractUsedReferencesForIteration(node)
-            if (refs == null) {
-                capturingData.markAsCannotTrack()
-                return
-            }
-            for (const ref of refs) {
-                if (ref.ref === "groups") {
-                    const sub = ref.getSubReferences()
-                    if (sub == null) {
-                        // unknown used as name
-                        capturingData.usedAllNames()
-                    } else {
-                        for (const namedRef of sub) {
-                            capturingData.usedName(namedRef.ref)
-                        }
-                    }
-                } else {
-                    capturingData.usedIndex(Number(ref.ref))
+            for (const iterationRef of extractPropertyReferences(
+                node,
+                context,
+            )) {
+                if (!iterationRef.extractPropertyReferences) {
+                    capturingData.markAsCannotTrack()
+                    return
                 }
-            }
-
-            /**
-             * Extract used references
-             */
-            function extractUsedReferencesForIteration(
-                expr: Expression,
-            ): UsedReference[] | null {
-                // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
-                const parent: ESTreeNode = (expr as any).parent
-                if (parent.type === "AssignmentExpression") {
-                    if (parent.right !== expr || parent.operator !== "=") {
-                        return null
-                    }
-                    return extractUsedReferencesForIdIteration(parent.left)
-                } else if (parent.type === "VariableDeclarator") {
-                    if (parent.init !== expr) {
-                        return null
-                    }
-                    return extractUsedReferencesForIdIteration(parent.id)
-                } else if (parent.type === "ForOfStatement") {
-                    if (parent.right !== expr) {
-                        return null
+                if (hasNameRef(iterationRef)) {
+                    if (Number.isNaN(Number(iterationRef.name))) {
+                        // Not aimed to iteration.
+                        continue
                     }
-                    let left = parent.left
-                    if (left.type === "VariableDeclaration") {
-                        left = left.declarations[0].id
-                    }
-                    return extractUsedReferencesFromPattern(left, context)
                 }
-
-                return null
-            }
-
-            /**
-             * Extract used references
-             */
-            function extractUsedReferencesForIdIteration(
-                ptn: Pattern,
-            ): UsedReference[] | null {
-                if (ptn.type === "Identifier") {
-                    const references = []
-                    // Track vars
-                    const variable = findVariable(context, ptn)
-                    if (!variable) {
-                        return null
-                    }
-                    for (const reference of variable.references) {
-                        if (reference.isRead()) {
-                            const resForId = extractUsedReferencesForIteration(
-                                reference.identifier,
-                            )
-                            if (resForId == null) {
-                                return null
+                for (const ref of iterationRef.extractPropertyReferences()) {
+                    if (hasNameRef(ref)) {
+                        if (ref.name === "groups") {
+                            for (const namedRef of ref.extractPropertyReferences()) {
+                                if (hasNameRef(namedRef)) {
+                                    capturingData.usedName(namedRef.name)
+                                } else {
+                                    // unknown used as name
+                                    capturingData.usedAllNames()
+                                }
                             }
-                            references.push(...resForId)
+                        } else {
+                            capturingData.usedIndex(Number(ref.name))
                         }
+                    } else {
+                        capturingData.markAsCannotTrack()
+                        return
                     }
-                    return references
                 }
-
-                return null
             }
         }
 
         /** Verify for String.prototype.split() */
         function verifyForSplit(node: KnownMethodCall) {
-            const capturingData = getCapturingData(node.arguments[0])
+            const capturingData = capturingDataMap.get(node.arguments[0])
             if (capturingData == null || capturingData.isAllUsed()) {
                 return
             }
@@ -634,6 +408,16 @@ export default createRule("no-unused-capturing-group", {
             const { regexpNode } = regexpContext
             const capturingData = new CapturingData(regexpContext)
             capturingDataMap.set(regexpNode, capturingData)
+            for (const ref of extractExpressionReferences(
+                regexpNode,
+                context,
+            )) {
+                if (ref.type === "argument" || ref.type === "member") {
+                    capturingDataMap.set(ref.node, capturingData)
+                } else {
+                    capturingData.markAsCannotTrack()
+                }
+            }
             return capturingData.visitor()
         }
 
@@ -643,7 +427,9 @@ export default createRule("no-unused-capturing-group", {
             }),
             {
                 "Program:exit"() {
-                    for (const capturingData of capturingDataMap.values()) {
+                    for (const capturingData of new Set(
+                        capturingDataMap.values(),
+                    )) {
                         if (capturingData.isNeedReport()) {
                             reportUnused(capturingData)
                         }
@@ -687,3 +473,10 @@ export default createRule("no-unused-capturing-group", {
         )
     },
 })
+
+/** Checks whether the given reference is a named reference. */
+function hasNameRef(
+    ref: PropertyReference,
+): ref is PropertyReference & { type: "member" | "destructuring" } {
+    return ref.type === "destructuring" || ref.type === "member"
+}
diff --git a/lib/rules/no-useless-flag.ts b/lib/rules/no-useless-flag.ts
index 2085e8f..ac1999c 100644
--- a/lib/rules/no-useless-flag.ts
+++ b/lib/rules/no-useless-flag.ts
@@ -3,14 +3,16 @@ import { compositingVisitors, createRule, defineRegexpVisitor } from "../utils"
 import type {
     CallExpression,
     Expression,
-    Identifier,
     NewExpression,
     Node,
     RegExpLiteral,
     Statement,
 } from "estree"
 import type { KnownMethodCall } from "../utils/ast-utils"
-import { findVariable, isKnownMethodCall, getParent } from "../utils/ast-utils"
+import {
+    isKnownMethodCall,
+    extractExpressionReferences,
+} from "../utils/ast-utils"
 import { createTypeTracker } from "../utils/type-tracker"
 import type { RuleListener } from "../types"
 import type { Rule } from "eslint"
@@ -60,6 +62,7 @@ class RegExpReference {
             replace?: boolean
             replaceAll?: boolean
         }
+        hasUnusedExpression?: boolean
     } = {
         usedIn: {},
         track: true,
@@ -154,26 +157,6 @@ class RegExpReference {
     }
 }
 
-/**
- * If the given expression node is assigned with a variable declaration, it returns the variable name node.
- */
-function getVariableId(node: Expression) {
-    const parent = getParent(node)
-    if (
-        !parent ||
-        parent.type !== "VariableDeclarator" ||
-        parent.init !== node ||
-        parent.id.type !== "Identifier"
-    ) {
-        return null
-    }
-    const decl = getParent(parent)
-    if (decl && decl.type === "VariableDeclaration" && decl.kind === "const") {
-        return parent.id
-    }
-    return null
-}
-
 /**
  * Gets the location for reporting the flag.
  */
@@ -493,29 +476,6 @@ function createRegExpReferenceExtractVisitor(
     const regExpReferenceMap = new Map<Node, RegExpReference>()
     const regExpReferenceList: RegExpReference[] = []
 
-    /**
-     * Extract read references
-     */
-    function extractReadReferences(node: Identifier): Identifier[] {
-        const references: Identifier[] = []
-        const variable = findVariable(context, node)
-        if (!variable) {
-            return references
-        }
-        for (const reference of variable.references) {
-            if (reference.isRead()) {
-                const id = getVariableId(reference.identifier)
-                if (id) {
-                    references.push(...extractReadReferences(id))
-                } else {
-                    references.push(reference.identifier)
-                }
-            }
-        }
-
-        return references
-    }
-
     /** Verify for String.prototype.search() or String.prototype.split() */
     function verifyForSearchOrSplit(
         node: KnownMethodCall,
@@ -568,15 +528,16 @@ function createRegExpReferenceExtractVisitor(
                     const regExpReference = new RegExpReference(regexpNode)
                     regExpReferenceList.push(regExpReference)
                     regExpReferenceMap.set(regexpNode, regExpReference)
-                    const id = getVariableId(regexpNode)
-                    if (id) {
-                        const readReferences = extractReadReferences(id)
-                        for (const ref of readReferences) {
-                            regExpReferenceMap.set(ref, regExpReference)
-                            regExpReference.addReadNode(ref)
+                    for (const ref of extractExpressionReferences(
+                        regexpNode,
+                        context,
+                    )) {
+                        if (ref.type === "argument" || ref.type === "member") {
+                            regExpReferenceMap.set(ref.node, regExpReference)
+                            regExpReference.addReadNode(ref.node)
+                        } else {
+                            regExpReference.markAsCannotTrack()
                         }
-                    } else {
-                        regExpReference.addReadNode(regexpNode)
                     }
                 }
                 return {} // not visit RegExpNodes
diff --git a/lib/rules/order-in-character-class.ts b/lib/rules/order-in-character-class.ts
index 0d1de91..acd27df 100644
--- a/lib/rules/order-in-character-class.ts
+++ b/lib/rules/order-in-character-class.ts
@@ -1,271 +1,19 @@
-import type { RegExpVisitor } from "regexpp/visitor"
-import type {
-    CharacterClass,
-    CharacterClassElement,
-    UnicodePropertyCharacterSet,
-} from "regexpp/ast"
-import type { RegExpContext } from "../utils"
-import {
-    CP_DIGIT_ZERO,
-    CP_SPACE,
-    createRule,
-    defineRegexpVisitor,
-} from "../utils"
+import { createRule } from "../utils"
 
-type CharacterClassElementKind = "\\w" | "\\d" | "\\s" | "\\p" | "*"
-const DEFAULT_ORDER: CharacterClassElementKind[] = [
-    "\\s",
-    "\\w",
-    "\\d",
-    "\\p",
-    "*",
-]
-
-/**
- * Get kind of CharacterClassElement for given CharacterClassElement
- */
-function getCharacterClassElementKind(
-    node: CharacterClassElement,
-): CharacterClassElementKind {
-    if (node.type === "CharacterSet") {
-        return node.kind === "word"
-            ? "\\w"
-            : node.kind === "digit"
-            ? "\\d"
-            : node.kind === "space"
-            ? "\\s"
-            : "\\p"
-    }
-    return "*"
-}
+import sortCharacterClassElements from "./sort-character-class-elements"
 
 export default createRule("order-in-character-class", {
     meta: {
+        ...sortCharacterClassElements.meta,
         docs: {
-            description: "enforces elements order in character class",
-            category: "Stylistic Issues",
+            ...sortCharacterClassElements.meta.docs,
             recommended: false,
+            replacedBy: ["sort-character-class-elements"],
         },
-        fixable: "code",
-        schema: [
-            {
-                type: "object",
-                properties: {
-                    order: {
-                        type: "array",
-                        items: { enum: ["\\w", "\\d", "\\s", "\\p", "*"] },
-                    },
-                },
-                additionalProperties: false,
-            },
-        ],
-        messages: {
-            sortElements:
-                "Expected character class elements to be in ascending order. '{{next}}' should be before '{{prev}}'.",
-        },
-        type: "layout",
+        // TODO Switch to deprecated in the major version.
+        // deprecated: true,
     },
     create(context) {
-        const orderOption: {
-            "*": number
-            "\\w"?: number
-            "\\d"?: number
-            "\\s"?: number
-            "\\p"?: number
-        } = { "*": Infinity }
-
-        ;((context.options[0]?.order ??
-            DEFAULT_ORDER) as CharacterClassElementKind[]).forEach((o, i) => {
-            orderOption[o] = i + 1
-        })
-
-        /**
-         * Create visitor
-         */
-        function createVisitor({
-            node,
-            fixerApplyEscape,
-            getRegexpLocation,
-            getRegexpRange,
-        }: RegExpContext): RegExpVisitor.Handlers {
-            return {
-                onCharacterClassEnter(ccNode) {
-                    const prevList: CharacterClassElement[] = []
-                    for (const next of ccNode.elements) {
-                        if (prevList.length) {
-                            const prev = prevList[0]
-                            if (!isValidOrder(prev, next)) {
-                                let moveTarget = prev
-                                for (const p of prevList) {
-                                    if (isValidOrder(p, next)) {
-                                        break
-                                    } else {
-                                        moveTarget = p
-                                    }
-                                }
-                                context.report({
-                                    node,
-                                    loc: getRegexpLocation(next),
-                                    messageId: "sortElements",
-                                    data: {
-                                        next: next.raw,
-                                        prev: moveTarget.raw,
-                                    },
-                                    *fix(fixer) {
-                                        const nextRange = getRegexpRange(next)
-                                        const targetRange = getRegexpRange(
-                                            moveTarget,
-                                        )
-                                        if (!targetRange || !nextRange) {
-                                            return
-                                        }
-
-                                        yield fixer.insertTextBeforeRange(
-                                            targetRange,
-                                            fixerApplyEscape(
-                                                escapeRaw(next, moveTarget),
-                                            ),
-                                        )
-
-                                        yield fixer.removeRange(nextRange)
-                                    },
-                                })
-                            }
-                        }
-                        prevList.unshift(next)
-                    }
-                },
-            }
-        }
-
-        /* eslint-disable complexity -- X( */
-        /**
-         * Check that the two given CharacterClassElements are in a valid order.
-         */
-        function isValidOrder(
-            /* eslint-enable complexity -- X( */
-            prev: CharacterClassElement,
-            next: CharacterClassElement,
-        ) {
-            const prevKind = getCharacterClassElementKind(prev)
-            const nextKind = getCharacterClassElementKind(next)
-            const prevOrder = orderOption[prevKind] ?? orderOption["*"]
-            const nextOrder = orderOption[nextKind] ?? orderOption["*"]
-            if (prevOrder < nextOrder) {
-                return true
-            } else if (prevOrder > nextOrder) {
-                return false
-            }
-            if (prev.type === "CharacterSet" && prev.kind === "property") {
-                if (next.type === "CharacterSet") {
-                    if (next.kind === "property") {
-                        return isValidOrderForUnicodePropertyCharacterSet(
-                            prev,
-                            next,
-                        )
-                    }
-                    // e.g. /[\p{ASCII}\d]/
-                    return false
-                }
-                // e.g. /[\p{ASCII}a]/
-                return true
-            } else if (
-                next.type === "CharacterSet" &&
-                next.kind === "property"
-            ) {
-                if (prev.type === "CharacterSet") {
-                    // e.g. /[\d\p{ASCII}]/
-                    return true
-                }
-                // e.g. /[a\p{ASCII}]/
-                return false
-            }
-            if (prev.type === "CharacterSet" && next.type === "CharacterSet") {
-                if (prev.kind === "word" && next.kind === "digit") {
-                    return true
-                }
-                if (prev.kind === "digit" && next.kind === "word") {
-                    return false
-                }
-            }
-            const prevCP = getTargetCodePoint(prev)
-            const nextCP = getTargetCodePoint(next)
-            if (prevCP <= nextCP) {
-                return true
-            }
-            return false
-        }
-
-        /**
-         * Check that the two given UnicodePropertyCharacterSet are in a valid order.
-         */
-        function isValidOrderForUnicodePropertyCharacterSet(
-            prev: UnicodePropertyCharacterSet,
-            next: UnicodePropertyCharacterSet,
-        ) {
-            if (prev.key < next.key) {
-                return true
-            } else if (prev.key > next.key) {
-                return false
-            }
-            if (prev.value) {
-                if (next.value) {
-                    if (prev.value <= next.value) {
-                        return true
-                    }
-                    return false
-                }
-                return false
-            }
-            return true
-        }
-
-        /**
-         * Gets the target code point for a given element.
-         */
-        function getTargetCodePoint(
-            node: Exclude<CharacterClassElement, UnicodePropertyCharacterSet>,
-        ) {
-            if (node.type === "CharacterSet") {
-                if (node.kind === "digit" || node.kind === "word") {
-                    return CP_DIGIT_ZERO
-                }
-                if (node.kind === "space") {
-                    return CP_SPACE
-                }
-                return Infinity
-            }
-            if (node.type === "CharacterClassRange") {
-                return node.min.value
-            }
-            return node.value
-        }
-
-        return defineRegexpVisitor(context, {
-            createVisitor,
-        })
+        return sortCharacterClassElements.create(context)
     },
 })
-
-/**
- * get the escape text from the given CharacterClassElement.
- */
-function escapeRaw(node: CharacterClassElement, target: CharacterClassElement) {
-    let raw = node.raw
-    if (raw.startsWith("-")) {
-        const parent = target.parent as CharacterClass
-        const prev = parent.elements[parent.elements.indexOf(target) - 1]
-        if (
-            prev &&
-            (prev.type === "Character" || prev.type === "CharacterSet")
-        ) {
-            raw = `\\${raw}`
-        }
-    }
-    if (target.raw.startsWith("-")) {
-        if (node.type === "Character" || node.type === "CharacterSet") {
-            raw = `${raw}\\`
-        }
-    }
-    return raw
-}
diff --git a/lib/rules/sort-character-class-elements.ts b/lib/rules/sort-character-class-elements.ts
new file mode 100644
index 0000000..b8a46d0
--- /dev/null
+++ b/lib/rules/sort-character-class-elements.ts
@@ -0,0 +1,271 @@
+import type { RegExpVisitor } from "regexpp/visitor"
+import type {
+    CharacterClass,
+    CharacterClassElement,
+    UnicodePropertyCharacterSet,
+} from "regexpp/ast"
+import type { RegExpContext } from "../utils"
+import {
+    CP_DIGIT_ZERO,
+    CP_SPACE,
+    createRule,
+    defineRegexpVisitor,
+} from "../utils"
+
+type CharacterClassElementKind = "\\w" | "\\d" | "\\s" | "\\p" | "*"
+const DEFAULT_ORDER: CharacterClassElementKind[] = [
+    "\\s",
+    "\\w",
+    "\\d",
+    "\\p",
+    "*",
+]
+
+/**
+ * Get kind of CharacterClassElement for given CharacterClassElement
+ */
+function getCharacterClassElementKind(
+    node: CharacterClassElement,
+): CharacterClassElementKind {
+    if (node.type === "CharacterSet") {
+        return node.kind === "word"
+            ? "\\w"
+            : node.kind === "digit"
+            ? "\\d"
+            : node.kind === "space"
+            ? "\\s"
+            : "\\p"
+    }
+    return "*"
+}
+
+export default createRule("sort-character-class-elements", {
+    meta: {
+        docs: {
+            description: "enforces elements order in character class",
+            category: "Stylistic Issues",
+            recommended: false,
+        },
+        fixable: "code",
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    order: {
+                        type: "array",
+                        items: { enum: ["\\w", "\\d", "\\s", "\\p", "*"] },
+                    },
+                },
+                additionalProperties: false,
+            },
+        ],
+        messages: {
+            sortElements:
+                "Expected character class elements to be in ascending order. '{{next}}' should be before '{{prev}}'.",
+        },
+        type: "layout",
+    },
+    create(context) {
+        const orderOption: {
+            "*": number
+            "\\w"?: number
+            "\\d"?: number
+            "\\s"?: number
+            "\\p"?: number
+        } = { "*": Infinity }
+
+        ;((context.options[0]?.order ??
+            DEFAULT_ORDER) as CharacterClassElementKind[]).forEach((o, i) => {
+            orderOption[o] = i + 1
+        })
+
+        /**
+         * Create visitor
+         */
+        function createVisitor({
+            node,
+            fixerApplyEscape,
+            getRegexpLocation,
+            getRegexpRange,
+        }: RegExpContext): RegExpVisitor.Handlers {
+            return {
+                onCharacterClassEnter(ccNode) {
+                    const prevList: CharacterClassElement[] = []
+                    for (const next of ccNode.elements) {
+                        if (prevList.length) {
+                            const prev = prevList[0]
+                            if (!isValidOrder(prev, next)) {
+                                let moveTarget = prev
+                                for (const p of prevList) {
+                                    if (isValidOrder(p, next)) {
+                                        break
+                                    } else {
+                                        moveTarget = p
+                                    }
+                                }
+                                context.report({
+                                    node,
+                                    loc: getRegexpLocation(next),
+                                    messageId: "sortElements",
+                                    data: {
+                                        next: next.raw,
+                                        prev: moveTarget.raw,
+                                    },
+                                    *fix(fixer) {
+                                        const nextRange = getRegexpRange(next)
+                                        const targetRange = getRegexpRange(
+                                            moveTarget,
+                                        )
+                                        if (!targetRange || !nextRange) {
+                                            return
+                                        }
+
+                                        yield fixer.insertTextBeforeRange(
+                                            targetRange,
+                                            fixerApplyEscape(
+                                                escapeRaw(next, moveTarget),
+                                            ),
+                                        )
+
+                                        yield fixer.removeRange(nextRange)
+                                    },
+                                })
+                            }
+                        }
+                        prevList.unshift(next)
+                    }
+                },
+            }
+        }
+
+        /* eslint-disable complexity -- X( */
+        /**
+         * Check that the two given CharacterClassElements are in a valid order.
+         */
+        function isValidOrder(
+            /* eslint-enable complexity -- X( */
+            prev: CharacterClassElement,
+            next: CharacterClassElement,
+        ) {
+            const prevKind = getCharacterClassElementKind(prev)
+            const nextKind = getCharacterClassElementKind(next)
+            const prevOrder = orderOption[prevKind] ?? orderOption["*"]
+            const nextOrder = orderOption[nextKind] ?? orderOption["*"]
+            if (prevOrder < nextOrder) {
+                return true
+            } else if (prevOrder > nextOrder) {
+                return false
+            }
+            if (prev.type === "CharacterSet" && prev.kind === "property") {
+                if (next.type === "CharacterSet") {
+                    if (next.kind === "property") {
+                        return isValidOrderForUnicodePropertyCharacterSet(
+                            prev,
+                            next,
+                        )
+                    }
+                    // e.g. /[\p{ASCII}\d]/
+                    return false
+                }
+                // e.g. /[\p{ASCII}a]/
+                return true
+            } else if (
+                next.type === "CharacterSet" &&
+                next.kind === "property"
+            ) {
+                if (prev.type === "CharacterSet") {
+                    // e.g. /[\d\p{ASCII}]/
+                    return true
+                }
+                // e.g. /[a\p{ASCII}]/
+                return false
+            }
+            if (prev.type === "CharacterSet" && next.type === "CharacterSet") {
+                if (prev.kind === "word" && next.kind === "digit") {
+                    return true
+                }
+                if (prev.kind === "digit" && next.kind === "word") {
+                    return false
+                }
+            }
+            const prevCP = getTargetCodePoint(prev)
+            const nextCP = getTargetCodePoint(next)
+            if (prevCP <= nextCP) {
+                return true
+            }
+            return false
+        }
+
+        /**
+         * Check that the two given UnicodePropertyCharacterSet are in a valid order.
+         */
+        function isValidOrderForUnicodePropertyCharacterSet(
+            prev: UnicodePropertyCharacterSet,
+            next: UnicodePropertyCharacterSet,
+        ) {
+            if (prev.key < next.key) {
+                return true
+            } else if (prev.key > next.key) {
+                return false
+            }
+            if (prev.value) {
+                if (next.value) {
+                    if (prev.value <= next.value) {
+                        return true
+                    }
+                    return false
+                }
+                return false
+            }
+            return true
+        }
+
+        /**
+         * Gets the target code point for a given element.
+         */
+        function getTargetCodePoint(
+            node: Exclude<CharacterClassElement, UnicodePropertyCharacterSet>,
+        ) {
+            if (node.type === "CharacterSet") {
+                if (node.kind === "digit" || node.kind === "word") {
+                    return CP_DIGIT_ZERO
+                }
+                if (node.kind === "space") {
+                    return CP_SPACE
+                }
+                return Infinity
+            }
+            if (node.type === "CharacterClassRange") {
+                return node.min.value
+            }
+            return node.value
+        }
+
+        return defineRegexpVisitor(context, {
+            createVisitor,
+        })
+    },
+})
+
+/**
+ * get the escape text from the given CharacterClassElement.
+ */
+function escapeRaw(node: CharacterClassElement, target: CharacterClassElement) {
+    let raw = node.raw
+    if (raw.startsWith("-")) {
+        const parent = target.parent as CharacterClass
+        const prev = parent.elements[parent.elements.indexOf(target) - 1]
+        if (
+            prev &&
+            (prev.type === "Character" || prev.type === "CharacterSet")
+        ) {
+            raw = `\\${raw}`
+        }
+    }
+    if (target.raw.startsWith("-")) {
+        if (node.type === "Character" || node.type === "CharacterSet") {
+            raw = `${raw}\\`
+        }
+    }
+    return raw
+}
diff --git a/lib/utils/ast-utils/extract-expression-references.ts b/lib/utils/ast-utils/extract-expression-references.ts
new file mode 100644
index 0000000..05a12bb
--- /dev/null
+++ b/lib/utils/ast-utils/extract-expression-references.ts
@@ -0,0 +1,267 @@
+import type { Rule } from "eslint"
+import type { Variable } from "eslint-scope"
+import type {
+    ArrayPattern,
+    ArrowFunctionExpression,
+    CallExpression,
+    Expression,
+    ForOfStatement,
+    FunctionDeclaration,
+    FunctionExpression,
+    Identifier,
+    MemberExpression,
+    ObjectPattern,
+    Pattern,
+} from "estree"
+import { findFunction, findVariable, getParent } from "./utils"
+
+export type ExpressionReference =
+    | {
+          // The result of the expression is not referenced.
+          type: "unused"
+          node: Expression
+      }
+    | {
+          // Unknown what the expression was referenced for.
+          type: "unknown"
+          node: Expression
+      }
+    | {
+          // The expression is exported.
+          type: "exported"
+          node: Expression
+      }
+    | {
+          // The expression is referenced for member access.
+          type: "member"
+          node: Expression
+          memberExpression: MemberExpression
+      }
+    | {
+          // The expression is referenced for destructuring.
+          type: "destructuring"
+          node: Expression
+          pattern: ObjectPattern | ArrayPattern
+      }
+    | {
+          // The expression is referenced to give as an argument.
+          type: "argument"
+          node: Expression
+          callExpression: CallExpression
+      }
+    | {
+          // The expression is referenced to call.
+          type: "call"
+          node: Expression
+      }
+    | {
+          // The expression is referenced for iteration.
+          type: "iteration"
+          node: Expression
+          for: ForOfStatement
+      }
+
+type AlreadyChecked = {
+    variables: Set<Variable>
+    functions: Map<
+        FunctionDeclaration | FunctionExpression | ArrowFunctionExpression,
+        Set<number>
+    >
+}
+/** Extract references from the given expression */
+export function* extractExpressionReferences(
+    node: Expression,
+    context: Rule.RuleContext,
+): Iterable<ExpressionReference> {
+    yield* iterateReferencesForExpression(node, context, {
+        variables: new Set(),
+        functions: new Map(),
+    })
+}
+
+/** Extract references from the given identifier */
+export function* extractExpressionReferencesForVariable(
+    node: Identifier,
+    context: Rule.RuleContext,
+): Iterable<ExpressionReference> {
+    yield* iterateReferencesForVariable(node, context, {
+        variables: new Set(),
+        functions: new Map(),
+    })
+}
+
+/* eslint-disable complexity -- ignore */
+/** Iterate references from the given expression */
+function* iterateReferencesForExpression(
+    /* eslint-enable complexity -- ignore */
+    expression: Expression,
+    context: Rule.RuleContext,
+    alreadyChecked: AlreadyChecked,
+): Iterable<ExpressionReference> {
+    let node = expression
+    let parent = getParent(node)
+    while (parent?.type === "ChainExpression") {
+        node = parent
+        parent = getParent(node)
+    }
+    if (!parent || parent.type === "ExpressionStatement") {
+        yield { node, type: "unused" }
+        return
+    }
+    if (parent.type === "MemberExpression") {
+        if (parent.object === node) {
+            yield { node, type: "member", memberExpression: parent }
+        } else {
+            yield { node, type: "unknown" }
+        }
+    } else if (parent.type === "AssignmentExpression") {
+        if (parent.right === node && parent.operator === "=") {
+            yield* iterateReferencesForESPattern(
+                node,
+                parent.left,
+                context,
+                alreadyChecked,
+            )
+        } else {
+            yield { node, type: "unknown" }
+        }
+    } else if (parent.type === "VariableDeclarator") {
+        if (parent.init === node) {
+            const pp = getParent(getParent(parent))
+            if (pp?.type === "ExportNamedDeclaration") {
+                yield { node, type: "exported" }
+            }
+            yield* iterateReferencesForESPattern(
+                node,
+                parent.id,
+                context,
+                alreadyChecked,
+            )
+        } else {
+            yield { node, type: "unknown" }
+        }
+    } else if (parent.type === "CallExpression") {
+        const argIndex = parent.arguments.indexOf(node)
+        if (argIndex > -1) {
+            // `foo(regexp)`
+            if (parent.callee.type === "Identifier") {
+                const fn = findFunction(context, parent.callee)
+                if (fn) {
+                    yield* iterateReferencesForFunctionArgument(
+                        node,
+                        fn,
+                        argIndex,
+                        context,
+                        alreadyChecked,
+                    )
+                    return
+                }
+            }
+            yield { node, type: "argument", callExpression: parent }
+        } else {
+            yield { node, type: "call" }
+        }
+    } else if (
+        parent.type === "ExportSpecifier" ||
+        parent.type === "ExportDefaultDeclaration"
+    ) {
+        yield { node, type: "exported" }
+    } else if (parent.type === "ForOfStatement") {
+        if (parent.right === node) {
+            yield { node, type: "iteration", for: parent }
+        } else {
+            yield { node, type: "unknown" }
+        }
+    } else {
+        yield { node, type: "unknown" }
+    }
+}
+
+/** Iterate references for the given pattern node. */
+function* iterateReferencesForESPattern(
+    expression: Expression,
+    pattern: Pattern,
+    context: Rule.RuleContext,
+    alreadyChecked: AlreadyChecked,
+): Iterable<ExpressionReference> {
+    let target = pattern
+    while (target.type === "AssignmentPattern") {
+        target = target.left
+    }
+    if (target.type === "Identifier") {
+        // e.g. const foo = expr
+        yield* iterateReferencesForVariable(target, context, alreadyChecked)
+    } else if (
+        target.type === "ObjectPattern" ||
+        target.type === "ArrayPattern"
+    ) {
+        yield { node: expression, type: "destructuring", pattern: target }
+    } else {
+        yield { node: expression, type: "unknown" }
+    }
+}
+
+/** Iterate references for the given variable id node. */
+function* iterateReferencesForVariable(
+    identifier: Identifier,
+    context: Rule.RuleContext,
+    alreadyChecked: AlreadyChecked,
+): Iterable<ExpressionReference> {
+    const variable = findVariable(context, identifier)
+    if (!variable) {
+        yield { node: identifier, type: "unknown" }
+        return
+    }
+    if (alreadyChecked.variables.has(variable)) {
+        return
+    }
+    alreadyChecked.variables.add(variable)
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- expect
+    if ((variable as any).eslintUsed) {
+        yield { node: identifier, type: "exported" }
+    }
+    const readReferences = variable.references.filter((ref) => ref.isRead())
+    if (!readReferences.length) {
+        yield { node: identifier, type: "unused" }
+        return
+    }
+    for (const reference of readReferences) {
+        yield* iterateReferencesForExpression(
+            reference.identifier,
+            context,
+            alreadyChecked,
+        )
+    }
+}
+
+/** Iterate references for the given function argument. */
+function* iterateReferencesForFunctionArgument(
+    expression: Expression,
+    fn: FunctionDeclaration | FunctionExpression | ArrowFunctionExpression,
+    argIndex: number,
+    context: Rule.RuleContext,
+    alreadyChecked: AlreadyChecked,
+): Iterable<ExpressionReference> {
+    let alreadyIndexes = alreadyChecked.functions.get(fn)
+    if (!alreadyIndexes) {
+        alreadyIndexes = new Set()
+        alreadyChecked.functions.set(fn, alreadyIndexes)
+    }
+    if (alreadyIndexes.has(argIndex)) {
+        // cannot check
+        return
+    }
+    alreadyIndexes.add(argIndex)
+    const params = fn.params.slice(0, argIndex + 1)
+    const argNode = params[argIndex]
+    if (!argNode || params.some((param) => param?.type === "RestElement")) {
+        yield { node: expression, type: "unknown" }
+        return
+    }
+    yield* iterateReferencesForESPattern(
+        expression,
+        argNode,
+        context,
+        alreadyChecked,
+    )
+}
diff --git a/lib/utils/ast-utils/extract-property-references.ts b/lib/utils/ast-utils/extract-property-references.ts
new file mode 100644
index 0000000..2486ed8
--- /dev/null
+++ b/lib/utils/ast-utils/extract-property-references.ts
@@ -0,0 +1,323 @@
+import type { Rule } from "eslint"
+import type {
+    ArrayExpression,
+    ArrayPattern,
+    Expression,
+    ForOfStatement,
+    MemberExpression,
+    ObjectExpression,
+    ObjectPattern,
+    Pattern,
+    Property,
+    SpreadElement,
+} from "estree"
+import { getParent, getStringIfConstant } from "./utils"
+import {
+    extractExpressionReferences,
+    extractExpressionReferencesForVariable,
+} from "./extract-expression-references"
+
+export type PropertyReference =
+    | {
+          // Property name is unknown.
+          type: "unknown"
+          extractPropertyReferences?: () => Iterable<PropertyReference>
+      }
+    | {
+          type: "member"
+          name: string
+          extractPropertyReferences: () => Iterable<PropertyReference>
+      }
+    | {
+          type: "destructuring"
+          name: string
+          extractPropertyReferences: () => Iterable<PropertyReference>
+      }
+    | {
+          type: "iteration"
+          extractPropertyReferences: () => Iterable<PropertyReference>
+      }
+/** Extract property references from the given expression */
+export function* extractPropertyReferences(
+    node: Expression,
+    context: Rule.RuleContext,
+): Iterable<PropertyReference> {
+    if (isShallowCopy(node)) {
+        yield* iteratePropertyReferencesForShallowCopy(node, context)
+        return
+    }
+
+    for (const ref of extractExpressionReferences(node, context)) {
+        if (ref.type === "member") {
+            yield* iteratePropertyReferencesForMemberExpression(
+                ref.memberExpression,
+                context,
+            )
+        } else if (ref.type === "destructuring") {
+            yield* iteratePropertyReferencesForPattern(ref.pattern, context)
+        } else if (ref.type === "iteration") {
+            yield* iteratePropertyReferencesForForOf(ref.for, context)
+        } else {
+            if (ref.node !== node && isShallowCopy(ref.node)) {
+                yield* iteratePropertyReferencesForShallowCopy(
+                    ref.node,
+                    context,
+                )
+                return
+            }
+            yield { type: "unknown" }
+        }
+    }
+}
+
+/** Checks whether the given expression is shallow copied. */
+function isShallowCopy(
+    node: Expression,
+): node is Expression & {
+    parent: SpreadElement & { parent: ObjectExpression | ArrayExpression }
+} {
+    const parent = getParent(node)
+    if (parent?.type === "SpreadElement") {
+        const spreadParent = getParent(parent)
+        if (
+            spreadParent?.type === "ObjectExpression" ||
+            spreadParent?.type === "ArrayExpression"
+        ) {
+            return true
+        }
+    }
+    return false
+}
+
+/** Iterate property references from the given member expression */
+function* iteratePropertyReferencesForMemberExpression(
+    node: MemberExpression,
+    context: Rule.RuleContext,
+): Iterable<PropertyReference> {
+    const property = getProperty(node, context)
+    if (property == null) {
+        yield {
+            type: "unknown",
+            *extractPropertyReferences() {
+                yield* extractPropertyReferences(node, context)
+            },
+        }
+        return
+    }
+    yield {
+        type: "member",
+        name: property,
+        *extractPropertyReferences() {
+            yield* extractPropertyReferences(node, context)
+        },
+    }
+}
+
+/** Iterate property references from the given object pattern */
+function* iteratePropertyReferencesForObjectPattern(
+    node: ObjectPattern,
+    context: Rule.RuleContext,
+): Iterable<PropertyReference> {
+    for (const prop of node.properties) {
+        if (prop.type === "RestElement") {
+            yield* iteratePropertyReferencesForPattern(prop.argument, context)
+            continue
+        }
+        const property = getProperty(prop, context)
+        if (property == null) {
+            yield {
+                type: "unknown",
+                *extractPropertyReferences() {
+                    yield* iteratePropertyReferencesForPattern(
+                        prop.value,
+                        context,
+                    )
+                },
+            }
+            continue
+        }
+        yield {
+            type: "destructuring",
+            name: property,
+            *extractPropertyReferences() {
+                yield* iteratePropertyReferencesForPattern(prop.value, context)
+            },
+        }
+    }
+}
+
+/** Iterate property references from the given array pattern */
+function* iteratePropertyReferencesForArrayPattern(
+    node: ArrayPattern,
+    context: Rule.RuleContext,
+): Iterable<PropertyReference> {
+    let index = 0
+    for (; index < node.elements.length; index++) {
+        const element = node.elements[index]
+        if (!element) {
+            continue
+        }
+        if (element.type === "RestElement") {
+            for (const ref of iteratePropertyReferencesForPattern(
+                element.argument,
+                context,
+            )) {
+                yield offsetRef(ref, index)
+            }
+            index++
+            break
+        }
+        yield {
+            type: "destructuring",
+            name: String(index),
+            *extractPropertyReferences() {
+                yield* iteratePropertyReferencesForPattern(element, context)
+            },
+        }
+    }
+    for (; index < node.elements.length; index++) {
+        const element = node.elements[index]
+        if (!element) {
+            continue
+        }
+        yield {
+            type: "unknown",
+            *extractPropertyReferences() {
+                yield* iteratePropertyReferencesForPattern(element, context)
+            },
+        }
+    }
+}
+
+/** Iterate property references from the given for of statement */
+function* iteratePropertyReferencesForForOf(
+    node: ForOfStatement,
+    context: Rule.RuleContext,
+): Iterable<PropertyReference> {
+    yield {
+        type: "iteration",
+        *extractPropertyReferences() {
+            let left = node.left
+            if (left.type === "VariableDeclaration") {
+                left = left.declarations[0].id
+            }
+            yield* iteratePropertyReferencesForPattern(left, context)
+        },
+    }
+}
+
+/** Iterate property references from the given pattern */
+function* iteratePropertyReferencesForPattern(
+    node: Pattern,
+    context: Rule.RuleContext,
+): Iterable<PropertyReference> {
+    let target = node
+    while (target.type === "AssignmentPattern") {
+        target = target.left
+    }
+    if (target.type === "Identifier") {
+        for (const exprRef of extractExpressionReferencesForVariable(
+            target,
+            context,
+        )) {
+            yield* extractPropertyReferences(exprRef.node, context)
+        }
+    } else if (target.type === "ObjectPattern") {
+        yield* iteratePropertyReferencesForObjectPattern(target, context)
+    } else if (target.type === "ArrayPattern") {
+        yield* iteratePropertyReferencesForArrayPattern(target, context)
+    } else {
+        yield { type: "unknown" }
+    }
+}
+
+/** Iterate property references from the given shallow copy expression */
+function* iteratePropertyReferencesForShallowCopy(
+    node: Expression & {
+        parent: SpreadElement & { parent: ObjectExpression | ArrayExpression }
+    },
+    context: Rule.RuleContext,
+): Iterable<PropertyReference> {
+    const spread = node.parent
+    const spreadParent = spread.parent
+    if (spreadParent.type === "ObjectExpression") {
+        yield* extractPropertyReferences(spreadParent, context)
+    } else if (spreadParent.type === "ArrayExpression") {
+        const index = spreadParent.elements.indexOf(spread)
+        if (index === 0) {
+            // e.g. [...expr] or [...expr, after]
+            yield* extractPropertyReferences(spreadParent, context)
+            return
+        }
+        const hasSpread = spreadParent.elements
+            .slice(0, index)
+            .some((e) => e?.type === "SpreadElement")
+        if (hasSpread) {
+            for (const ref of extractPropertyReferences(
+                spreadParent,
+                context,
+            )) {
+                yield {
+                    type: "unknown",
+                    extractPropertyReferences: ref.extractPropertyReferences,
+                }
+            }
+        } else {
+            for (const ref of extractPropertyReferences(
+                spreadParent,
+                context,
+            )) {
+                yield offsetRef(ref, -index)
+            }
+        }
+    }
+}
+
+/**
+ * Get property from given node
+ */
+function getProperty(
+    node: MemberExpression | Property,
+    context: Rule.RuleContext,
+): null | string {
+    if (node.type === "MemberExpression") {
+        if (node.computed) {
+            if (node.property.type === "Literal") {
+                if (
+                    typeof node.property.value === "string" ||
+                    typeof node.property.value === "number"
+                )
+                    return String(node.property.value)
+            }
+            return getStringIfConstant(context, node.property)
+        } else if (node.property.type === "Identifier") {
+            return node.property.name
+        }
+    }
+    if (node.type === "Property") {
+        if (node.key.type === "Literal") {
+            if (
+                typeof node.key.value === "string" ||
+                typeof node.key.value === "number"
+            )
+                return String(node.key.value)
+        }
+        if (node.computed) {
+            return getStringIfConstant(context, node.key)
+        } else if (node.key.type === "Identifier") {
+            return node.key.name
+        }
+    }
+    return null
+}
+
+/** Moves the reference position of the index reference. */
+function offsetRef(ref: PropertyReference, offset: number): PropertyReference {
+    if (ref.type === "member" || ref.type === "destructuring") {
+        const num = Number(ref.name) + offset
+        if (!Number.isNaN(num)) {
+            return { ...ref, name: String(num) }
+        }
+    }
+    return ref
+}
diff --git a/lib/utils/ast-utils/index.ts b/lib/utils/ast-utils/index.ts
new file mode 100644
index 0000000..92f6383
--- /dev/null
+++ b/lib/utils/ast-utils/index.ts
@@ -0,0 +1,19 @@
+export {
+    getParent,
+    findVariable,
+    getStringIfConstant,
+    getStaticValue,
+    getScope,
+    isKnownMethodCall,
+    KnownMethodCall,
+    parseReplacements,
+    ReferenceElement,
+} from "./utils"
+export {
+    extractExpressionReferences,
+    ExpressionReference,
+} from "./extract-expression-references"
+export {
+    extractPropertyReferences,
+    PropertyReference,
+} from "./extract-property-references"
diff --git a/lib/utils/ast-utils.ts b/lib/utils/ast-utils/utils.ts
similarity index 82%
rename from lib/utils/ast-utils.ts
rename to lib/utils/ast-utils/utils.ts
index a3cf79b..ecb1efa 100644
--- a/lib/utils/ast-utils.ts
+++ b/lib/utils/ast-utils/utils.ts
@@ -1,15 +1,18 @@
 import type { Rule } from "eslint"
 import * as eslintUtils from "eslint-utils"
 import type {
+    ArrowFunctionExpression,
     CallExpression,
     Expression,
+    FunctionDeclaration,
+    FunctionExpression,
     Identifier,
     Literal,
     MemberExpression,
     Node,
 } from "estree"
-import { parseStringLiteral } from "./string-literal-parser"
-import { baseParseReplacements } from "./replacements-utils"
+import { parseStringLiteral } from "../string-literal-parser"
+import { baseParseReplacements } from "../replacements-utils"
 import type { Scope, Variable } from "eslint-scope"
 
 /**
@@ -153,6 +156,51 @@ export function getScope(context: Rule.RuleContext, currentNode: Node): Scope {
     return scopeManager.scopes[0]
 }
 
+/**
+ * Find function node
+ */
+export function findFunction(
+    context: Rule.RuleContext,
+    id: Identifier,
+): FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | null {
+    let target = id
+
+    const set = new Set<Identifier>()
+    for (;;) {
+        if (set.has(target)) {
+            return null
+        }
+        set.add(target)
+        const calleeVariable = findVariable(context, target)
+        if (!calleeVariable) {
+            return null
+        }
+        if (calleeVariable.defs.length === 1) {
+            const def = calleeVariable.defs[0]
+            if (def.node.type === "FunctionDeclaration") {
+                return def.node
+            }
+            if (
+                def.type === "Variable" &&
+                def.parent.kind === "const" &&
+                def.node.init
+            ) {
+                if (
+                    def.node.init.type === "FunctionExpression" ||
+                    def.node.init.type === "ArrowFunctionExpression"
+                ) {
+                    return def.node.init
+                }
+                if (def.node.init.type === "Identifier") {
+                    target = def.node.init
+                    continue
+                }
+            }
+        }
+        return null
+    }
+}
+
 export type KnownMethodCall = CallExpression & {
     callee: MemberExpression & { object: Expression; property: Identifier }
     arguments: Expression[]
diff --git a/lib/utils/get-usage-of-pattern.ts b/lib/utils/get-usage-of-pattern.ts
index 9f19ec7..2fd5407 100644
--- a/lib/utils/get-usage-of-pattern.ts
+++ b/lib/utils/get-usage-of-pattern.ts
@@ -1,16 +1,12 @@
 import type { Rule } from "eslint"
 import type {
-    ArrowFunctionExpression,
     Expression,
-    FunctionDeclaration,
-    FunctionExpression,
     Identifier,
     Literal,
     MemberExpression,
     ObjectPattern,
-    Pattern,
 } from "estree"
-import { findVariable, getParent, getStringIfConstant } from "./ast-utils"
+import { getStringIfConstant, extractExpressionReferences } from "./ast-utils"
 
 export enum UsageOfPattern {
     /** The pattern was only used via `.source`. */
@@ -34,11 +30,7 @@ export function getUsageOfPattern(
     context: Rule.RuleContext,
 ): UsageOfPattern {
     const usageSet = new Set<UsageOfPattern.partial | UsageOfPattern.whole>()
-    for (const usage of iterateUsageOfPatternForExpression(
-        node,
-        context,
-        new Map(),
-    )) {
+    for (const usage of iterateUsageOfPattern(node, context)) {
         if (usage === UsageOfPattern.unknown) {
             return UsageOfPattern.unknown
         }
@@ -56,76 +48,31 @@ export function getUsageOfPattern(
 }
 
 /** Iterate the usage of pattern for the given expression node. */
-function* iterateUsageOfPatternForExpression(
+function* iterateUsageOfPattern(
     node: Expression,
     context: Rule.RuleContext,
-    alreadyFn: Map<
-        FunctionDeclaration | FunctionExpression | ArrowFunctionExpression,
-        Set<number>
-    >,
-): IterableIterator<InternalUsageOfPattern> {
-    const parent = getParent(node)
-    if (!parent) {
-        return
-    }
-    if (parent.type === "MemberExpression") {
-        if (parent.object === node) {
-            yield* iterateUsageOfPatternForMemberExpression(parent, context)
-        } else {
-            // unknown.
-            yield UsageOfPattern.unknown
-        }
-    } else if (parent.type === "AssignmentExpression") {
-        if (parent.right === node) {
-            yield* iterateUsageOfPatternForESPattern(
-                parent.left,
+): Iterable<InternalUsageOfPattern> {
+    for (const ref of extractExpressionReferences(node, context)) {
+        if (ref.type === "member") {
+            yield* iterateUsageOfPatternForMemberExpression(
+                ref.memberExpression,
                 context,
-                alreadyFn,
             )
-        } else {
-            // unknown.
-            yield UsageOfPattern.unknown
-        }
-    } else if (parent.type === "VariableDeclarator") {
-        if (parent.init === node) {
-            yield* iterateUsageOfPatternForESPattern(
-                parent.id,
-                context,
-                alreadyFn,
-            )
-        } else {
-            // unknown.
-            yield UsageOfPattern.unknown
-        }
-    } else if (parent.type === "CallExpression") {
-        const argIndex = parent.arguments.indexOf(node)
-        if (argIndex > -1) {
-            // `foo(regexp)`
-            if (parent.callee.type === "Identifier") {
-                const fn = findFunction(context, parent.callee)
-                if (fn) {
-                    yield* iterateUsageOfPatternForFunctionArgument(
-                        fn,
-                        argIndex,
-                        context,
-                        alreadyFn,
-                    )
-                    return
-                }
-            }
-
+        } else if (ref.type === "destructuring") {
+            if (ref.pattern.type === "ObjectPattern")
+                yield* iterateUsageOfPatternForObjectPattern(
+                    ref.pattern,
+                    context,
+                )
+        } else if (ref.type === "unused") {
+            // noop
+        } else if (ref.type === "argument") {
             // It could be a call to a known method that uses a regexp (`match`, `matchAll`, `split`, `replace`, `replaceAll`, and `search`),
             // or it could use an unknown method, both of which are considered to have used a regexp.
             yield UsageOfPattern.whole
         } else {
-            // unknown.
             yield UsageOfPattern.unknown
         }
-    } else if (parent.type === "ChainExpression") {
-        yield* iterateUsageOfPatternForExpression(parent, context, alreadyFn)
-    } else {
-        // unknown.
-        yield UsageOfPattern.unknown
     }
 }
 
@@ -133,7 +80,7 @@ function* iterateUsageOfPatternForExpression(
 function* iterateUsageOfPatternForMemberExpression(
     node: MemberExpression,
     context: Rule.RuleContext,
-): IterableIterator<InternalUsageOfPattern> {
+): Iterable<InternalUsageOfPattern> {
     const propName: string | null = !node.computed
         ? (node.property as Identifier).name
         : getStringIfConstant(context, node.property)
@@ -143,7 +90,7 @@ function* iterateUsageOfPatternForMemberExpression(
 /** Iterate the usage of pattern for the given member expression node. */
 function* iterateUsageOfPatternForPropName(
     propName: string | null,
-): IterableIterator<InternalUsageOfPattern> {
+): Iterable<InternalUsageOfPattern> {
     const regexpPropName: keyof RegExp | null = propName as never
     if (regexpPropName === "source") {
         yield UsageOfPattern.partial
@@ -169,31 +116,11 @@ function* iterateUsageOfPatternForPropName(
     yield UsageOfPattern.whole
 }
 
-/** Iterate the usage of pattern for the given pattern node. */
-function* iterateUsageOfPatternForESPattern(
-    node: Pattern,
-    context: Rule.RuleContext,
-    alreadyFn: Map<
-        FunctionDeclaration | FunctionExpression | ArrowFunctionExpression,
-        Set<number>
-    >,
-): IterableIterator<InternalUsageOfPattern> {
-    if (node.type === "Identifier") {
-        // e.g. const foo = regexp
-        yield* iterateUsageOfPatternForVariable(node, context, alreadyFn)
-    } else if (node.type === "ObjectPattern") {
-        yield* iterateUsageOfPatternForObjectPattern(node, context)
-    } else {
-        // unknown
-        yield UsageOfPattern.unknown
-    }
-}
-
 /** Iterate the usage of pattern for the given object pattern node. */
 function* iterateUsageOfPatternForObjectPattern(
     node: ObjectPattern,
     context: Rule.RuleContext,
-): IterableIterator<InternalUsageOfPattern> {
+): Iterable<InternalUsageOfPattern> {
     for (const prop of node.properties) {
         if (prop.type === "RestElement") {
             continue
@@ -210,97 +137,3 @@ function* iterateUsageOfPatternForObjectPattern(
         yield* iterateUsageOfPatternForPropName(propName)
     }
 }
-
-/** Iterate the usage of pattern for the given variable id node. */
-function* iterateUsageOfPatternForVariable(
-    node: Identifier,
-    context: Rule.RuleContext,
-    alreadyFn: Map<
-        FunctionDeclaration | FunctionExpression | ArrowFunctionExpression,
-        Set<number>
-    >,
-): IterableIterator<InternalUsageOfPattern> {
-    const variable = findVariable(context, node)
-    if (!variable) {
-        // unknown.
-        yield UsageOfPattern.unknown
-        return
-    }
-    const readReferences = variable.references.filter((ref) => ref.isRead())
-    if (!readReferences.length) {
-        // The variable has not been read, so the usage cannot be determined.
-        yield UsageOfPattern.unknown
-        return
-    }
-    for (const reference of readReferences) {
-        yield* iterateUsageOfPatternForExpression(
-            reference.identifier,
-            context,
-            alreadyFn,
-        )
-    }
-}
-
-/** Iterate the usage of pattern for the given function argument. */
-function* iterateUsageOfPatternForFunctionArgument(
-    node: FunctionDeclaration | FunctionExpression | ArrowFunctionExpression,
-    argIndex: number,
-    context: Rule.RuleContext,
-    alreadyFn: Map<
-        FunctionDeclaration | FunctionExpression | ArrowFunctionExpression,
-        Set<number>
-    >,
-): IterableIterator<InternalUsageOfPattern> {
-    let alreadyIndexes = alreadyFn.get(node)
-    if (!alreadyIndexes) {
-        alreadyIndexes = new Set()
-        alreadyFn.set(node, alreadyIndexes)
-    }
-    if (alreadyIndexes.has(argIndex)) {
-        // cannot check
-        return
-    }
-    alreadyIndexes.add(argIndex)
-    const argNode = node.params[argIndex]
-    if (argNode && argNode.type === "Identifier") {
-        yield* iterateUsageOfPatternForVariable(argNode, context, alreadyFn)
-    } else {
-        // unknown.
-        yield UsageOfPattern.unknown
-    }
-}
-
-/**
- * Find function node
- */
-function findFunction(
-    context: Rule.RuleContext,
-    id: Identifier,
-): FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | null {
-    const calleeVariable = findVariable(context, id)
-    if (!calleeVariable) {
-        return null
-    }
-    if (calleeVariable.defs.length === 1) {
-        const def = calleeVariable.defs[0]
-        if (def.node.type === "FunctionDeclaration") {
-            return def.node
-        }
-        if (
-            def.type === "Variable" &&
-            def.parent.kind === "const" &&
-            def.node.init
-        ) {
-            if (
-                def.node.init.type === "FunctionExpression" ||
-                def.node.init.type === "ArrowFunctionExpression"
-            ) {
-                return def.node.init
-            }
-            if (def.node.init.type === "Identifier") {
-                return findFunction(context, def.node.init)
-            }
-        }
-    }
-    return null
-}
diff --git a/lib/utils/rules.ts b/lib/utils/rules.ts
index 1dfdfb5..a084182 100644
--- a/lib/utils/rules.ts
+++ b/lib/utils/rules.ts
@@ -58,6 +58,7 @@ import preferT from "../rules/prefer-t"
 import preferUnicodeCodepointEscapes from "../rules/prefer-unicode-codepoint-escapes"
 import preferW from "../rules/prefer-w"
 import sortAlternatives from "../rules/sort-alternatives"
+import sortCharacterClassElements from "../rules/sort-character-class-elements"
 import sortFlags from "../rules/sort-flags"
 import strict from "../rules/strict"
 import unicodeEscape from "../rules/unicode-escape"
@@ -122,6 +123,7 @@ export const rules = [
     preferUnicodeCodepointEscapes,
     preferW,
     sortAlternatives,
+    sortCharacterClassElements,
     sortFlags,
     strict,
     unicodeEscape,


Test patch:
diff --git a/tests/lib/rules/no-lazy-ends.ts b/tests/lib/rules/no-lazy-ends.ts
index e24cbeb..3a6eadb 100644
--- a/tests/lib/rules/no-lazy-ends.ts
+++ b/tests/lib/rules/no-lazy-ends.ts
@@ -16,6 +16,18 @@ tester.run("no-lazy-ends", rule as any, {
         `/a??/`, // UsageOfPattern.unknown
 
         `/a{3}?/.test(str)`, // uselessly lazy but that's not for this rule to correct
+
+        // exported
+        {
+            code: `
+            /* exported a */
+            const a = /a??/
+            a.test(str)`,
+            parserOptions: {
+                ecmaVersion: 2020,
+                sourceType: "script",
+            },
+        },
     ],
     invalid: [
         {
diff --git a/tests/lib/rules/no-unused-capturing-group.ts b/tests/lib/rules/no-unused-capturing-group.ts
index 9c276bc..9bc8840 100644
--- a/tests/lib/rules/no-unused-capturing-group.ts
+++ b/tests/lib/rules/no-unused-capturing-group.ts
@@ -100,6 +100,18 @@ tester.run("no-unused-capturing-group", rule as any, {
         const reg = /(,)/;
         'a,b,c'.split(reg)
         `,
+        {
+            // exported
+            code: String.raw`
+            /* exported regexp */
+            const regexp = /(\d{4})-(\d{2})-(\d{2})/
+            const replaced = '2000-12-31'.replace(regexp, 'Date') // "Date"
+            `,
+            parserOptions: {
+                ecmaVersion: 2020,
+                sourceType: "script",
+            },
+        },
     ],
     invalid: [
         {
@@ -444,7 +456,59 @@ tester.run("no-unused-capturing-group", rule as any, {
                 // var d = matches[3] // "31"
             }
             `,
-            errors: ["Capturing group is defined but never used."],
+            errors: [
+                "'y' is defined for capturing group, but it name is never used.",
+                "Capturing group is defined but never used.",
+            ],
+        },
+        {
+            code: String.raw`
+            const result = [...'2000-12-31 2000-12-31'.matchAll(/(?<y>\d{4})-(?<m>\d{2})-(\d{2})/g)]
+            for (const matches of result) {
+                var y = matches[1] // "2000"
+                var m = matches.groups.m // "12"
+                // var d = matches[3] // "31"
+            }
+            `,
+            errors: [
+                "'y' is defined for capturing group, but it name is never used.",
+                "Capturing group is defined but never used.",
+            ],
+        },
+        {
+            code: String.raw`
+            const result = [
+                { 1: 2000, groups: { m: 12 } },
+                ...'2000-12-31 2000-12-31'.matchAll(/(?<y>\d{4})-(?<m>\d{2})-(\d{2})/g),
+                ...'2000/12/31 2000/12/31'.matchAll(/(?<y>\d{4})\/(?<m>\d{2})\/(?<d>\d{2})/g),
+            ]
+            for (const matches of result) {
+                var y = matches[1]
+                var m = matches.groups.m
+                // var d = matches[3]
+            }
+            `,
+            errors: [
+                "'y' is defined for capturing group, but it name is never used.",
+                "Capturing group is defined but never used.",
+                "'y' is defined for capturing group, but it name is never used.",
+                "'d' capturing group is defined but never used.",
+            ],
+        },
+        {
+            code: String.raw`
+            const result = [...'2000-12-31 2000-12-31'.matchAll(/(?<y>\d{4})-(?<m>\d{2})-(\d{2})/g)]
+            for (let index = 0; index < result.length; index++) {
+                const matches = result[index]
+                var y = matches[1] // "2000"
+                var m = matches.groups.m // "12"
+                // var d = matches[3] // "31"
+            }
+            `,
+            errors: [
+                "'y' is defined for capturing group, but it name is never used.",
+                "Capturing group is defined but never used.",
+            ],
         },
         {
             code: String.raw`
diff --git a/tests/lib/rules/no-useless-flag.ts b/tests/lib/rules/no-useless-flag.ts
index 4600c32..cdad538 100644
--- a/tests/lib/rules/no-useless-flag.ts
+++ b/tests/lib/rules/no-useless-flag.ts
@@ -179,6 +179,28 @@ tester.run("no-useless-flag", rule as any, {
         const regex = /foo/y;
         unknown.exec(regex)
         `,
+        {
+            // exported
+            code: `
+            /* exported b */
+            const a = /foo/y;
+            const b = a;
+            const regex = b;
+
+            'str'.split(regex)
+            `,
+            parserOptions: {
+                ecmaVersion: 2020,
+                sourceType: "script",
+            },
+        },
+        `
+        const a = /foo/y;
+        const b = a;
+        const regex = b; // unused
+
+        'str'.split(b)
+        `,
 
         // ignore
         { code: String.raw`/\w/i`, options: [{ ignore: ["i"] }] },
@@ -534,7 +556,7 @@ tester.run("no-useless-flag", rule as any, {
             const b = a;
             const regex = b;
 
-            'str'.split(b)
+            'str'.split(regex)
             `,
             output: null,
             errors: [
diff --git a/tests/lib/rules/order-in-character-class.ts b/tests/lib/rules/order-in-character-class.ts
index b5c3230..9967d69 100644
--- a/tests/lib/rules/order-in-character-class.ts
+++ b/tests/lib/rules/order-in-character-class.ts
@@ -9,220 +9,13 @@ const tester = new RuleTester({
 })
 
 tester.run("order-in-character-class", rule as any, {
-    valid: [
-        `/[abcd]/`,
-        String.raw`/[aa]/u`,
-        String.raw`/[\d\d]/u`,
-        String.raw`/[\s\S]/u`,
-        String.raw`/[\S\s]/u`,
-        String.raw`/[\d\D]/u`,
-        String.raw`/[\D\d]/u`,
-        String.raw`/[\w\W]/u`,
-        String.raw`/[\W\w]/u`,
-        String.raw`/[\p{Script=Hiragana}\p{Script=Hiragana}]/u`,
-        String.raw`/[\p{Script=Hiragana}\P{Script=Hiragana}]/u`,
-        String.raw`/[\P{Script=Hiragana}\p{Script=Hiragana}]/u`,
-        String.raw`/[\w\p{ASCII}]/u`,
-        {
-            code: String.raw`/[\w\p{ASCII}]/u`,
-            options: [{ order: [] }],
-        },
-        String.raw`/[\p{ASCII}\p{Script=Hiragana}]/u`,
-        String.raw`/[\w\d]/u`,
-        {
-            code: String.raw`/[\w\d]/u`,
-            options: [{ order: [] }],
-        },
-        String.raw`/[\p{ASCII}a]/u`,
-        {
-            code: String.raw`/[\p{ASCII}a]/u`,
-            options: [{ order: [] }],
-        },
-    ],
+    valid: [`/[abcd]/`],
     invalid: [
         {
             code: `/[acdb]/`,
             output: `/[abcd]/`,
             errors: [
-                {
-                    message:
-                        "Expected character class elements to be in ascending order. 'b' should be before 'c'.",
-                    line: 1,
-                    column: 6,
-                    endLine: 1,
-                    endColumn: 7,
-                },
-            ],
-        },
-        {
-            code: `/[b-da]/`,
-            output: `/[ab-d]/`,
-            errors: [
-                {
-                    message:
-                        "Expected character class elements to be in ascending order. 'a' should be before 'b-d'.",
-                    line: 1,
-                    column: 6,
-                    endLine: 1,
-                    endColumn: 7,
-                },
-            ],
-        },
-        {
-            code: `/[b-da]/`,
-            output: `/[ab-d]/`,
-            options: [{ order: [] }],
-            errors: [
-                {
-                    message:
-                        "Expected character class elements to be in ascending order. 'a' should be before 'b-d'.",
-                    line: 1,
-                    column: 6,
-                    endLine: 1,
-                    endColumn: 7,
-                },
-            ],
-        },
-        {
-            code: `/[da-c]/`,
-            output: `/[a-cd]/`,
-            errors: [
-                {
-                    message:
-                        "Expected character class elements to be in ascending order. 'a-c' should be before 'd'.",
-                    line: 1,
-                    column: 4,
-                    endLine: 1,
-                    endColumn: 7,
-                },
-            ],
-        },
-        {
-            code: `/[da-c]/`,
-            output: `/[a-cd]/`,
-            options: [{ order: [] }],
-            errors: [
-                {
-                    message:
-                        "Expected character class elements to be in ascending order. 'a-c' should be before 'd'.",
-                    line: 1,
-                    column: 4,
-                    endLine: 1,
-                    endColumn: 7,
-                },
-            ],
-        },
-        {
-            code: String.raw`/[abcd\d]/`,
-            output: String.raw`/[\dabcd]/`,
-            errors: [
-                "Expected character class elements to be in ascending order. '\\d' should be before 'a'.",
-            ],
-        },
-        {
-            code: String.raw`/[\s\d\w]/`,
-            output: String.raw`/[\s\w\d]/`,
-            errors: [
-                "Expected character class elements to be in ascending order. '\\w' should be before '\\d'.",
-            ],
-        },
-        {
-            code: String.raw`/[\s\d\w]/`,
-            output: String.raw`/[\s\w\d]/`,
-            options: [{ order: [] }],
-            errors: [
-                "Expected character class elements to be in ascending order. '\\w' should be before '\\d'.",
-            ],
-        },
-        {
-            code: String.raw`/[\p{ASCII}\w]/u`,
-            output: String.raw`/[\w\p{ASCII}]/u`,
-            errors: [
-                "Expected character class elements to be in ascending order. '\\w' should be before '\\p{ASCII}'.",
-            ],
-        },
-        {
-            code: String.raw`/[\p{ASCII}\w]/u`,
-            output: String.raw`/[\w\p{ASCII}]/u`,
-            options: [{ order: [] }],
-            errors: [
-                "Expected character class elements to be in ascending order. '\\w' should be before '\\p{ASCII}'.",
-            ],
-        },
-        {
-            code: String.raw`/[\p{Script=Hiragana}\p{ASCII}]/u`,
-            output: String.raw`/[\p{ASCII}\p{Script=Hiragana}]/u`,
-            errors: [
-                "Expected character class elements to be in ascending order. '\\p{ASCII}' should be before '\\p{Script=Hiragana}'.",
-            ],
-        },
-        {
-            code: String.raw`/[\p{Script=Hiragana}\p{Script=Han}]/u`,
-            output: String.raw`/[\p{Script=Han}\p{Script=Hiragana}]/u`,
-            errors: [
-                `Expected character class elements to be in ascending order. '\\p{Script=Han}' should be before '\\p{Script=Hiragana}'.`,
-            ],
-        },
-        {
-            code: String.raw`/[\d\w]/u`,
-            output: String.raw`/[\w\d]/u`,
-            options: [{ order: [] }],
-            errors: [
-                "Expected character class elements to be in ascending order. '\\w' should be before '\\d'.",
-            ],
-        },
-        {
-            code: String.raw`/[\da-b-]/u`,
-            output: String.raw`/[\d\-a-b]/u`,
-            errors: [
-                "Expected character class elements to be in ascending order. '-' should be before 'a-b'.",
-            ],
-        },
-        {
-            code: String.raw`/[a-b-]/u`,
-            output: String.raw`/[-a-b]/u`,
-            errors: [
-                "Expected character class elements to be in ascending order. '-' should be before 'a-b'.",
-            ],
-        },
-        {
-            code: String.raw`/[-$a]/u`,
-            output: String.raw`/[$\-a]/u`,
-            errors: [
-                "Expected character class elements to be in ascending order. '$' should be before '-'.",
-            ],
-        },
-        {
-            code: String.raw`/[-_\s]+/gu`,
-            output: String.raw`/[\s\-_]+/gu`,
-            errors: [
-                "Expected character class elements to be in ascending order. '\\s' should be before '-'.",
-            ],
-        },
-        {
-            code: String.raw`/[-_-]/u`,
-            output: String.raw`/[-\-_]/u`,
-            errors: [
-                "Expected character class elements to be in ascending order. '-' should be before '_'.",
-            ],
-        },
-        {
-            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 s = "[\\d"+"\\w]"
-            new RegExp(s, 'u')`,
-            output: null,
-            options: [{ order: [] }],
-            errors: [
-                "Expected character class elements to be in ascending order. '\\w' should be before '\\d'.",
+                "Expected character class elements to be in ascending order. 'b' should be before 'c'.",
             ],
         },
     ],
diff --git a/tests/lib/rules/sort-character-class-elements.ts b/tests/lib/rules/sort-character-class-elements.ts
new file mode 100644
index 0000000..2506f0d
--- /dev/null
+++ b/tests/lib/rules/sort-character-class-elements.ts
@@ -0,0 +1,229 @@
+import { RuleTester } from "eslint"
+import rule from "../../../lib/rules/sort-character-class-elements"
+
+const tester = new RuleTester({
+    parserOptions: {
+        ecmaVersion: 2020,
+        sourceType: "module",
+    },
+})
+
+tester.run("sort-character-class-elements", rule as any, {
+    valid: [
+        `/[abcd]/`,
+        String.raw`/[aa]/u`,
+        String.raw`/[\d\d]/u`,
+        String.raw`/[\s\S]/u`,
+        String.raw`/[\S\s]/u`,
+        String.raw`/[\d\D]/u`,
+        String.raw`/[\D\d]/u`,
+        String.raw`/[\w\W]/u`,
+        String.raw`/[\W\w]/u`,
+        String.raw`/[\p{Script=Hiragana}\p{Script=Hiragana}]/u`,
+        String.raw`/[\p{Script=Hiragana}\P{Script=Hiragana}]/u`,
+        String.raw`/[\P{Script=Hiragana}\p{Script=Hiragana}]/u`,
+        String.raw`/[\w\p{ASCII}]/u`,
+        {
+            code: String.raw`/[\w\p{ASCII}]/u`,
+            options: [{ order: [] }],
+        },
+        String.raw`/[\p{ASCII}\p{Script=Hiragana}]/u`,
+        String.raw`/[\w\d]/u`,
+        {
+            code: String.raw`/[\w\d]/u`,
+            options: [{ order: [] }],
+        },
+        String.raw`/[\p{ASCII}a]/u`,
+        {
+            code: String.raw`/[\p{ASCII}a]/u`,
+            options: [{ order: [] }],
+        },
+    ],
+    invalid: [
+        {
+            code: `/[acdb]/`,
+            output: `/[abcd]/`,
+            errors: [
+                {
+                    message:
+                        "Expected character class elements to be in ascending order. 'b' should be before 'c'.",
+                    line: 1,
+                    column: 6,
+                    endLine: 1,
+                    endColumn: 7,
+                },
+            ],
+        },
+        {
+            code: `/[b-da]/`,
+            output: `/[ab-d]/`,
+            errors: [
+                {
+                    message:
+                        "Expected character class elements to be in ascending order. 'a' should be before 'b-d'.",
+                    line: 1,
+                    column: 6,
+                    endLine: 1,
+                    endColumn: 7,
+                },
+            ],
+        },
+        {
+            code: `/[b-da]/`,
+            output: `/[ab-d]/`,
+            options: [{ order: [] }],
+            errors: [
+                {
+                    message:
+                        "Expected character class elements to be in ascending order. 'a' should be before 'b-d'.",
+                    line: 1,
+                    column: 6,
+                    endLine: 1,
+                    endColumn: 7,
+                },
+            ],
+        },
+        {
+            code: `/[da-c]/`,
+            output: `/[a-cd]/`,
+            errors: [
+                {
+                    message:
+                        "Expected character class elements to be in ascending order. 'a-c' should be before 'd'.",
+                    line: 1,
+                    column: 4,
+                    endLine: 1,
+                    endColumn: 7,
+                },
+            ],
+        },
+        {
+            code: `/[da-c]/`,
+            output: `/[a-cd]/`,
+            options: [{ order: [] }],
+            errors: [
+                {
+                    message:
+                        "Expected character class elements to be in ascending order. 'a-c' should be before 'd'.",
+                    line: 1,
+                    column: 4,
+                    endLine: 1,
+                    endColumn: 7,
+                },
+            ],
+        },
+        {
+            code: String.raw`/[abcd\d]/`,
+            output: String.raw`/[\dabcd]/`,
+            errors: [
+                "Expected character class elements to be in ascending order. '\\d' should be before 'a'.",
+            ],
+        },
+        {
+            code: String.raw`/[\s\d\w]/`,
+            output: String.raw`/[\s\w\d]/`,
+            errors: [
+                "Expected character class elements to be in ascending order. '\\w' should be before '\\d'.",
+            ],
+        },
+        {
+            code: String.raw`/[\s\d\w]/`,
+            output: String.raw`/[\s\w\d]/`,
+            options: [{ order: [] }],
+            errors: [
+                "Expected character class elements to be in ascending order. '\\w' should be before '\\d'.",
+            ],
+        },
+        {
+            code: String.raw`/[\p{ASCII}\w]/u`,
+            output: String.raw`/[\w\p{ASCII}]/u`,
+            errors: [
+                "Expected character class elements to be in ascending order. '\\w' should be before '\\p{ASCII}'.",
+            ],
+        },
+        {
+            code: String.raw`/[\p{ASCII}\w]/u`,
+            output: String.raw`/[\w\p{ASCII}]/u`,
+            options: [{ order: [] }],
+            errors: [
+                "Expected character class elements to be in ascending order. '\\w' should be before '\\p{ASCII}'.",
+            ],
+        },
+        {
+            code: String.raw`/[\p{Script=Hiragana}\p{ASCII}]/u`,
+            output: String.raw`/[\p{ASCII}\p{Script=Hiragana}]/u`,
+            errors: [
+                "Expected character class elements to be in ascending order. '\\p{ASCII}' should be before '\\p{Script=Hiragana}'.",
+            ],
+        },
+        {
+            code: String.raw`/[\p{Script=Hiragana}\p{Script=Han}]/u`,
+            output: String.raw`/[\p{Script=Han}\p{Script=Hiragana}]/u`,
+            errors: [
+                `Expected character class elements to be in ascending order. '\\p{Script=Han}' should be before '\\p{Script=Hiragana}'.`,
+            ],
+        },
+        {
+            code: String.raw`/[\d\w]/u`,
+            output: String.raw`/[\w\d]/u`,
+            options: [{ order: [] }],
+            errors: [
+                "Expected character class elements to be in ascending order. '\\w' should be before '\\d'.",
+            ],
+        },
+        {
+            code: String.raw`/[\da-b-]/u`,
+            output: String.raw`/[\d\-a-b]/u`,
+            errors: [
+                "Expected character class elements to be in ascending order. '-' should be before 'a-b'.",
+            ],
+        },
+        {
+            code: String.raw`/[a-b-]/u`,
+            output: String.raw`/[-a-b]/u`,
+            errors: [
+                "Expected character class elements to be in ascending order. '-' should be before 'a-b'.",
+            ],
+        },
+        {
+            code: String.raw`/[-$a]/u`,
+            output: String.raw`/[$\-a]/u`,
+            errors: [
+                "Expected character class elements to be in ascending order. '$' should be before '-'.",
+            ],
+        },
+        {
+            code: String.raw`/[-_\s]+/gu`,
+            output: String.raw`/[\s\-_]+/gu`,
+            errors: [
+                "Expected character class elements to be in ascending order. '\\s' should be before '-'.",
+            ],
+        },
+        {
+            code: String.raw`/[-_-]/u`,
+            output: String.raw`/[-\-_]/u`,
+            errors: [
+                "Expected character class elements to be in ascending order. '-' should be before '_'.",
+            ],
+        },
+        {
+            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 s = "[\\d"+"\\w]"
+            new RegExp(s, 'u')`,
+            output: null,
+            options: [{ order: [] }],
+            errors: [
+                "Expected character class elements to be in ascending order. '\\w' should be before '\\d'.",
+            ],
+        },
+    ],
+})
diff --git a/tests/lib/utils/ast-utils/extract-expression-references.ts b/tests/lib/utils/ast-utils/extract-expression-references.ts
new file mode 100644
index 0000000..baaac8c
--- /dev/null
+++ b/tests/lib/utils/ast-utils/extract-expression-references.ts
@@ -0,0 +1,202 @@
+import type { Rule } from "eslint"
+import { Linter } from "eslint"
+import assert from "assert"
+import type * as ESTree from "estree"
+import { CALL, CONSTRUCT, ReferenceTracker } from "eslint-utils"
+import type { ExpressionReference } from "../../../../lib/utils/ast-utils"
+import { extractExpressionReferences } from "../../../../lib/utils/ast-utils"
+import { isRegexpLiteral } from "../../../../lib/utils"
+
+type ExpressionReferenceResult = { type: string; [key: string]: any }
+
+type TestCase = {
+    code: string
+    results: ExpressionReferenceResult[][]
+    sourceType?: "script" | "module"
+}
+const TESTCASES: TestCase[] = [
+    {
+        code: `const a = /a/`,
+        results: [[{ type: "unused", node: "a" }]],
+    },
+    {
+        code: `const a = /a/
+        b[a]`,
+        results: [[{ type: "unknown", node: "a" }]],
+    },
+    {
+        code: `
+        export const a = /a/
+        a.source`,
+        sourceType: "module",
+        results: [
+            [
+                { type: "exported", node: "/a/" },
+                { type: "member", node: "a", memberExpression: "a.source" },
+            ],
+        ],
+    },
+    {
+        code: `
+        const a = /a/
+        export default a
+        a.source`,
+        sourceType: "module",
+        results: [
+            [
+                { type: "exported", node: "a" },
+                { type: "member", node: "a", memberExpression: "a.source" },
+            ],
+        ],
+    },
+    {
+        code: `/* exported a */
+        const a = /a/
+        a.source`,
+        sourceType: "script",
+        results: [
+            [
+                { type: "exported", node: "a" },
+                { type: "member", node: "a", memberExpression: "a.source" },
+            ],
+        ],
+    },
+    {
+        code: `const a = /a/
+        a.source`,
+        results: [
+            [{ type: "member", node: "a", memberExpression: "a.source" }],
+        ],
+    },
+    {
+        code: `const a = /a/
+        const {source} = a`,
+        results: [[{ type: "destructuring", node: "a", pattern: "{source}" }]],
+    },
+    {
+        code: `const a = /a/
+        fn(a)`,
+        results: [[{ type: "argument", node: "a", callExpression: "fn(a)" }]],
+    },
+    {
+        code: `const a = /a/
+        fn(a)
+        
+        function fn(b) { b.source }`,
+        results: [
+            [{ type: "member", node: "b", memberExpression: "b.source" }],
+        ],
+    },
+    {
+        code: `const a = /a/
+        a()`,
+        results: [[{ type: "call", node: "a" }]],
+    },
+    {
+        code: `const a = /a/
+        for (const b of a) {}`,
+        results: [
+            [{ type: "iteration", node: "a", for: "for (const b of a) {}" }],
+        ],
+    },
+]
+describe("extractExpressionReferences", () => {
+    for (const testCase of TESTCASES) {
+        it(testCase.code, () => {
+            let results: ExpressionReferenceResult[][] = []
+            const regexps: (ESTree.NewExpression | ESTree.CallExpression)[] = []
+            const testNodes: (
+                | ESTree.Literal
+                | ESTree.NewExpression
+                | ESTree.CallExpression
+            )[] = []
+            const linter = new Linter()
+            linter.defineRule("test", {
+                create(context) {
+                    return {
+                        Program() {
+                            const scope = context.getScope()
+                            const tracker = new ReferenceTracker(scope)
+
+                            for (const {
+                                node,
+                            } of tracker.iterateGlobalReferences({
+                                RegExp: { [CALL]: true, [CONSTRUCT]: true },
+                            })) {
+                                const newOrCall = node as
+                                    | ESTree.NewExpression
+                                    | ESTree.CallExpression
+                                regexps.push(newOrCall)
+                            }
+                        },
+                        Literal(node: ESTree.Literal) {
+                            if (isRegexpLiteral(node)) {
+                                testNodes.push(node)
+                            }
+                        },
+                        "NewExpression,CallExpression"(
+                            node: ESTree.NewExpression | ESTree.CallExpression,
+                        ) {
+                            if (regexps.includes(node)) {
+                                testNodes.push(node)
+                            }
+                        },
+                        "Program:exit"() {
+                            results = testNodes.map((node) =>
+                                [
+                                    ...extractExpressionReferences(
+                                        node,
+                                        context,
+                                    ),
+                                ].map((r) => toResult(r, context)),
+                            )
+                        },
+                    }
+                },
+            })
+            const r = linter.verify(
+                testCase.code,
+                {
+                    globals: {
+                        Set: "readonly",
+                        Map: "readonly",
+                        BigInt: "readonly",
+                        window: "readonly",
+                        globalThis: "readonly",
+                    },
+                    parserOptions: {
+                        ecmaVersion: 2020,
+                        sourceType: testCase.sourceType,
+                    },
+                    rules: {
+                        test: "error",
+                    },
+                },
+                "test.js",
+            )
+            if (r.length) {
+                assert.deepStrictEqual(r, [])
+            }
+
+            assert.deepStrictEqual(results, testCase.results)
+        })
+    }
+})
+
+function toResult(
+    ref: ExpressionReference,
+    context: Rule.RuleContext,
+): ExpressionReferenceResult {
+    const result: ExpressionReferenceResult = {
+        type: ref.type,
+        node: undefined,
+    }
+    for (const key of Object.keys(ref)) {
+        if (key === "type") continue
+
+        const o = (ref as any)[key]
+        result[key] = o.range ? context.getSourceCode().getText(o) : o
+    }
+
+    return result
+}
diff --git a/tests/lib/utils/ast-utils/extract-property-references.ts b/tests/lib/utils/ast-utils/extract-property-references.ts
new file mode 100644
index 0000000..50e3286
--- /dev/null
+++ b/tests/lib/utils/ast-utils/extract-property-references.ts
@@ -0,0 +1,347 @@
+import { Linter } from "eslint"
+import assert from "assert"
+import type * as ESTree from "estree"
+import { CALL, CONSTRUCT, ReferenceTracker } from "eslint-utils"
+import type { PropertyReference } from "../../../../lib/utils/ast-utils"
+import { extractPropertyReferences } from "../../../../lib/utils/ast-utils"
+import { isRegexpLiteral } from "../../../../lib/utils"
+
+type PropertyReferenceResult = {
+    [key: string]: { type: string; refs?: PropertyReferenceResult }
+}
+
+type TestCase = {
+    code: string
+    results: PropertyReferenceResult[]
+    sourceType?: "script" | "module"
+}
+const TESTCASES: TestCase[] = [
+    {
+        code: `const a = /a/`,
+        results: [
+            {
+                $unknown1: { type: "unknown" },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        a.b;
+        const d = a.c;
+        d.e;`,
+        results: [
+            {
+                b: {
+                    type: "member",
+                    refs: {
+                        $unknown1: { type: "unknown" },
+                    },
+                },
+                c: {
+                    type: "member",
+                    refs: {
+                        e: {
+                            type: "member",
+                            refs: {
+                                $unknown2: { type: "unknown" },
+                            },
+                        },
+                    },
+                },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        const b = 'bb'
+        a[b]
+        a[c];`,
+        results: [
+            {
+                bb: {
+                    type: "member",
+                    refs: {
+                        $unknown1: { type: "unknown" },
+                    },
+                },
+                $unknown2: {
+                    type: "unknown",
+                    refs: {
+                        $unknown3: { type: "unknown" },
+                    },
+                },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        const {b,c} = a;
+        c.d.e;`,
+        results: [
+            {
+                b: {
+                    type: "destructuring",
+                    refs: {
+                        $unknown1: { type: "unknown" },
+                    },
+                },
+                c: {
+                    type: "destructuring",
+                    refs: {
+                        d: {
+                            type: "member",
+                            refs: {
+                                e: {
+                                    type: "member",
+                                    refs: {
+                                        $unknown2: { type: "unknown" },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        const b = 'bb'
+        const {[b]: c, [d]: e} = a;`,
+        results: [
+            {
+                bb: {
+                    type: "destructuring",
+                    refs: {
+                        $unknown1: { type: "unknown" },
+                    },
+                },
+                $unknown2: {
+                    type: "unknown",
+                    refs: {
+                        $unknown3: { type: "unknown" },
+                    },
+                },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        for (const b of a) {
+            const y = b.group.y
+        }`,
+        results: [
+            {
+                $iteration1: {
+                    type: "iteration",
+                    refs: {
+                        group: {
+                            type: "member",
+                            refs: {
+                                y: {
+                                    type: "member",
+                                    refs: {
+                                        $unknown2: { type: "unknown" },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        const a1 = [...a];
+        a1[1];`,
+        results: [
+            {
+                "1": {
+                    type: "member",
+                    refs: {
+                        $unknown1: { type: "unknown" },
+                    },
+                },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        const a1 = {...a};
+        a1.source;`,
+        results: [
+            {
+                source: {
+                    type: "member",
+                    refs: {
+                        $unknown1: { type: "unknown" },
+                    },
+                },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        const arr = [1,2,3, ...a];
+        arr[4];`,
+        results: [
+            {
+                "1": {
+                    type: "member",
+                    refs: {
+                        $unknown1: { type: "unknown" },
+                    },
+                },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        const [b, ...arr] = [...a];
+        arr[0];`,
+        results: [
+            {
+                "0": {
+                    type: "destructuring",
+                    refs: {
+                        $unknown1: { type: "unknown" },
+                    },
+                },
+                "1": {
+                    type: "member",
+                    refs: {
+                        $unknown2: { type: "unknown" },
+                    },
+                },
+            },
+        ],
+    },
+    {
+        code: `const a = /a/;
+        fn([...a]);
+        function fn([,b] = []) {
+            b.c;
+        }`,
+        results: [
+            {
+                "1": {
+                    type: "destructuring",
+                    refs: {
+                        c: {
+                            type: "member",
+                            refs: {
+                                $unknown1: { type: "unknown" },
+                            },
+                        },
+                    },
+                },
+            },
+        ],
+    },
+]
+describe("extractPropertyReferences", () => {
+    for (const testCase of TESTCASES.reverse()) {
+        it(testCase.code, () => {
+            let results: PropertyReferenceResult[] = []
+            const regexps: (ESTree.NewExpression | ESTree.CallExpression)[] = []
+            const testNodes: (
+                | ESTree.Literal
+                | ESTree.NewExpression
+                | ESTree.CallExpression
+            )[] = []
+            const linter = new Linter()
+            linter.defineRule("test", {
+                create(context) {
+                    return {
+                        Program() {
+                            const scope = context.getScope()
+                            const tracker = new ReferenceTracker(scope)
+
+                            for (const {
+                                node,
+                            } of tracker.iterateGlobalReferences({
+                                RegExp: { [CALL]: true, [CONSTRUCT]: true },
+                            })) {
+                                const newOrCall = node as
+                                    | ESTree.NewExpression
+                                    | ESTree.CallExpression
+                                regexps.push(newOrCall)
+                            }
+                        },
+                        Literal(node: ESTree.Literal) {
+                            if (isRegexpLiteral(node)) {
+                                testNodes.push(node)
+                            }
+                        },
+                        "NewExpression,CallExpression"(
+                            node: ESTree.NewExpression | ESTree.CallExpression,
+                        ) {
+                            if (regexps.includes(node)) {
+                                testNodes.push(node)
+                            }
+                        },
+                        "Program:exit"() {
+                            results = testNodes.map((node) =>
+                                toResult([
+                                    ...extractPropertyReferences(node, context),
+                                ]),
+                            )
+                        },
+                    }
+                },
+            })
+            const r = linter.verify(
+                testCase.code,
+                {
+                    globals: {
+                        Set: "readonly",
+                        Map: "readonly",
+                        BigInt: "readonly",
+                        window: "readonly",
+                        globalThis: "readonly",
+                    },
+                    parserOptions: {
+                        ecmaVersion: 2020,
+                        sourceType: testCase.sourceType,
+                    },
+                    rules: {
+                        test: "error",
+                    },
+                },
+                "test.js",
+            )
+            if (r.length) {
+                assert.deepStrictEqual(r, [])
+            }
+
+            assert.deepStrictEqual(results, testCase.results)
+        })
+    }
+})
+
+function toResult(
+    refs: PropertyReference[],
+    seq = [0],
+): PropertyReferenceResult {
+    const result: PropertyReferenceResult = {}
+    for (const ref of refs) {
+        let name
+        if (ref.type === "member" || ref.type === "destructuring") {
+            name = ref.name
+        } else {
+            name = `$${ref.type}${String(++seq[0])}`
+        }
+        result[name] = {
+            type: ref.type,
+        }
+        if (ref.extractPropertyReferences) {
+            result[name].refs = toResult(
+                [...ref.extractPropertyReferences()],
+                seq,
+            )
+        }
+    }
+
+    return result
+}
diff --git a/tests/lib/utils/get-usage-of-pattern.ts b/tests/lib/utils/get-usage-of-pattern.ts
index 733d0e0..4b7e2e7 100644
--- a/tests/lib/utils/get-usage-of-pattern.ts
+++ b/tests/lib/utils/get-usage-of-pattern.ts
@@ -11,6 +11,7 @@ import { CALL, CONSTRUCT, ReferenceTracker } from "eslint-utils"
 type TestCase = {
     code: string
     results: UsageOfPattern[]
+    sourceType?: "script" | "module"
 }
 const TESTCASES: TestCase[] = [
     {
@@ -212,6 +213,25 @@ const TESTCASES: TestCase[] = [
         `,
         results: [UsageOfPattern.partial, UsageOfPattern.whole],
     },
+    {
+        code: `
+        /* exported a */
+        const a = /a/
+        const b = new RegExp(a.source+'b')
+        b.exec(str)
+        `,
+        results: [UsageOfPattern.unknown, UsageOfPattern.whole],
+        sourceType: "script",
+    },
+    {
+        code: `
+        export const a = /a/
+        const b = new RegExp(a.source+'b')
+        b.exec(str)
+        `,
+        results: [UsageOfPattern.unknown, UsageOfPattern.whole],
+        sourceType: "module",
+    },
 ]
 describe("getUsageOfPattern", () => {
     for (const testCase of TESTCASES) {
@@ -274,6 +294,7 @@ describe("getUsageOfPattern", () => {
                     },
                     parserOptions: {
                         ecmaVersion: 2020,
+                        sourceType: testCase.sourceType,
                     },
                     rules: {
                         test: "error",