Repository: eslint/eslint (js)

Issue:
New rule proposal: prefer-regex-literals
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for new rule proposals. If you are proposing a new rule, please continue on. If you are here for another reason, please see below:

    1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
    2. To request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
    3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
    4. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

    Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**Please describe what the rule should do:**

Suggests to use regex literals instead of `RegExp()`/`new RegExp()` when these calls have:

* One argument that is a string literal.
* Two arguments and they are both string literals.

**What category of rule is this? (place an "X" next to just one item)**

[X] Suggests an alternate way of doing something (suggestion)

**Provide 2-3 code examples that this rule will warn about:**

<!-- Put your code examples here -->
```js
// good, dynamic
new RegExp(pattern);
new RegExp(prefix + "foo");
new RegExp("foo", flags);

// good, regex literals
/^\d\./
/\d\d\.\d\d\.\d\d\d\d/

// bad, this is a very possible error
new RegExp("^\d\.");
new RegExp("\d\d\.\d\d\.\d\d\d\d");

// bad, correct but unreadable
new RegExp("^\\d\\.");
new RegExp("\\d\\d\\.\\d\\d\\.\\d\\d\\d\\d");
```

**Why should this rule be included in ESLint (instead of a plugin)?**

I think this is not just a style preference, rather a good practice to avoid an unnecessary level of the string literal syntax on top of the regex syntax. It's error-prone and difficult to reason about escapes of escapes.

**Are you willing to submit a pull request to implement this rule?**

Yes.


