Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'tools/node_modules/eslint/lib/rules/utils/ast-utils.js')
-rw-r--r--tools/node_modules/eslint/lib/rules/utils/ast-utils.js210
1 files changed, 203 insertions, 7 deletions
diff --git a/tools/node_modules/eslint/lib/rules/utils/ast-utils.js b/tools/node_modules/eslint/lib/rules/utils/ast-utils.js
index ecde099fa02..f919f5a26c8 100644
--- a/tools/node_modules/eslint/lib/rules/utils/ast-utils.js
+++ b/tools/node_modules/eslint/lib/rules/utils/ast-utils.js
@@ -32,6 +32,7 @@ const thisTagPattern = /^[\s*]*@this/mu;
const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u;
+const ESLINT_DIRECTIVE_PATTERN = /^(?:eslint[- ]|(?:globals?|exported) )/u;
const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
// A set of node types that can contain a list of statements
@@ -788,6 +789,203 @@ function getModuleExportName(node) {
return node.value;
}
+/**
+ * Returns literal's value converted to the Boolean type
+ * @param {ASTNode} node any `Literal` node
+ * @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
+ * `null` when it cannot be determined.
+ */
+function getBooleanValue(node) {
+ if (node.value === null) {
+
+ /*
+ * it might be a null literal or bigint/regex literal in unsupported environments .
+ * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
+ * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
+ */
+
+ if (node.raw === "null") {
+ return false;
+ }
+
+ // regex is always truthy
+ if (typeof node.regex === "object") {
+ return true;
+ }
+
+ return null;
+ }
+
+ return !!node.value;
+}
+
+/**
+ * Checks if a branch node of LogicalExpression short circuits the whole condition
+ * @param {ASTNode} node The branch of main condition which needs to be checked
+ * @param {string} operator The operator of the main LogicalExpression.
+ * @returns {boolean} true when condition short circuits whole condition
+ */
+function isLogicalIdentity(node, operator) {
+ switch (node.type) {
+ case "Literal":
+ return (operator === "||" && getBooleanValue(node) === true) ||
+ (operator === "&&" && getBooleanValue(node) === false);
+
+ case "UnaryExpression":
+ return (operator === "&&" && node.operator === "void");
+
+ case "LogicalExpression":
+
+ /*
+ * handles `a && false || b`
+ * `false` is an identity element of `&&` but not `||`
+ */
+ return operator === node.operator &&
+ (
+ isLogicalIdentity(node.left, operator) ||
+ isLogicalIdentity(node.right, operator)
+ );
+
+ case "AssignmentExpression":
+ return ["||=", "&&="].includes(node.operator) &&
+ operator === node.operator.slice(0, -1) &&
+ isLogicalIdentity(node.right, operator);
+
+ // no default
+ }
+ return false;
+}
+
+/**
+ * Checks if an identifier is a reference to a global variable.
+ * @param {Scope} scope The scope in which the identifier is referenced.
+ * @param {ASTNode} node An identifier node to check.
+ * @returns {boolean} `true` if the identifier is a reference to a global variable.
+ */
+function isReferenceToGlobalVariable(scope, node) {
+ const reference = scope.references.find(ref => ref.identifier === node);
+
+ return Boolean(
+ reference &&
+ reference.resolved &&
+ reference.resolved.scope.type === "global" &&
+ reference.resolved.defs.length === 0
+ );
+}
+
+
+/**
+ * Checks if a node has a constant truthiness value.
+ * @param {Scope} scope Scope in which the node appears.
+ * @param {ASTNode} node The AST node to check.
+ * @param {boolean} inBooleanPosition `true` if checking the test of a
+ * condition. `false` in all other cases. When `false`, checks if -- for
+ * both string and number -- if coerced to that type, the value will
+ * be constant.
+ * @returns {boolean} true when node's truthiness is constant
+ * @private
+ */
+function isConstant(scope, node, inBooleanPosition) {
+
+ // node.elements can return null values in the case of sparse arrays ex. [,]
+ if (!node) {
+ return true;
+ }
+ switch (node.type) {
+ case "Literal":
+ case "ArrowFunctionExpression":
+ case "FunctionExpression":
+ return true;
+ case "ClassExpression":
+ case "ObjectExpression":
+
+ /**
+ * In theory objects like:
+ *
+ * `{toString: () => a}`
+ * `{valueOf: () => a}`
+ *
+ * Or a classes like:
+ *
+ * `class { static toString() { return a } }`
+ * `class { static valueOf() { return a } }`
+ *
+ * Are not constant verifiably when `inBooleanPosition` is
+ * false, but it's an edge case we've opted not to handle.
+ */
+ return true;
+ case "TemplateLiteral":
+ return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
+ node.expressions.every(exp => isConstant(scope, exp, false));
+
+ case "ArrayExpression": {
+ if (!inBooleanPosition) {
+ return node.elements.every(element => isConstant(scope, element, false));
+ }
+ return true;
+ }
+
+ case "UnaryExpression":
+ if (
+ node.operator === "void" ||
+ node.operator === "typeof" && inBooleanPosition
+ ) {
+ return true;
+ }
+
+ if (node.operator === "!") {
+ return isConstant(scope, node.argument, true);
+ }
+
+ return isConstant(scope, node.argument, false);
+
+ case "BinaryExpression":
+ return isConstant(scope, node.left, false) &&
+ isConstant(scope, node.right, false) &&
+ node.operator !== "in";
+
+ case "LogicalExpression": {
+ const isLeftConstant = isConstant(scope, node.left, inBooleanPosition);
+ const isRightConstant = isConstant(scope, node.right, inBooleanPosition);
+ const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
+ const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
+
+ return (isLeftConstant && isRightConstant) ||
+ isLeftShortCircuit ||
+ isRightShortCircuit;
+ }
+ case "NewExpression":
+ return inBooleanPosition;
+ case "AssignmentExpression":
+ if (node.operator === "=") {
+ return isConstant(scope, node.right, inBooleanPosition);
+ }
+
+ if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
+ return isLogicalIdentity(node.right, node.operator.slice(0, -1));
+ }
+
+ return false;
+
+ case "SequenceExpression":
+ return isConstant(scope, node.expressions[node.expressions.length - 1], inBooleanPosition);
+ case "SpreadElement":
+ return isConstant(scope, node.argument, inBooleanPosition);
+ case "CallExpression":
+ if (node.callee.type === "Identifier" && node.callee.name === "Boolean") {
+ if (node.arguments.length === 0 || isConstant(scope, node.arguments[0], true)) {
+ return isReferenceToGlobalVariable(scope, node.callee);
+ }
+ }
+ return false;
+ case "Identifier":
+ return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
+
+ // no default
+ }
+ return false;
+}
+
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@@ -908,12 +1106,8 @@ module.exports = {
const comment = node.value.trim();
return (
- node.type === "Line" && comment.indexOf("eslint-") === 0 ||
- node.type === "Block" && (
- comment.indexOf("global ") === 0 ||
- comment.indexOf("eslint ") === 0 ||
- comment.indexOf("eslint-") === 0
- )
+ node.type === "Line" && comment.startsWith("eslint-") ||
+ node.type === "Block" && ESLINT_DIRECTIVE_PATTERN.test(comment)
);
},
@@ -1905,6 +2099,7 @@ module.exports = {
return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
},
+ isReferenceToGlobalVariable,
isLogicalExpression,
isCoalesceExpression,
isMixedLogicalAndCoalesceExpressions,
@@ -1918,5 +2113,6 @@ module.exports = {
isSameReference,
isLogicalAssignmentOperator,
getSwitchCaseColonToken,
- getModuleExportName
+ getModuleExportName,
+ isConstant
};