Permalink
Cannot retrieve contributors at this time
| /** | |
| * @fileoverview Rule to flag use constant conditions | |
| * @author Christian Schulz <http://rndm.de> | |
| */ | |
| "use strict"; | |
| //------------------------------------------------------------------------------ | |
| // Helpers | |
| //------------------------------------------------------------------------------ | |
| const EQUALITY_OPERATORS = ["===", "!==", "==", "!="]; | |
| const RELATIONAL_OPERATORS = [">", "<", ">=", "<=", "in", "instanceof"]; | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
| module.exports = { | |
| meta: { | |
| type: "problem", | |
| docs: { | |
| description: "disallow constant expressions in conditions", | |
| category: "Possible Errors", | |
| recommended: true, | |
| url: "https://eslint.org/docs/rules/no-constant-condition" | |
| }, | |
| schema: [ | |
| { | |
| type: "object", | |
| properties: { | |
| checkLoops: { | |
| type: "boolean", | |
| default: true | |
| } | |
| }, | |
| additionalProperties: false | |
| } | |
| ], | |
| messages: { | |
| unexpected: "Unexpected constant condition." | |
| } | |
| }, | |
| create(context) { | |
| const options = context.options[0] || {}, | |
| checkLoops = options.checkLoops !== false, | |
| loopSetStack = []; | |
| let loopsInCurrentScope = new Set(); | |
| //-------------------------------------------------------------------------- | |
| // Helpers | |
| //-------------------------------------------------------------------------- | |
| /** | |
| * 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 === "||" && node.value === true) || | |
| (operator === "&&" && node.value === false); | |
| case "UnaryExpression": | |
| return (operator === "&&" && node.operator === "void"); | |
| case "LogicalExpression": | |
| return isLogicalIdentity(node.left, node.operator) || | |
| isLogicalIdentity(node.right, node.operator); | |
| // no default | |
| } | |
| return false; | |
| } | |
| /** | |
| * Checks if a node has a constant truthiness value. | |
| * @param {ASTNode} node The AST node to check. | |
| * @param {boolean} inBooleanPosition `false` if checking branch of a condition. | |
| * `true` in all other cases | |
| * @returns {Bool} true when node's truthiness is constant | |
| * @private | |
| */ | |
| function isConstant(node, inBooleanPosition) { | |
| switch (node.type) { | |
| case "Literal": | |
| case "ArrowFunctionExpression": | |
| case "FunctionExpression": | |
| case "ObjectExpression": | |
| case "ArrayExpression": | |
| return true; | |
| case "UnaryExpression": | |
| if (node.operator === "void") { | |
| return true; | |
| } | |
| return (node.operator === "typeof" && inBooleanPosition) || | |
| isConstant(node.argument, true); | |
| case "BinaryExpression": | |
| return isConstant(node.left, false) && | |
| isConstant(node.right, false) && | |
| node.operator !== "in"; | |
| case "LogicalExpression": { | |
| const isLeftConstant = isConstant(node.left, inBooleanPosition); | |
| const isRightConstant = isConstant(node.right, inBooleanPosition); | |
| const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator)); | |
| const isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator)); | |
| return (isLeftConstant && isRightConstant) || | |
| ( | |
| // in the case of an "OR", we need to know if the right constant value is truthy | |
| node.operator === "||" && | |
| isRightConstant && | |
| node.right.value && | |
| ( | |
| !node.parent || | |
| node.parent.type !== "BinaryExpression" || | |
| !(EQUALITY_OPERATORS.includes(node.parent.operator) || RELATIONAL_OPERATORS.includes(node.parent.operator)) | |
| ) | |
| ) || | |
| isLeftShortCircuit || | |
| isRightShortCircuit; | |
| } | |
| case "AssignmentExpression": | |
| return (node.operator === "=") && isConstant(node.right, inBooleanPosition); | |
| case "SequenceExpression": | |
| return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition); | |
| // no default | |
| } | |
| return false; | |
| } | |
| /** | |
| * Tracks when the given node contains a constant condition. | |
| * @param {ASTNode} node The AST node to check. | |
| * @returns {void} | |
| * @private | |
| */ | |
| function trackConstantConditionLoop(node) { | |
| if (node.test && isConstant(node.test, true)) { | |
| loopsInCurrentScope.add(node); | |
| } | |
| } | |
| /** | |
| * Reports when the set contains the given constant condition node | |
| * @param {ASTNode} node The AST node to check. | |
| * @returns {void} | |
| * @private | |
| */ | |
| function checkConstantConditionLoopInSet(node) { | |
| if (loopsInCurrentScope.has(node)) { | |
| loopsInCurrentScope.delete(node); | |
| context.report({ node: node.test, messageId: "unexpected" }); | |
| } | |
| } | |
| /** | |
| * Reports when the given node contains a constant condition. | |
| * @param {ASTNode} node The AST node to check. | |
| * @returns {void} | |
| * @private | |
| */ | |
| function reportIfConstant(node) { | |
| if (node.test && isConstant(node.test, true)) { | |
| context.report({ node: node.test, messageId: "unexpected" }); | |
| } | |
| } | |
| /** | |
| * Stores current set of constant loops in loopSetStack temporarily | |
| * and uses a new set to track constant loops | |
| * @returns {void} | |
| * @private | |
| */ | |
| function enterFunction() { | |
| loopSetStack.push(loopsInCurrentScope); | |
| loopsInCurrentScope = new Set(); | |
| } | |
| /** | |
| * Reports when the set still contains stored constant conditions | |
| * @returns {void} | |
| * @private | |
| */ | |
| function exitFunction() { | |
| loopsInCurrentScope = loopSetStack.pop(); | |
| } | |
| /** | |
| * Checks node when checkLoops option is enabled | |
| * @param {ASTNode} node The AST node to check. | |
| * @returns {void} | |
| * @private | |
| */ | |
| function checkLoop(node) { | |
| if (checkLoops) { | |
| trackConstantConditionLoop(node); | |
| } | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Public | |
| //-------------------------------------------------------------------------- | |
| return { | |
| ConditionalExpression: reportIfConstant, | |
| IfStatement: reportIfConstant, | |
| WhileStatement: checkLoop, | |
| "WhileStatement:exit": checkConstantConditionLoopInSet, | |
| DoWhileStatement: checkLoop, | |
| "DoWhileStatement:exit": checkConstantConditionLoopInSet, | |
| ForStatement: checkLoop, | |
| "ForStatement > .test": node => checkLoop(node.parent), | |
| "ForStatement:exit": checkConstantConditionLoopInSet, | |
| FunctionDeclaration: enterFunction, | |
| "FunctionDeclaration:exit": exitFunction, | |
| FunctionExpression: enterFunction, | |
| "FunctionExpression:exit": exitFunction, | |
| YieldExpression: () => loopsInCurrentScope.clear() | |
| }; | |
| } | |
| }; |