Pull request description:
New: Add prefer-regex-literals rule (fixes #12238)
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[X] New rule #12238

Examples of **incorrect** code for this rule:

```js
new RegExp("abc");
new RegExp("abc", "u");
RegExp("abc");
RegExp("abc", "u");
new RegExp("\\d\\d\\.\\d\\d\\.\\d\\d\\d\\d");
RegExp(`^\\d\\.$`);
new RegExp(String.raw`^\d\.$`);
```
Examples of **correct** code for this rule:

```js
/abc/;
/abc/u;
/\d\d\.\d\d\.\d\d\d\d/;
/^\d\.$/;
// RegExp constructor is allowed for dynamically generated regular expressions
new RegExp(pattern);
RegExp("abc", flags);
new RegExp(prefix + "abc");
RegExp(`${prefix}abc`);
new RegExp(String.raw`^\d\. ${sufix}`);
```
<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

Added new rule `prefer-regex-literals`

**Is there anything you'd like reviewers to focus on?**




Patch:
diff --git a/README.md b/README.md
index 4c02d619c..28f512920 100644
--- a/README.md
+++ b/README.md
@@ -262,7 +262,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <!-- NOTE: This section is autogenerated. Do not manually edit.-->
 <!--sponsorsstart-->
 <h3>Gold Sponsors</h3>
-<p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/eeb91aa/logo.png" alt="Shopify" height="96"></a> <a href="http://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/d1b37c4/logo.png" alt="Salesforce" height="96"></a> <a href="https://badoo.com/team?utm_source=eslint"><img src="https://images.opencollective.com/badoo/2826a3b/logo.png" alt="Badoo" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/001a341/logo.png" alt="Airbnb" height="96"></a> <a href="https://code.facebook.com/projects/"><img src="https://images.opencollective.com/fbopensource/fbb8a5b/logo.png" alt="Facebook Open Source" height="96"></a></p><h3>Silver Sponsors</h3>
+<p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/eeb91aa/logo.png" alt="Shopify" height="96"></a> <a href="http://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/d1b37c4/logo.png" alt="Salesforce" height="96"></a> <a href="https://badoo.com/team?utm_source=eslint"><img src="https://images.opencollective.com/badoo/2826a3b/logo.png" alt="Badoo" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://code.facebook.com/projects/"><img src="https://images.opencollective.com/fbopensource/fbb8a5b/logo.png" alt="Facebook Open Source" height="96"></a></p><h3>Silver Sponsors</h3>
 <p><a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
 <p><a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://moneypug.co.uk/"><img src="https://images.opencollective.com/moneypug/45f8d53/logo.png" alt="MONEYPUG" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://uxplanet.org/top-ui-ux-design-agencies-user-experience-firms-8c54697e290"><img src="https://images.opencollective.com/ui-ux-design-agencies/cae5dfe/logo.png" alt="UI UX Design Agencies" height="32"></a> <a href="https://clay.global"><img src="https://images.opencollective.com/clayglobal/2468f34/logo.png" alt="clay" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://jsheroes.io/"><img src="https://images.opencollective.com/jsheroes1/9fedf0b/logo.png" alt="JSHeroes " height="32"></a></p>
 <!--sponsorsend-->
diff --git a/docs/rules/no-obj-calls.md b/docs/rules/no-obj-calls.md
index 9484eb736..cfd5576ea 100644
--- a/docs/rules/no-obj-calls.md
+++ b/docs/rules/no-obj-calls.md
@@ -6,13 +6,17 @@ The [ECMAScript 5 specification](https://es5.github.io/#x15.8) makes it clear th
 
 > The Math object does not have a `[[Call]]` internal property; it is not possible to invoke the Math object as a function.
 
-And the [ECMAScript 2015 specification](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect-object) makes it clear that `Reflect` cannot be invoked:
+The [ECMAScript 2015 specification](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect-object) makes it clear that `Reflect` cannot be invoked:
 
 > The Reflect object also does not have a `[[Call]]` internal method; it is not possible to invoke the Reflect object as a function.
 
+And the [ECMAScript 2017 specification](https://www.ecma-international.org/ecma-262/8.0/index.html#sec-atomics-object) makes it clear that `Atomics` cannot be invoked:
+
+> The Atomics object does not have a [[Call]] internal method; it is not possible to invoke the Atomics object as a function.
+
 ## Rule Details
 
-This rule disallows calling the `Math`, `JSON` and `Reflect` objects as functions.
+This rule disallows calling the `Math`, `JSON`, `Reflect` and `Atomics` objects as functions.
 
 Examples of **incorrect** code for this rule:
 
@@ -22,6 +26,7 @@ Examples of **incorrect** code for this rule:
 var math = Math();
 var json = JSON();
 var reflect = Reflect();
+var atomics = Atomics();
 ```
 
 Examples of **correct** code for this rule:
@@ -34,6 +39,7 @@ function area(r) {
 }
 var object = JSON.parse("{}");
 var value = Reflect.get({ x: 1, y: 2 }, "x");
+var first = Atomics.load(foo, 0);
 ```
 
 ## Further Reading
diff --git a/docs/rules/no-sequences.md b/docs/rules/no-sequences.md
index f1f4afea0..602a412f1 100644
--- a/docs/rules/no-sequences.md
+++ b/docs/rules/no-sequences.md
@@ -60,7 +60,7 @@ switch ((val = foo(), val)) {}
 
 while ((val = foo(), val < 42));
 
-// with ((doSomething(), val)) {}
+with ((doSomething(), val)) {}
 ```
 
 ## When Not To Use It
diff --git a/docs/rules/no-useless-catch.md b/docs/rules/no-useless-catch.md
index d320e561d..0562063f6 100644
--- a/docs/rules/no-useless-catch.md
+++ b/docs/rules/no-useless-catch.md
@@ -43,6 +43,12 @@ try {
 } catch (e) {
   handleError(e);
 }
+
+try {
+  doSomethingThatMightThrow();
+} finally {
+  cleanUp();
+}
 ```
 
 ## When Not To Use It
diff --git a/docs/rules/prefer-regex-literals.md b/docs/rules/prefer-regex-literals.md
new file mode 100644
index 000000000..01d4149cf
--- /dev/null
+++ b/docs/rules/prefer-regex-literals.md
@@ -0,0 +1,90 @@
+# Disallow use of the `RegExp` constructor in favor of regular expression literals (prefer-regex-literals)
+
+There are two ways to create a regular expression:
+
+* Regular expression literals, e.g., `/abc/u`.
+* The `RegExp` constructor function, e.g., `new RegExp("abc", "u")` or `RegExp("abc", "u")`.
+
+The constructor function is particularly useful when you want to dynamically generate the pattern,
+because it takes string arguments.
+
+When using the constructor function with string literals, don't forget that the string escaping rules still apply.
+If you want to put a backslash in the pattern, you need to escape it in the string literal.
+Thus, the following are equivalent:
+
+```js
+new RegExp("^\\d\\.$");
+
+/^\d\.$/;
+
+// matches "0.", "1.", "2." ... "9."
+```
+
+In the above example, the regular expression literal is easier to read and reason about.
+Also, it's a common mistake to omit the extra `\` in the string literal, which would produce a completely different regular expression:
+
+```js
+new RegExp("^\d\.$");
+
+// equivalent to /^d.$/, matches "d1", "d2", "da", "db" ...
+```
+
+When a regular expression is known in advance, it is considered a best practice to avoid the string literal notation on top
+of the regular expression notation, and use regular expression literals instead of the constructor function.
+
+## Rule Details
+
+This rule disallows the use of the `RegExp` constructor function with string literals as its arguments.
+
+This rule also disallows the use of the `RegExp` constructor function with template literals without expressions
+and `String.raw` tagged template literals without expressions.
+
+The rule does not disallow all use of the `RegExp` constructor. It should be still used for
+dynamically generated regular expressions.
+
+Examples of **incorrect** code for this rule:
+
+```js
+new RegExp("abc");
+
+new RegExp("abc", "u");
+
+RegExp("abc");
+
+RegExp("abc", "u");
+
+new RegExp("\\d\\d\\.\\d\\d\\.\\d\\d\\d\\d");
+
+RegExp(`^\\d\\.$`);
+
+new RegExp(String.raw`^\d\.$`);
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/abc/;
+
+/abc/u;
+
+/\d\d\.\d\d\.\d\d\d\d/;
+
+/^\d\.$/;
+
+// RegExp constructor is allowed for dynamically generated regular expressions
+
+new RegExp(pattern);
+
+RegExp("abc", flags);
+
+new RegExp(prefix + "abc");
+
+RegExp(`${prefix}abc`);
+
+new RegExp(String.raw`^\d\. ${sufix}`);
+```
+
+## Further Reading
+
+* [MDN: Regular Expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
+* [MDN: RegExp Constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
diff --git a/lib/rules/eqeqeq.js b/lib/rules/eqeqeq.js
index 58ff9030d..57926dbed 100644
--- a/lib/rules/eqeqeq.js
+++ b/lib/rules/eqeqeq.js
@@ -116,18 +116,6 @@ module.exports = {
             return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left);
         }
 
-        /**
-         * Gets the location (line and column) of the binary expression's operator
-         * @param {ASTNode} node The binary expression node to check
-         * @returns {Object} { line, column } location of operator
-         * @private
-         */
-        function getOperatorLocation(node) {
-            const opToken = sourceCode.getTokenAfter(node.left);
-
-            return { line: opToken.loc.start.line, column: opToken.loc.start.column };
-        }
-
         /**
          * Reports a message for this rule.
          * @param {ASTNode} node The binary expression node that was checked
@@ -136,21 +124,21 @@ module.exports = {
          * @private
          */
         function report(node, expectedOperator) {
+            const operatorToken = sourceCode.getFirstTokenBetween(
+                node.left,
+                node.right,
+                token => token.value === node.operator
+            );
+
             context.report({
                 node,
-                loc: getOperatorLocation(node),
+                loc: operatorToken.loc,
                 messageId: "unexpected",
                 data: { expectedOperator, actualOperator: node.operator },
                 fix(fixer) {
 
                     // If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix.
                     if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
-                        const operatorToken = sourceCode.getFirstTokenBetween(
-                            node.left,
-                            node.right,
-                            token => token.value === node.operator
-                        );
-
                         return fixer.replaceText(operatorToken, expectedOperator);
                     }
                     return null;
diff --git a/lib/rules/index.js b/lib/rules/index.js
index 51d224d21..dcfe2e1c3 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -242,6 +242,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
     "prefer-object-spread": () => require("./prefer-object-spread"),
     "prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"),
     "prefer-reflect": () => require("./prefer-reflect"),
+    "prefer-regex-literals": () => require("./prefer-regex-literals"),
     "prefer-rest-params": () => require("./prefer-rest-params"),
     "prefer-spread": () => require("./prefer-spread"),
     "prefer-template": () => require("./prefer-template"),
diff --git a/lib/rules/no-obj-calls.js b/lib/rules/no-obj-calls.js
index 92492b7a2..5102d5594 100644
--- a/lib/rules/no-obj-calls.js
+++ b/lib/rules/no-obj-calls.js
@@ -5,6 +5,18 @@
 
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { CALL, ReferenceTracker } = require("eslint-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"];
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -20,23 +32,31 @@ module.exports = {
             url: "https://eslint.org/docs/rules/no-obj-calls"
         },
 
-        schema: []
+        schema: [],
+
+        messages: {
+            unexpectedCall: "'{{name}}' is not a function."
+        }
     },
 
     create(context) {
 
         return {
-            CallExpression(node) {
-
-                if (node.callee.type === "Identifier") {
-                    const name = node.callee.name;
+            Program() {
+                const scope = context.getScope();
+                const tracker = new ReferenceTracker(scope);
+                const traceMap = {};
+
+                for (const global of nonCallableGlobals) {
+                    traceMap[global] = {
+                        [CALL]: true
+                    };
+                }
 
-                    if (name === "Math" || name === "JSON" || name === "Reflect") {
-                        context.report({ node, message: "'{{name}}' is not a function.", data: { name } });
-                    }
+                for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
+                    context.report({ node, messageId: "unexpectedCall", data: { name: node.callee.name } });
                 }
             }
         };
-
     }
 };
diff --git a/lib/rules/no-octal-escape.js b/lib/rules/no-octal-escape.js
index a2860ca5b..7f6845ec7 100644
--- a/lib/rules/no-octal-escape.js
+++ b/lib/rules/no-octal-escape.js
@@ -20,7 +20,11 @@ module.exports = {
             url: "https://eslint.org/docs/rules/no-octal-escape"
         },
 
-        schema: []
+        schema: [],
+
+        messages: {
+            octalEscapeSequence: "Don't use octal: '\\{{sequence}}'. Use '\\u....' instead."
+        }
     },
 
     create(context) {
@@ -32,15 +36,17 @@ module.exports = {
                     return;
                 }
 
-                const match = node.raw.match(/^([^\\]|\\[^0-7])*\\([0-3][0-7]{1,2}|[4-7][0-7]|[0-7])/u);
+                // \0 represents a valid NULL character if it isn't followed by a digit.
+                const match = node.raw.match(
+                    /^(?:[^\\]|\\.)*?\\([0-3][0-7]{1,2}|[4-7][0-7]|[1-7])/u
+                );
 
                 if (match) {
-                    const octalDigit = match[2];
-
-                    // \0 is actually not considered an octal
-                    if (match[2] !== "0" || typeof match[3] !== "undefined") {
-                        context.report({ node, message: "Don't use octal: '\\{{octalDigit}}'. Use '\\u....' instead.", data: { octalDigit } });
-                    }
+                    context.report({
+                        node,
+                        messageId: "octalEscapeSequence",
+                        data: { sequence: match[1] }
+                    });
                 }
             }
 
diff --git a/lib/rules/no-unsafe-negation.js b/lib/rules/no-unsafe-negation.js
index f394f6742..717e5f6be 100644
--- a/lib/rules/no-unsafe-negation.js
+++ b/lib/rules/no-unsafe-negation.js
@@ -51,7 +51,7 @@ module.exports = {
         },
 
         schema: [],
-        fixable: "code",
+        fixable: null,
         messages: {
             unexpected: "Unexpected negating the left operand of '{{operator}}' operator."
         }
@@ -70,15 +70,7 @@ module.exports = {
                         node,
                         loc: node.left.loc,
                         messageId: "unexpected",
-                        data: { operator: node.operator },
-
-                        fix(fixer) {
-                            const negationToken = sourceCode.getFirstToken(node.left);
-                            const fixRange = [negationToken.range[1], node.range[1]];
-                            const text = sourceCode.text.slice(fixRange[0], fixRange[1]);
-
-                            return fixer.replaceTextRange(fixRange, `(${text})`);
-                        }
+                        data: { operator: node.operator }
                     });
                 }
             }
diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js
new file mode 100644
index 000000000..47b2b090f
--- /dev/null
+++ b/lib/rules/prefer-regex-literals.js
@@ -0,0 +1,125 @@
+/**
+ * @fileoverview Rule to disallow use of the `RegExp` constructor in favor of regular expression literals
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the given node is a string literal.
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} True if the node is a string literal.
+ */
+function isStringLiteral(node) {
+    return node.type === "Literal" && typeof node.value === "string";
+}
+
+/**
+ * Determines whether the given node is a template literal without expressions.
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} True if the node is a template literal without expressions.
+ */
+function isStaticTemplateLiteral(node) {
+    return node.type === "TemplateLiteral" && node.expressions.length === 0;
+}
+
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "disallow use of the `RegExp` constructor in favor of regular expression literals",
+            category: "Best Practices",
+            recommended: false,
+            url: "https://eslint.org/docs/rules/prefer-regex-literals"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Determines whether the given identifier node is a reference to a global variable.
+         * @param {ASTNode} node `Identifier` node to check.
+         * @returns {boolean} True if the identifier is a reference to a global variable.
+         */
+        function isGlobalReference(node) {
+            const scope = context.getScope();
+            const variable = findVariable(scope, node);
+
+            return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
+        }
+
+        /**
+         * Determines whether the given node is a String.raw`` tagged template expression
+         * with a static template literal.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} True if the node is String.raw`` with a static template.
+         */
+        function isStringRawTaggedStaticTemplateLiteral(node) {
+            return node.type === "TaggedTemplateExpression" &&
+                node.tag.type === "MemberExpression" &&
+                node.tag.object.type === "Identifier" &&
+                node.tag.object.name === "String" &&
+                isGlobalReference(node.tag.object) &&
+                astUtils.getStaticPropertyName(node.tag) === "raw" &&
+                isStaticTemplateLiteral(node.quasi);
+        }
+
+        /**
+         * Determines whether the given node is considered to be a static string by the logic of this rule.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} True if the node is a static string.
+         */
+        function isStaticString(node) {
+            return isStringLiteral(node) ||
+                isStaticTemplateLiteral(node) ||
+                isStringRawTaggedStaticTemplateLiteral(node);
+        }
+
+        return {
+            Program() {
+                const scope = context.getScope();
+                const tracker = new ReferenceTracker(scope);
+                const traceMap = {
+                    RegExp: {
+                        [CALL]: true,
+                        [CONSTRUCT]: true
+                    }
+                };
+
+                for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
+                    const args = node.arguments;
+
+                    if (
+                        (args.length === 1 || args.length === 2) &&
+                        args.every(isStaticString)
+                    ) {
+                        context.report({ node, messageId: "unexpectedRegExp" });
+                    }
+                }
+            }
+        };
+    }
+};
diff --git a/lib/rules/space-before-function-paren.js b/lib/rules/space-before-function-paren.js
index ca20dbc4a..3a6d430f3 100644
--- a/lib/rules/space-before-function-paren.js
+++ b/lib/rules/space-before-function-paren.js
@@ -124,7 +124,18 @@ module.exports = {
                     node,
                     loc: leftToken.loc.end,
                     message: "Unexpected space before function parentheses.",
-                    fix: fixer => fixer.removeRange([leftToken.range[1], rightToken.range[0]])
+                    fix(fixer) {
+                        const comments = sourceCode.getCommentsBefore(rightToken);
+
+                        // Don't fix anything if there's a single line comment between the left and the right token
+                        if (comments.some(comment => comment.type === "Line")) {
+                            return null;
+                        }
+                        return fixer.replaceTextRange(
+                            [leftToken.range[1], rightToken.range[0]],
+                            comments.reduce((text, comment) => text + sourceCode.getText(comment), "")
+                        );
+                    }
                 });
             } else if (!hasSpacing && functionConfig === "always") {
                 context.report({
diff --git a/tools/rule-types.json b/tools/rule-types.json
index 74cae9391..ea15a2cfd 100644
--- a/tools/rule-types.json
+++ b/tools/rule-types.json
@@ -229,6 +229,7 @@
     "prefer-object-spread": "suggestion",
     "prefer-promise-reject-errors": "suggestion",
     "prefer-reflect": "suggestion",
+    "prefer-regex-literals": "suggestion",
     "prefer-rest-params": "suggestion",
     "prefer-spread": "suggestion",
     "prefer-template": "suggestion",


Test patch:
diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js
index 0c6db3ff9..b3039e743 100644
--- a/tests/lib/cli-engine/cli-engine.js
+++ b/tests/lib/cli-engine/cli-engine.js
@@ -1641,6 +1641,8 @@ describe("CLIEngine", () => {
                             {
                                 column: 9,
                                 line: 2,
+                                endColumn: 11,
+                                endLine: 2,
                                 message: "Expected '===' and instead saw '=='.",
                                 messageId: "unexpected",
                                 nodeType: "BinaryExpression",
diff --git a/tests/lib/rules/eqeqeq.js b/tests/lib/rules/eqeqeq.js
index b74e9bc2c..05e33bbcf 100644
--- a/tests/lib/rules/eqeqeq.js
+++ b/tests/lib/rules/eqeqeq.js
@@ -105,6 +105,69 @@ ruleTester.run("eqeqeq", rule, {
                 { messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression", line: 1 },
                 { messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression", line: 1 }
             ]
+        },
+
+        // location tests
+        {
+            code: "a == b;",
+            errors: [
+                {
+                    messageId: "unexpected",
+                    data: wantedEqEqEq,
+                    type: "BinaryExpression",
+                    column: 3,
+                    endColumn: 5
+                }
+            ]
+        },
+        {
+            code: "a!=b;",
+            errors: [
+                {
+                    messageId: "unexpected",
+                    data: wantedNotEqEq,
+                    type: "BinaryExpression",
+                    column: 2,
+                    endColumn: 4
+                }
+            ]
+        },
+        {
+            code: "(a + b) == c;",
+            errors: [
+                {
+                    messageId: "unexpected",
+                    data: wantedEqEqEq,
+                    type: "BinaryExpression",
+                    column: 9,
+                    endColumn: 11
+                }
+            ]
+        },
+        {
+            code: "(a + b)  !=  c;",
+            errors: [
+                {
+                    messageId: "unexpected",
+                    data: wantedNotEqEq,
+                    type: "BinaryExpression",
+                    column: 10,
+                    endColumn: 12
+                }
+            ]
+        },
+        {
+            code: "((1) )  ==  (2);",
+            output: "((1) )  ===  (2);",
+            errors: [
+                {
+                    messageId: "unexpected",
+                    data: wantedEqEqEq,
+                    type: "BinaryExpression",
+                    column: 9,
+                    endColumn: 11
+                }
+            ]
         }
 
     // If no output is provided, assert that no output is produced.
diff --git a/tests/lib/rules/no-obj-calls.js b/tests/lib/rules/no-obj-calls.js
index 6f390ab45..d5bcf0638 100644
--- a/tests/lib/rules/no-obj-calls.js
+++ b/tests/lib/rules/no-obj-calls.js
@@ -20,11 +20,112 @@ const ruleTester = new RuleTester();
 
 ruleTester.run("no-obj-calls", rule, {
     valid: [
-        "var x = Math.random();"
+        "var x = Math;",
+        "var x = Math.random();",
+        "var x = Math.PI;",
+        "var x = foo.Math();",
+        "JSON.parse(foo)",
+        "Reflect.get(foo, 'x')",
+        "Atomics.load(foo, 0)",
+
+        // non-existing variables
+        "/*globals Math: off*/ Math();",
+        {
+            code: "JSON();",
+            globals: { JSON: "off" }
+        },
+        "Reflect();",
+        "Atomics();",
+        {
+            code: "Atomics();",
+            env: { es6: true }
+        },
+
+        // shadowed variables
+        "var Math; Math();",
+        {
+            code: "let JSON; JSON();",
+            parserOptions: { ecmaVersion: 2015 }
+        },
+        {
+            code: "if (foo) { const Reflect = 1; Reflect(); }",
+            parserOptions: { ecmaVersion: 2015 },
+            env: { es6: true }
+        },
+        "function foo(Math) { Math(); }",
+        {
+            code: "function foo(Atomics) { Atomics(); }",
+            env: { es2017: true }
+        },
+        "function foo() { var JSON; JSON(); }",
+        {
+            code: "function foo() { var Atomics = bar(); var baz = Atomics(5); }",
+            globals: { Atomics: false }
+        }
     ],
     invalid: [
-        { code: "var x = Math();", errors: [{ message: "'Math' is not a function.", type: "CallExpression" }] },
-        { code: "var x = JSON();", errors: [{ message: "'JSON' is not a function.", type: "CallExpression" }] },
-        { code: "var x = Reflect();", errors: [{ message: "'Reflect' is not a function.", type: "CallExpression" }] }
+
+        // test full message
+        {
+            code: "Math();",
+            errors: [{ message: "'Math' is not a function.", type: "CallExpression" }]
+        },
+
+        {
+            code: "var x = Math();",
+            errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression" }]
+        },
+        {
+            code: "f(Math());",
+            errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 3, endColumn: 9 }]
+        },
+        {
+            code: "Math().foo;",
+            errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 1, endColumn: 7 }]
+        },
+        {
+            code: "var x = JSON();",
+            errors: [{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression" }]
+        },
+        {
+            code: "x = JSON(str);",
+            errors: [{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression" }]
+        },
+        {
+            code: "Math( JSON() );",
+            errors: [
+                { messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 1, endColumn: 15 },
+                { messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression", column: 7, endColumn: 13 }
+            ]
+        },
+        {
+            code: "var x = Reflect();",
+            env: { es6: true },
+            errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }]
+        },
+        {
+            code: "var x = Reflect();",
+            env: { es2017: true },
+            errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }]
+        },
+        {
+            code: "/*globals Reflect: true*/ Reflect();",
+            errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }]
+        },
+        {
+            code: "var x = Atomics();",
+            env: { es2017: true },
+            errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }]
+        },
+        {
+            code: "var x = Atomics();",
+            env: { es2020: true },
+            errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }]
+        },
+        {
+            code: "var x = Atomics();",
+            globals: { Atomics: false },
+            errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }]
+        }
     ]
 });
