const esutils = require("esutils");
const espree = require("espree");
-const lodash = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
const {
breakableTypePattern,
createGlobalLinebreakMatcher,
// 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.
/**
* 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));
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.
*/
function isNullOrUndefined(node) {
return (
- module.exports.isNullLiteral(node) ||
+ isNullLiteral(node) ||
(node.type === "Identifier" && node.name === "undefined") ||
(node.type === "UnaryExpression" && node.operator === "void")
);
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");
}
/**
* @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");
}
/**
* @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);
}
/**
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.
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
//------------------------------------------------------------------------------
isCommaToken,
isCommentToken,
isDotToken,
+ isQuestionDotToken,
isKeywordToken,
isNotClosingBraceToken: negate(isClosingBraceToken),
isNotClosingBracketToken: negate(isClosingBracketToken),
isNotColonToken: negate(isColonToken),
isNotCommaToken: negate(isCommaToken),
isNotDotToken: negate(isDotToken),
+ isNotQuestionDotToken: negate(isQuestionDotToken),
isNotOpeningBraceToken: negate(isOpeningBraceToken),
isNotOpeningBracketToken: negate(isOpeningBracketToken),
isNotOpeningParenToken: negate(isOpeningParenToken),
*/
case "LogicalExpression":
case "ConditionalExpression":
+ case "ChainExpression":
currentNode = parent;
break;
* (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.
case "LogicalExpression":
switch (node.operator) {
case "||":
+ case "??":
return 4;
case "&&":
return 5;
return 17;
case "CallExpression":
+ case "ChainExpression":
case "ImportExpression":
return 18;
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.
* @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" &&
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}'`);
/**
* 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;
},
/**
},
/*
- * 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
*/
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;
}
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":
},
/**
- * 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))
+ );
},
/**
* 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") {
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;
}
return true;
}
+ if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") {
+ return true;
+ }
+
return false;
},
* @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;
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
};