Repository: ota-meshi/eslint-plugin-regexp (ts)

Issue:
`control-character-escape` doesn't report control characters in `/re/.source` expressions
**Description**
As the title says, `control-character-escape` doesn't report control characters in `re.source` expressions if those expressions are owned by a `RegExp` constructor.

![image](https://user-images.githubusercontent.com/20878432/133100883-a74eeb00-f4fb-4535-975f-c1f243afb399.png)



Pull request description:
Fix `control-character-escape` false negatives
fixes #326.

I used `PatternSource`'s replace ranges to determine whether the control character is part of a RegExp literal or not.

![image](https://user-images.githubusercontent.com/20878432/133101846-29aa929f-c8cb-4aa2-b468-a926fa1176f0.png)


Patch:
diff --git a/lib/rules/control-character-escape.ts b/lib/rules/control-character-escape.ts
index a131ee2..eb09705 100644
--- a/lib/rules/control-character-escape.ts
+++ b/lib/rules/control-character-escape.ts
@@ -1,3 +1,4 @@
+import type { PatternRange } from "../utils/ast-utils/pattern-source"
 import type { RegExpVisitor } from "regexpp/visitor"
 import type { RegExpContext } from "../utils"
 import {
@@ -21,6 +22,26 @@ const CONTROL_CHARS = new Map<number, string>([
     [CP_CR, "\\r"],
 ])
 
+/**
+ * Returns whether the regex is represented by a RegExp literal in source code
+ * at the given range.
+ */
+function isRegExpLiteralAt(
+    { node, patternSource }: RegExpContext,
+    at: PatternRange,
+): boolean {
+    if (isRegexpLiteral(node)) {
+        return true
+    }
+
+    const replaceRange = patternSource.getReplaceRange(at)
+    if (replaceRange && replaceRange.type === "RegExp") {
+        return true
+    }
+
+    return false
+}
+
 export default createRule("control-character-escape", {
     meta: {
         docs: {
@@ -40,11 +61,11 @@ export default createRule("control-character-escape", {
         /**
          * Create visitor
          */
-        function createVisitor({
-            node,
-            getRegexpLocation,
-            fixReplaceNode,
-        }: RegExpContext): RegExpVisitor.Handlers {
+        function createVisitor(
+            regexpContext: RegExpContext,
+        ): RegExpVisitor.Handlers {
+            const { node, getRegexpLocation, fixReplaceNode } = regexpContext
+
             return {
                 onCharacterEnter(cNode) {
                     if (cNode.parent.type === "CharacterClassRange") {
@@ -62,7 +83,7 @@ export default createRule("control-character-escape", {
                         return
                     }
                     if (
-                        !isRegexpLiteral(node) &&
+                        !isRegExpLiteralAt(regexpContext, cNode) &&
                         cNode.raw === String.fromCodePoint(cNode.value)
                     ) {
                         // we allow the direct usage of control characters in


Test patch:
diff --git a/tests/lib/rules/control-character-escape.ts b/tests/lib/rules/control-character-escape.ts
index 5d7fb17..75ba623 100644
--- a/tests/lib/rules/control-character-escape.ts
+++ b/tests/lib/rules/control-character-escape.ts
@@ -108,5 +108,12 @@ tester.run("control-character-escape", rule as any, {
                 "Unexpected control character escape '\\u0009' (U+0009). Use '\\t' instead.",
             ],
         },
+        {
+            code: String.raw`RegExp("\t\r\n\0" + /	/.source)`,
+            output: String.raw`RegExp("\t\r\n\0" + /\t/.source)`,
+            errors: [
+                "Unexpected control character escape '\t' (U+0009). Use '\\t' instead.",
+            ],
+        },
     ],
 })