massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / utils / ast-utils.js
index 01c6b47b82eefcf9d4e53beb8f2df21375009594..6b853001132ed29c9a2b779d165b219e2a2442c3 100644 (file)
@@ -11,7 +11,7 @@
 
 const esutils = require("esutils");
 const espree = require("espree");
-const lodash = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
 const {
     breakableTypePattern,
     createGlobalLinebreakMatcher,
@@ -37,8 +37,12 @@ const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
 // A set of node types that can contain a list of statements
 const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
 
-const DECIMAL_INTEGER_PATTERN = /^(0|[1-9]\d*)$/u;
-const OCTAL_ESCAPE_PATTERN = /^(?:[^\\]|\\[^0-7]|\\0(?![0-9]))*\\(?:[1-7]|0[0-9])/u;
+const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u;
+
+// Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string
+const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su;
+
+const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]);
 
 /**
  * Checks reference if is non initializer and writable.
@@ -78,7 +82,7 @@ function startsWithUpperCase(s) {
 /**
  * Checks whether or not a node is a constructor.
  * @param {ASTNode} node A function node to check.
- * @returns {boolean} Wehether or not a node is a constructor.
+ * @returns {boolean} Whether or not a node is a constructor.
  */
 function isES5Constructor(node) {
     return (node.id && startsWithUpperCase(node.id.name));
@@ -143,6 +147,23 @@ function isInLoop(node) {
     return false;
 }
 
+/**
+ * Determines whether the given node is a `null` literal.
+ * @param {ASTNode} node The node to check
+ * @returns {boolean} `true` if the node is a `null` literal
+ */
+function isNullLiteral(node) {
+
+    /*
+     * Checking `node.value === null` does not guarantee that a literal is a null literal.
+     * When parsing values that cannot be represented in the current environment (e.g. unicode
+     * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
+     * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
+     * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
+     */
+    return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
+}
+
 /**
  * Checks whether or not a node is `null` or `undefined`.
  * @param {ASTNode} node A node to check.
@@ -151,7 +172,7 @@ function isInLoop(node) {
  */
 function isNullOrUndefined(node) {
     return (
-        module.exports.isNullLiteral(node) ||
+        isNullLiteral(node) ||
         (node.type === "Identifier" && node.name === "undefined") ||
         (node.type === "UnaryExpression" && node.operator === "void")
     );
@@ -166,20 +187,270 @@ function isCallee(node) {
     return node.parent.type === "CallExpression" && node.parent.callee === node;
 }
 
+/**
+ * Returns the result of the string conversion applied to the evaluated value of the given expression node,
+ * if it can be determined statically.
+ *
+ * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
+ * In all other cases, this function returns `null`.
+ * @param {ASTNode} node Expression node.
+ * @returns {string|null} String value if it can be determined. Otherwise, `null`.
+ */
+function getStaticStringValue(node) {
+    switch (node.type) {
+        case "Literal":
+            if (node.value === null) {
+                if (isNullLiteral(node)) {
+                    return String(node.value); // "null"
+                }
+                if (node.regex) {
+                    return `/${node.regex.pattern}/${node.regex.flags}`;
+                }
+                if (node.bigint) {
+                    return node.bigint;
+                }
+
+                // Otherwise, this is an unknown literal. The function will return null.
+
+            } else {
+                return String(node.value);
+            }
+            break;
+        case "TemplateLiteral":
+            if (node.expressions.length === 0 && node.quasis.length === 1) {
+                return node.quasis[0].value.cooked;
+            }
+            break;
+
+            // no default
+    }
+
+    return null;
+}
+
+/**
+ * Gets the property name of a given node.
+ * The node can be a MemberExpression, a Property, or a MethodDefinition.
+ *
+ * If the name is dynamic, this returns `null`.
+ *
+ * For examples:
+ *
+ *     a.b           // => "b"
+ *     a["b"]        // => "b"
+ *     a['b']        // => "b"
+ *     a[`b`]        // => "b"
+ *     a[100]        // => "100"
+ *     a[b]          // => null
+ *     a["a" + "b"]  // => null
+ *     a[tag`b`]     // => null
+ *     a[`${b}`]     // => null
+ *
+ *     let a = {b: 1}            // => "b"
+ *     let a = {["b"]: 1}        // => "b"
+ *     let a = {['b']: 1}        // => "b"
+ *     let a = {[`b`]: 1}        // => "b"
+ *     let a = {[100]: 1}        // => "100"
+ *     let a = {[b]: 1}          // => null
+ *     let a = {["a" + "b"]: 1}  // => null
+ *     let a = {[tag`b`]: 1}     // => null
+ *     let a = {[`${b}`]: 1}     // => null
+ * @param {ASTNode} node The node to get.
+ * @returns {string|null} The property name if static. Otherwise, null.
+ */
+function getStaticPropertyName(node) {
+    let prop;
+
+    switch (node && node.type) {
+        case "ChainExpression":
+            return getStaticPropertyName(node.expression);
+
+        case "Property":
+        case "MethodDefinition":
+            prop = node.key;
+            break;
+
+        case "MemberExpression":
+            prop = node.property;
+            break;
+
+            // no default
+    }
+
+    if (prop) {
+        if (prop.type === "Identifier" && !node.computed) {
+            return prop.name;
+        }
+
+        return getStaticStringValue(prop);
+    }
+
+    return null;
+}
+
+/**
+ * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
+ * @param {ASTNode} node The node to address.
+ * @returns {ASTNode} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node.
+ */
+function skipChainExpression(node) {
+    return node && node.type === "ChainExpression" ? node.expression : node;
+}
+
+/**
+ * Check if the `actual` is an expected value.
+ * @param {string} actual The string value to check.
+ * @param {string | RegExp} expected The expected string value or pattern.
+ * @returns {boolean} `true` if the `actual` is an expected value.
+ */
+function checkText(actual, expected) {
+    return typeof expected === "string"
+        ? actual === expected
+        : expected.test(actual);
+}
+
+/**
+ * Check if a given node is an Identifier node with a given name.
+ * @param {ASTNode} node The node to check.
+ * @param {string | RegExp} name The expected name or the expected pattern of the object name.
+ * @returns {boolean} `true` if the node is an Identifier node with the name.
+ */
+function isSpecificId(node, name) {
+    return node.type === "Identifier" && checkText(node.name, name);
+}
+
+/**
+ * Check if a given node is member access with a given object name and property name pair.
+ * This is regardless of optional or not.
+ * @param {ASTNode} node The node to check.
+ * @param {string | RegExp | null} objectName The expected name or the expected pattern of the object name. If this is nullish, this method doesn't check object.
+ * @param {string | RegExp | null} propertyName The expected name or the expected pattern of the property name. If this is nullish, this method doesn't check property.
+ * @returns {boolean} `true` if the node is member access with the object name and property name pair.
+ * The node is a `MemberExpression` or `ChainExpression`.
+ */
+function isSpecificMemberAccess(node, objectName, propertyName) {
+    const checkNode = skipChainExpression(node);
+
+    if (checkNode.type !== "MemberExpression") {
+        return false;
+    }
+
+    if (objectName && !isSpecificId(checkNode.object, objectName)) {
+        return false;
+    }
+
+    if (propertyName) {
+        const actualPropertyName = getStaticPropertyName(checkNode);
+
+        if (typeof actualPropertyName !== "string" || !checkText(actualPropertyName, propertyName)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Check if two literal nodes are the same value.
+ * @param {ASTNode} left The Literal node to compare.
+ * @param {ASTNode} right The other Literal node to compare.
+ * @returns {boolean} `true` if the two literal nodes are the same value.
+ */
+function equalLiteralValue(left, right) {
+
+    // RegExp literal.
+    if (left.regex || right.regex) {
+        return Boolean(
+            left.regex &&
+            right.regex &&
+            left.regex.pattern === right.regex.pattern &&
+            left.regex.flags === right.regex.flags
+        );
+    }
+
+    // BigInt literal.
+    if (left.bigint || right.bigint) {
+        return left.bigint === right.bigint;
+    }
+
+    return left.value === right.value;
+}
+
+/**
+ * Check if two expressions reference the same value. For example:
+ *     a = a
+ *     a.b = a.b
+ *     a[0] = a[0]
+ *     a['b'] = a['b']
+ * @param {ASTNode} left The left side of the comparison.
+ * @param {ASTNode} right The right side of the comparison.
+ * @param {boolean} [disableStaticComputedKey] Don't address `a.b` and `a["b"]` are the same if `true`. For backward compatibility.
+ * @returns {boolean} `true` if both sides match and reference the same value.
+ */
+function isSameReference(left, right, disableStaticComputedKey = false) {
+    if (left.type !== right.type) {
+
+        // Handle `a.b` and `a?.b` are samely.
+        if (left.type === "ChainExpression") {
+            return isSameReference(left.expression, right, disableStaticComputedKey);
+        }
+        if (right.type === "ChainExpression") {
+            return isSameReference(left, right.expression, disableStaticComputedKey);
+        }
+
+        return false;
+    }
+
+    switch (left.type) {
+        case "Super":
+        case "ThisExpression":
+            return true;
+
+        case "Identifier":
+            return left.name === right.name;
+        case "Literal":
+            return equalLiteralValue(left, right);
+
+        case "ChainExpression":
+            return isSameReference(left.expression, right.expression, disableStaticComputedKey);
+
+        case "MemberExpression": {
+            if (!disableStaticComputedKey) {
+                const nameA = getStaticPropertyName(left);
+
+                // x.y = x["y"]
+                if (nameA !== null) {
+                    return (
+                        isSameReference(left.object, right.object, disableStaticComputedKey) &&
+                        nameA === getStaticPropertyName(right)
+                    );
+                }
+            }
+
+            /*
+             * x[0] = x[0]
+             * x[y] = x[y]
+             * x.y = x.y
+             */
+            return (
+                left.computed === right.computed &&
+                isSameReference(left.object, right.object, disableStaticComputedKey) &&
+                isSameReference(left.property, right.property, disableStaticComputedKey)
+            );
+        }
+
+        default:
+            return false;
+    }
+}
+
 /**
  * Checks whether or not a node is `Reflect.apply`.
  * @param {ASTNode} node A node to check.
  * @returns {boolean} Whether or not the node is a `Reflect.apply`.
  */
 function isReflectApply(node) {
-    return (
-        node.type === "MemberExpression" &&
-        node.object.type === "Identifier" &&
-        node.object.name === "Reflect" &&
-        node.property.type === "Identifier" &&
-        node.property.name === "apply" &&
-        node.computed === false
-    );
+    return isSpecificMemberAccess(node, "Reflect", "apply");
 }
 
 /**
@@ -188,14 +459,7 @@ function isReflectApply(node) {
  * @returns {boolean} Whether or not the node is a `Array.from`.
  */
 function isArrayFromMethod(node) {
-    return (
-        node.type === "MemberExpression" &&
-        node.object.type === "Identifier" &&
-        arrayOrTypedArrayPattern.test(node.object.name) &&
-        node.property.type === "Identifier" &&
-        node.property.name === "from" &&
-        node.computed === false
-    );
+    return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from");
 }
 
 /**
@@ -204,17 +468,7 @@ function isArrayFromMethod(node) {
  * @returns {boolean} Whether or not the node is a method which has `thisArg`.
  */
 function isMethodWhichHasThisArg(node) {
-    for (
-        let currentNode = node;
-        currentNode.type === "MemberExpression" && !currentNode.computed;
-        currentNode = currentNode.property
-    ) {
-        if (currentNode.property.type === "Identifier") {
-            return arrayMethodPattern.test(currentNode.property.name);
-        }
-    }
-
-    return false;
+    return isSpecificMemberAccess(node, null, arrayMethodPattern);
 }
 
 /**
@@ -289,6 +543,15 @@ function isDotToken(token) {
     return token.value === "." && token.type === "Punctuator";
 }
 
+/**
+ * Checks if the given token is a `?.` token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a `?.` token.
+ */
+function isQuestionDotToken(token) {
+    return token.value === "?." && token.type === "Punctuator";
+}
+
 /**
  * Checks if the given token is a semicolon token or not.
  * @param {Token} token The token to check.
@@ -416,6 +679,62 @@ function equalTokens(left, right, sourceCode) {
     return true;
 }
 
+/**
+ * Check if the given node is a true logical expression or not.
+ *
+ * The three binary expressions logical-or (`||`), logical-and (`&&`), and
+ * coalesce (`??`) are known as `ShortCircuitExpression`.
+ * But ESTree represents those by `LogicalExpression` node.
+ *
+ * This function rejects coalesce expressions of `LogicalExpression` node.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is `&&` or `||`.
+ * @see https://tc39.es/ecma262/#prod-ShortCircuitExpression
+ */
+function isLogicalExpression(node) {
+    return (
+        node.type === "LogicalExpression" &&
+            (node.operator === "&&" || node.operator === "||")
+    );
+}
+
+/**
+ * Check if the given node is a nullish coalescing expression or not.
+ *
+ * The three binary expressions logical-or (`||`), logical-and (`&&`), and
+ * coalesce (`??`) are known as `ShortCircuitExpression`.
+ * But ESTree represents those by `LogicalExpression` node.
+ *
+ * This function finds only coalesce expressions of `LogicalExpression` node.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is `??`.
+ */
+function isCoalesceExpression(node) {
+    return node.type === "LogicalExpression" && node.operator === "??";
+}
+
+/**
+ * Check if given two nodes are the pair of a logical expression and a coalesce expression.
+ * @param {ASTNode} left A node to check.
+ * @param {ASTNode} right Another node to check.
+ * @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression.
+ */
+function isMixedLogicalAndCoalesceExpressions(left, right) {
+    return (
+        (isLogicalExpression(left) && isCoalesceExpression(right)) ||
+            (isCoalesceExpression(left) && isLogicalExpression(right))
+    );
+}
+
+/**
+ * Checks if the given operator is a logical assignment operator.
+ * @param {string} operator The operator to check.
+ * @returns {boolean} `true` if the operator is a logical assignment operator.
+ */
+function isLogicalAssignmentOperator(operator) {
+    return LOGICAL_ASSIGNMENT_OPERATORS.has(operator);
+}
+
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
@@ -458,6 +777,7 @@ module.exports = {
     isCommaToken,
     isCommentToken,
     isDotToken,
+    isQuestionDotToken,
     isKeywordToken,
     isNotClosingBraceToken: negate(isClosingBraceToken),
     isNotClosingBracketToken: negate(isClosingBracketToken),
@@ -465,6 +785,7 @@ module.exports = {
     isNotColonToken: negate(isColonToken),
     isNotCommaToken: negate(isCommaToken),
     isNotDotToken: negate(isDotToken),
+    isNotQuestionDotToken: negate(isQuestionDotToken),
     isNotOpeningBraceToken: negate(isOpeningBraceToken),
     isNotOpeningBracketToken: negate(isOpeningBracketToken),
     isNotOpeningParenToken: negate(isOpeningParenToken),
@@ -622,6 +943,7 @@ module.exports = {
                  */
                 case "LogicalExpression":
                 case "ConditionalExpression":
+                case "ChainExpression":
                     currentNode = parent;
                     break;
 
@@ -708,14 +1030,21 @@ module.exports = {
                  *   (function foo() { ... }).apply(obj, []);
                  */
                 case "MemberExpression":
-                    return (
-                        parent.object !== currentNode ||
-                        parent.property.type !== "Identifier" ||
-                        !bindOrCallOrApplyPattern.test(parent.property.name) ||
-                        !isCallee(parent) ||
-                        parent.parent.arguments.length === 0 ||
-                        isNullOrUndefined(parent.parent.arguments[0])
-                    );
+                    if (
+                        parent.object === currentNode &&
+                        isSpecificMemberAccess(parent, null, bindOrCallOrApplyPattern)
+                    ) {
+                        const maybeCalleeNode = parent.parent.type === "ChainExpression"
+                            ? parent.parent
+                            : parent;
+
+                        return !(
+                            isCallee(maybeCalleeNode) &&
+                            maybeCalleeNode.parent.arguments.length >= 1 &&
+                            !isNullOrUndefined(maybeCalleeNode.parent.arguments[0])
+                        );
+                    }
+                    return true;
 
                 /*
                  * e.g.
@@ -779,6 +1108,7 @@ module.exports = {
             case "LogicalExpression":
                 switch (node.operator) {
                     case "||":
+                    case "??":
                         return 4;
                     case "&&":
                         return 5;
@@ -836,6 +1166,7 @@ module.exports = {
                 return 17;
 
             case "CallExpression":
+            case "ChainExpression":
             case "ImportExpression":
                 return 18;
 
@@ -865,74 +1196,6 @@ module.exports = {
         return isFunction(node) && module.exports.isEmptyBlock(node.body);
     },
 
-    /**
-     * Gets the property name of a given node.
-     * The node can be a MemberExpression, a Property, or a MethodDefinition.
-     *
-     * If the name is dynamic, this returns `null`.
-     *
-     * For examples:
-     *
-     *     a.b           // => "b"
-     *     a["b"]        // => "b"
-     *     a['b']        // => "b"
-     *     a[`b`]        // => "b"
-     *     a[100]        // => "100"
-     *     a[b]          // => null
-     *     a["a" + "b"]  // => null
-     *     a[tag`b`]     // => null
-     *     a[`${b}`]     // => null
-     *
-     *     let a = {b: 1}            // => "b"
-     *     let a = {["b"]: 1}        // => "b"
-     *     let a = {['b']: 1}        // => "b"
-     *     let a = {[`b`]: 1}        // => "b"
-     *     let a = {[100]: 1}        // => "100"
-     *     let a = {[b]: 1}          // => null
-     *     let a = {["a" + "b"]: 1}  // => null
-     *     let a = {[tag`b`]: 1}     // => null
-     *     let a = {[`${b}`]: 1}     // => null
-     * @param {ASTNode} node The node to get.
-     * @returns {string|null} The property name if static. Otherwise, null.
-     */
-    getStaticPropertyName(node) {
-        let prop;
-
-        switch (node && node.type) {
-            case "Property":
-            case "MethodDefinition":
-                prop = node.key;
-                break;
-
-            case "MemberExpression":
-                prop = node.property;
-                break;
-
-            // no default
-        }
-
-        switch (prop && prop.type) {
-            case "Literal":
-                return String(prop.value);
-
-            case "TemplateLiteral":
-                if (prop.expressions.length === 0 && prop.quasis.length === 1) {
-                    return prop.quasis[0].value.cooked;
-                }
-                break;
-
-            case "Identifier":
-                if (!node.computed) {
-                    return prop.name;
-                }
-                break;
-
-            // no default
-        }
-
-        return null;
-    },
-
     /**
      * Get directives from directive prologue of a Program or Function node.
      * @param {ASTNode} node The node to check.
@@ -978,15 +1241,27 @@ module.exports = {
      * @returns {boolean} `true` if this node is a decimal integer.
      * @example
      *
-     * 5       // true
-     * 5.      // false
-     * 5.0     // false
-     * 05      // false
-     * 0x5     // false
-     * 0b101   // false
-     * 0o5     // false
-     * 5e0     // false
-     * '5'     // false
+     * 0         // true
+     * 5         // true
+     * 50        // true
+     * 5_000     // true
+     * 1_234_56  // true
+     * 08        // true
+     * 0192      // true
+     * 5.        // false
+     * .5        // false
+     * 5.0       // false
+     * 5.00_00   // false
+     * 05        // false
+     * 0x5       // false
+     * 0b101     // false
+     * 0b11_01   // false
+     * 0o5       // false
+     * 5e0       // false
+     * 5e1_000   // false
+     * 5n        // false
+     * 1_000n    // false
+     * '5'       // false
      */
     isDecimalInteger(node) {
         return node.type === "Literal" && typeof node.value === "number" &&
@@ -1085,7 +1360,7 @@ module.exports = {
         if (node.id) {
             tokens.push(`'${node.id.name}'`);
         } else {
-            const name = module.exports.getStaticPropertyName(parent);
+            const name = getStaticPropertyName(parent);
 
             if (name !== null) {
                 tokens.push(`'${name}'`);
@@ -1212,19 +1487,64 @@ module.exports = {
 
     /**
      * Gets next location when the result is not out of bound, otherwise returns null.
+     *
+     * Assumptions:
+     *
+     * - The given location represents a valid location in the given source code.
+     * - Columns are 0-based.
+     * - Lines are 1-based.
+     * - Column immediately after the last character in a line (not incl. linebreaks) is considered to be a valid location.
+     * - If the source code ends with a linebreak, `sourceCode.lines` array will have an extra element (empty string) at the end.
+     *   The start (column 0) of that extra line is considered to be a valid location.
+     *
+     * Examples of successive locations (line, column):
+     *
+     * code: foo
+     * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> null
+     *
+     * code: foo<LF>
+     * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
+     *
+     * code: foo<CR><LF>
+     * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
+     *
+     * code: a<LF>b
+     * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> null
+     *
+     * code: a<LF>b<LF>
+     * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
+     *
+     * code: a<CR><LF>b<CR><LF>
+     * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
+     *
+     * code: a<LF><LF>
+     * locations: (1, 0) -> (1, 1) -> (2, 0) -> (3, 0) -> null
+     *
+     * code: <LF>
+     * locations: (1, 0) -> (2, 0) -> null
+     *
+     * code:
+     * locations: (1, 0) -> null
      * @param {SourceCode} sourceCode The sourceCode
      * @param {{line: number, column: number}} location The location
      * @returns {{line: number, column: number} | null} Next location
      */
-    getNextLocation(sourceCode, location) {
-        const index = sourceCode.getIndexFromLoc(location);
+    getNextLocation(sourceCode, { line, column }) {
+        if (column < sourceCode.lines[line - 1].length) {
+            return {
+                line,
+                column: column + 1
+            };
+        }
 
-        // Avoid out of bound location
-        if (index + 1 > sourceCode.text.length) {
-            return null;
+        if (line < sourceCode.lines.length) {
+            return {
+                line: line + 1,
+                column: 0
+            };
         }
 
-        return sourceCode.getLocFromIndex(index + 1);
+        return null;
     },
 
     /**
@@ -1254,7 +1574,7 @@ module.exports = {
     },
 
     /*
-     * Determine if a node has a possiblity to be an Error object
+     * Determine if a node has a possibility to be an Error object
      * @param  {ASTNode} node  ASTNode to check
      * @returns {boolean} True if there is a chance it contains an Error obj
      */
@@ -1267,10 +1587,24 @@ module.exports = {
             case "TaggedTemplateExpression":
             case "YieldExpression":
             case "AwaitExpression":
+            case "ChainExpression":
                 return true; // possibly an error object.
 
             case "AssignmentExpression":
-                return module.exports.couldBeError(node.right);
+                if (["=", "&&="].includes(node.operator)) {
+                    return module.exports.couldBeError(node.right);
+                }
+
+                if (["||=", "??="].includes(node.operator)) {
+                    return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
+                }
+
+                /**
+                 * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
+                 * An assignment expression with a mathematical operator can either evaluate to a primitive value,
+                 * or throw, depending on the operands. Thus, it cannot evaluate to an `Error` object.
+                 */
+                return false;
 
             case "SequenceExpression": {
                 const exprs = node.expressions;
@@ -1279,6 +1613,17 @@ module.exports = {
             }
 
             case "LogicalExpression":
+
+                /*
+                 * If the && operator short-circuits, the left side was falsy and therefore not an error, and if it
+                 * doesn't short-circuit, it takes the value from the right side, so the right side must always be
+                 * a plausible error. A future improvement could verify that the left side could be truthy by
+                 * excluding falsy literals.
+                 */
+                if (node.operator === "&&") {
+                    return module.exports.couldBeError(node.right);
+                }
+
                 return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
 
             case "ConditionalExpression":
@@ -1290,20 +1635,15 @@ module.exports = {
     },
 
     /**
-     * Determines whether the given node is a `null` literal.
-     * @param {ASTNode} node The node to check
-     * @returns {boolean} `true` if the node is a `null` literal
+     * Check if a given node is a numeric literal or not.
+     * @param {ASTNode} node The node to check.
+     * @returns {boolean} `true` if the node is a number or bigint literal.
      */
-    isNullLiteral(node) {
-
-        /*
-         * Checking `node.value === null` does not guarantee that a literal is a null literal.
-         * When parsing values that cannot be represented in the current environment (e.g. unicode
-         * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
-         * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
-         * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
-         */
-        return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
+    isNumericLiteral(node) {
+        return (
+            node.type === "Literal" &&
+            (typeof node.value === "number" || Boolean(node.bigint))
+        );
     },
 
     /**
@@ -1314,17 +1654,65 @@ module.exports = {
      * next to each other, behavior is undefined (although it should return `true` in most cases).
      */
     canTokensBeAdjacent(leftValue, rightValue) {
+        const espreeOptions = {
+            ecmaVersion: espree.latestEcmaVersion,
+            comment: true,
+            range: true
+        };
+
         let leftToken;
 
         if (typeof leftValue === "string") {
-            const leftTokens = espree.tokenize(leftValue, { ecmaVersion: 2015 });
+            let tokens;
 
-            leftToken = leftTokens[leftTokens.length - 1];
+            try {
+                tokens = espree.tokenize(leftValue, espreeOptions);
+            } catch {
+                return false;
+            }
+
+            const comments = tokens.comments;
+
+            leftToken = tokens[tokens.length - 1];
+            if (comments.length) {
+                const lastComment = comments[comments.length - 1];
+
+                if (lastComment.range[0] > leftToken.range[0]) {
+                    leftToken = lastComment;
+                }
+            }
         } else {
             leftToken = leftValue;
         }
 
-        const rightToken = typeof rightValue === "string" ? espree.tokenize(rightValue, { ecmaVersion: 2015 })[0] : rightValue;
+        if (leftToken.type === "Shebang") {
+            return false;
+        }
+
+        let rightToken;
+
+        if (typeof rightValue === "string") {
+            let tokens;
+
+            try {
+                tokens = espree.tokenize(rightValue, espreeOptions);
+            } catch {
+                return false;
+            }
+
+            const comments = tokens.comments;
+
+            rightToken = tokens[0];
+            if (comments.length) {
+                const firstComment = comments[0];
+
+                if (firstComment.range[0] < rightToken.range[0]) {
+                    rightToken = firstComment;
+                }
+            }
+        } else {
+            rightToken = rightValue;
+        }
 
         if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") {
             if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") {
@@ -1336,6 +1724,9 @@ module.exports = {
                     MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value)
                 );
             }
+            if (leftToken.type === "Punctuator" && leftToken.value === "/") {
+                return !["Block", "Line", "RegularExpression"].includes(rightToken.type);
+            }
             return true;
         }
 
@@ -1350,6 +1741,10 @@ module.exports = {
             return true;
         }
 
+        if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") {
+            return true;
+        }
+
         return false;
     },
 
@@ -1361,7 +1756,7 @@ module.exports = {
      * @returns {SourceLocation} The `loc` object.
      */
     getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) {
-        const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
+        const namePattern = new RegExp(`[\\s,]${escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
 
         // To ignore the first text "global".
         namePattern.lastIndex = comment.value.indexOf("global") + 6;
@@ -1370,24 +1765,45 @@ module.exports = {
         const match = namePattern.exec(comment.value);
 
         // Convert the index to loc.
-        return sourceCode.getLocFromIndex(
+        const start = sourceCode.getLocFromIndex(
             comment.range[0] +
             "/*".length +
             (match ? match.index + 1 : 0)
         );
+        const end = {
+            line: start.line,
+            column: start.column + (match ? name.length : 1)
+        };
+
+        return { start, end };
     },
 
     /**
-     * Determines whether the given raw string contains an octal escape sequence.
+     * Determines whether the given raw string contains an octal escape sequence
+     * or a non-octal decimal escape sequence ("\8", "\9").
      *
-     * "\1", "\2" ... "\7"
-     * "\00", "\01" ... "\09"
+     * "\1", "\2" ... "\7", "\8", "\9"
+     * "\00", "\01" ... "\07", "\08", "\09"
      *
      * "\0", when not followed by a digit, is not an octal escape sequence.
      * @param {string} rawString A string in its raw representation.
-     * @returns {boolean} `true` if the string contains at least one octal escape sequence.
+     * @returns {boolean} `true` if the string contains at least one octal escape sequence
+     * or at least one non-octal decimal escape sequence.
      */
-    hasOctalEscapeSequence(rawString) {
-        return OCTAL_ESCAPE_PATTERN.test(rawString);
-    }
+    hasOctalOrNonOctalDecimalEscapeSequence(rawString) {
+        return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
+    },
+
+    isLogicalExpression,
+    isCoalesceExpression,
+    isMixedLogicalAndCoalesceExpressions,
+    isNullLiteral,
+    getStaticStringValue,
+    getStaticPropertyName,
+    skipChainExpression,
+    isSpecificId,
+    isSpecificMemberAccess,
+    equalLiteralValue,
+    isSameReference,
+    isLogicalAssignmentOperator
 };