diff --git a/tests/lib/rules/no-octal-escape.js b/tests/lib/rules/no-octal-escape.js
index b15416ebf..cd555ab60 100644
--- a/tests/lib/rules/no-octal-escape.js
+++ b/tests/lib/rules/no-octal-escape.js
@@ -24,13 +24,34 @@ ruleTester.run("no-octal-escape", rule, {
         "var foo = \"\\x51\";",
         "var foo = \"foo \\\\251 bar\";",
         "var foo = /([abc]) \\1/g;",
-        "var foo = '\\0';"
+        "var foo = '\\0';",
+        "'\\0 '",
+        "'\\0a'",
+        "'\\\\1'",
+        "'\\\\01'",
+        "'\\08'",
+        "'\\09'"
     ],
     invalid: [
+
+        // Test full message
         { code: "var foo = \"foo \\01 bar\";", errors: [{ message: "Don't use octal: '\\01'. Use '\\u....' instead.", type: "Literal" }] },
-        { code: "var foo = \"foo \\251 bar\";", errors: [{ message: "Don't use octal: '\\251'. Use '\\u....' instead.", type: "Literal" }] },
-        { code: "var foo = \"\\751\";", errors: [{ message: "Don't use octal: '\\75'. Use '\\u....' instead.", type: "Literal" }] },
-        { code: "var foo = \"\\3s51\";", errors: [{ message: "Don't use octal: '\\3'. Use '\\u....' instead.", type: "Literal" }] },
-        { code: "var foo = \"\\\\\\751\";", errors: [{ message: "Don't use octal: '\\75'. Use '\\u....' instead.", type: "Literal" }] }
+
+        { code: "var foo = \"foo \\251 bar\";", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "251" }, type: "Literal" }] },
+        { code: "var foo = \"\\751\";", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "75" }, type: "Literal" }] },
+        { code: "var foo = \"\\3s51\";", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "3" }, type: "Literal" }] },
+        { code: "var foo = \"\\\\\\751\";", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "75" }, type: "Literal" }] },
+        { code: "'\\0\\1'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "1" }, type: "Literal" }] },
+        { code: "'\\0 \\1'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "1" }, type: "Literal" }] },
+        { code: "'\\0\\01'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "01" }, type: "Literal" }] },
+        { code: "'\\0 \\01'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "01" }, type: "Literal" }] },
+        { code: "'\\08\\1'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "1" }, type: "Literal" }] },
+        { code: "'\\08\\01'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "01" }, type: "Literal" }] },
+
+        // Only the first one is reported
+        { code: "'\\01\\02'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "01" }, type: "Literal" }] },
+        { code: "'\\02\\01'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "02" }, type: "Literal" }] },
+        { code: "'\\01\\2'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "01" }, type: "Literal" }] },
+        { code: "'\\2\\01'", errors: [{ messageId: "octalEscapeSequence", data: { sequence: "2" }, type: "Literal" }] }
     ]
 });
diff --git a/tests/lib/rules/no-unsafe-negation.js b/tests/lib/rules/no-unsafe-negation.js
index 8316aad02..6ac245412 100644
--- a/tests/lib/rules/no-unsafe-negation.js
+++ b/tests/lib/rules/no-unsafe-negation.js
@@ -34,32 +34,26 @@ ruleTester.run("no-unsafe-negation", rule, {
     invalid: [
         {
             code: "!a in b",
-            output: "!(a in b)",
             errors: [unexpectedInError]
         },
         {
             code: "(!a in b)",
-            output: "(!(a in b))",
             errors: [unexpectedInError]
         },
         {
             code: "!(a) in b",
-            output: "!((a) in b)",
             errors: [unexpectedInError]
         },
         {
             code: "!a instanceof b",
-            output: "!(a instanceof b)",
             errors: [unexpectedInstanceofError]
         },
         {
             code: "(!a instanceof b)",
-            output: "(!(a instanceof b))",
             errors: [unexpectedInstanceofError]
         },
         {
             code: "!(a) instanceof b",
-            output: "!((a) instanceof b)",
             errors: [unexpectedInstanceofError]
         }
     ]
diff --git a/tests/lib/rules/prefer-regex-literals.js b/tests/lib/rules/prefer-regex-literals.js
new file mode 100644
index 000000000..e263ef03d
--- /dev/null
+++ b/tests/lib/rules/prefer-regex-literals.js
@@ -0,0 +1,182 @@
+/**
+ * @fileoverview Tests for the prefer-regex-literals rule
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/prefer-regex-literals");
+const { RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
+
+ruleTester.run("prefer-regex-literals", rule, {
+    valid: [
+        "/abc/",
+        "/abc/g",
+
+        // considered as dynamic
+        "new RegExp(pattern)",
+        "RegExp(pattern, 'g')",
+        "new RegExp(f('a'))",
+        "RegExp(prefix + 'a')",
+        "new RegExp('a' + sufix)",
+        "RegExp(`a` + sufix);",
+        "new RegExp(String.raw`a` + sufix);",
+        "RegExp('a', flags)",
+        "RegExp('a', 'g' + flags)",
+        "new RegExp(String.raw`a`, flags);",
+        "RegExp(`${prefix}abc`)",
+        "new RegExp(`a${b}c`);",
+        "new RegExp(`a${''}c`);",
+        "new RegExp(String.raw`a${b}c`);",
+        "new RegExp(String.raw`a${''}c`);",
+        "new RegExp('a' + 'b')",
+        "RegExp(1)",
+
+        // invalid number of arguments
+        "new RegExp;",
+        "new RegExp();",
+        "RegExp();",
+        "new RegExp('a', 'g', 'b');",
+        "RegExp('a', 'g', 'b');",
+        "new RegExp(`a`, `g`, `b`);",
+        "RegExp(`a`, `g`, `b`);",
+        "new RegExp(String.raw`a`, String.raw`g`, String.raw`b`);",
+        "RegExp(String.raw`a`, String.raw`g`, String.raw`b`);",
+
+        // not String.raw``
+        "new RegExp(String`a`);",
+        "RegExp(raw`a`);",
+        "new RegExp(f(String.raw)`a`);",
+        "RegExp(string.raw`a`);",
+        "new RegExp(String.Raw`a`);",
+        "new RegExp(String[raw]`a`);",
+        "RegExp(String.raw.foo`a`);",
+        "new RegExp(String.foo.raw`a`);",
+        "RegExp(foo.String.raw`a`);",
+        "new RegExp(String.raw);",
+
+        // not the global String in String.raw``
+        "let String; new RegExp(String.raw`a`);",
+        "function foo() { var String; new RegExp(String.raw`a`); }",
+        "function foo(String) { RegExp(String.raw`a`); }",
+        "if (foo) { const String = bar; RegExp(String.raw`a`); }",
+        "/* globals String:off */ new RegExp(String.raw`a`);",
+        {
+            code: "RegExp('a', String.raw`g`);",
+            globals: { String: "off" }
+        },
+
+        // not RegExp
+        "new Regexp('abc');",
+        "Regexp(`a`);",
+        "new Regexp(String.raw`a`);",
+
+        // not the global RegExp
+        "let RegExp; new RegExp('a');",
+        "function foo() { var RegExp; RegExp('a', 'g'); }",
+        "function foo(RegExp) { new RegExp(String.raw`a`); }",
+        "if (foo) { const RegExp = bar; RegExp('a'); }",
+        "/* globals RegExp:off */ new RegExp('a');",
+        {
+            code: "RegExp('a');",
+            globals: { RegExp: "off" }
+        }
+    ],
+
+    invalid: [
+        {
+            code: "new RegExp('abc');",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "RegExp('abc');",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+        {
+            code: "new RegExp('abc', 'g');",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "RegExp('abc', 'g');",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+        {
+            code: "new RegExp(`abc`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "RegExp(`abc`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+        {
+            code: "new RegExp(`abc`, `g`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "RegExp(`abc`, `g`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+        {
+            code: "new RegExp(String.raw`abc`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "RegExp(String.raw`abc`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+        {
+            code: "new RegExp(String.raw`abc`, String.raw`g`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "RegExp(String.raw`abc`, String.raw`g`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+        {
+            code: "new RegExp(String['raw']`a`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "new RegExp('');",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "RegExp('', '');",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+        {
+            code: "new RegExp(String.raw``);",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "new RegExp('a', `g`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "RegExp(`a`, 'g');",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+        {
+            code: "RegExp(String.raw`a`, 'g');",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+        {
+            code: "new RegExp(String.raw`\\d`, `g`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }]
+        },
+        {
+            code: "RegExp('a', String.raw`g`);",
+            errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        }
+    ]
+});
diff --git a/tests/lib/rules/space-before-function-paren.js b/tests/lib/rules/space-before-function-paren.js
index 5bcfd7be1..961d6895a 100644
--- a/tests/lib/rules/space-before-function-paren.js
+++ b/tests/lib/rules/space-before-function-paren.js
@@ -24,6 +24,11 @@ ruleTester.run("space-before-function-paren", rule, {
         "function foo () {}",
         "var foo = function () {}",
         "var bar = function foo () {}",
+        "var bar = function foo/**/ () {}",
+        "var bar = function foo /**/() {}",
+        "var bar = function foo/**/\n() {}",
+        "var bar = function foo\n/**/() {}",
+        "var bar = function foo//\n() {}",
         "var obj = { get foo () {}, set foo (val) {} };",
         {
             code: "var obj = { foo () {} };",
@@ -34,6 +39,9 @@ ruleTester.run("space-before-function-paren", rule, {
 
         { code: "function foo() {}", options: ["never"] },
         { code: "var foo = function() {}", options: ["never"] },
+        { code: "var foo = function/**/() {}", options: ["never"] },
+        { code: "var foo = function/* */() {}", options: ["never"] },
+        { code: "var foo = function/* *//*  */() {}", options: ["never"] },
         { code: "var bar = function foo() {}", options: ["never"] },
         { code: "var obj = { get foo() {}, set foo(val) {} };", options: ["never"] },
         {
@@ -226,6 +234,84 @@ ruleTester.run("space-before-function-paren", rule, {
                 }
             ]
         },
+        {
+            code: "function foo /* */ () {}",
+            output: "function foo/* */() {}",
+            options: ["never"],
+            errors: [
+                {
+                    type: "FunctionDeclaration",
+                    message: "Unexpected space before function parentheses.",
+                    line: 1,
+                    column: 13
+                }
+            ]
+        },
+        {
+            code: "function foo/* block comment */ () {}",
+            output: "function foo/* block comment */() {}",
+            options: ["never"],
+            errors: [
+                {
+                    type: "FunctionDeclaration",
+                    message: "Unexpected space before function parentheses.",
+                    line: 1,
+                    column: 13
+                }
+            ]
+        },
+        {
+            code: "function foo/* 1 */ /* 2 */ \n /* 3 */\n/* 4 */ () {}",
+            output: "function foo/* 1 *//* 2 *//* 3 *//* 4 */() {}",
+            options: ["never"],
+            errors: [
+                {
+                    type: "FunctionDeclaration",
+                    message: "Unexpected space before function parentheses.",
+                    line: 1,
+                    column: 13
+                }
+            ]
+        },
+        {
+            code: "function foo//\n() {}",
+            output: null,
+            options: ["never"],
+            errors: [
+                {
+                    type: "FunctionDeclaration",
+                    message: "Unexpected space before function parentheses.",
+                    line: 1,
+                    column: 13
+                }
+            ]
+        },
+        {
+            code: "function foo // line comment \n () {}",
+            output: null,
+            options: ["never"],
+            errors: [
+                {
+                    type: "FunctionDeclaration",
+                    message: "Unexpected space before function parentheses.",
+                    line: 1,
+                    column: 13
+                }
+            ]
+        },
+        {
+            code: "function foo\n//\n() {}",
+            output: null,
+            options: ["never"],
+            errors: [
+                {
+                    type: "FunctionDeclaration",
+                    message: "Unexpected space before function parentheses.",
+                    line: 1,
+                    column: 13
+                }
+            ]
+        },
         {
             code: "var foo = function () {}",
             output: "var foo = function() {}",