UNPKG

7.58 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag use constant conditions
3 * @author Christian Schulz <http://rndm.de>
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13 meta: {
14 docs: {
15 description: "disallow constant expressions in conditions",
16 category: "Possible Errors",
17 recommended: true,
18 url: "https://eslint.org/docs/rules/no-constant-condition"
19 },
20
21 schema: [
22 {
23 type: "object",
24 properties: {
25 checkLoops: {
26 type: "boolean"
27 }
28 },
29 additionalProperties: false
30 }
31 ],
32
33 messages: {
34 unexpected: "Unexpected constant condition."
35 }
36 },
37
38 create(context) {
39 const options = context.options[0] || {},
40 checkLoops = options.checkLoops !== false,
41 loopSetStack = [];
42
43 let loopsInCurrentScope = new Set();
44
45 //--------------------------------------------------------------------------
46 // Helpers
47 //--------------------------------------------------------------------------
48
49
50 /**
51 * Checks if a branch node of LogicalExpression short circuits the whole condition
52 * @param {ASTNode} node The branch of main condition which needs to be checked
53 * @param {string} operator The operator of the main LogicalExpression.
54 * @returns {boolean} true when condition short circuits whole condition
55 */
56 function isLogicalIdentity(node, operator) {
57 switch (node.type) {
58 case "Literal":
59 return (operator === "||" && node.value === true) ||
60 (operator === "&&" && node.value === false);
61
62 case "UnaryExpression":
63 return (operator === "&&" && node.operator === "void");
64
65 case "LogicalExpression":
66 return isLogicalIdentity(node.left, node.operator) ||
67 isLogicalIdentity(node.right, node.operator);
68
69 // no default
70 }
71 return false;
72 }
73
74 /**
75 * Checks if a node has a constant truthiness value.
76 * @param {ASTNode} node The AST node to check.
77 * @param {boolean} inBooleanPosition `false` if checking branch of a condition.
78 * `true` in all other cases
79 * @returns {Bool} true when node's truthiness is constant
80 * @private
81 */
82 function isConstant(node, inBooleanPosition) {
83 switch (node.type) {
84 case "Literal":
85 case "ArrowFunctionExpression":
86 case "FunctionExpression":
87 case "ObjectExpression":
88 case "ArrayExpression":
89 return true;
90
91 case "UnaryExpression":
92 if (node.operator === "void") {
93 return true;
94 }
95
96 return (node.operator === "typeof" && inBooleanPosition) ||
97 isConstant(node.argument, true);
98
99 case "BinaryExpression":
100 return isConstant(node.left, false) &&
101 isConstant(node.right, false) &&
102 node.operator !== "in";
103
104 case "LogicalExpression": {
105 const isLeftConstant = isConstant(node.left, inBooleanPosition);
106 const isRightConstant = isConstant(node.right, inBooleanPosition);
107 const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
108 const isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator));
109
110 return (isLeftConstant && isRightConstant) || isLeftShortCircuit || isRightShortCircuit;
111 }
112
113 case "AssignmentExpression":
114 return (node.operator === "=") && isConstant(node.right, inBooleanPosition);
115
116 case "SequenceExpression":
117 return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
118
119 // no default
120 }
121 return false;
122 }
123
124 /**
125 * Tracks when the given node contains a constant condition.
126 * @param {ASTNode} node The AST node to check.
127 * @returns {void}
128 * @private
129 */
130 function trackConstantConditionLoop(node) {
131 if (node.test && isConstant(node.test, true)) {
132 loopsInCurrentScope.add(node);
133 }
134 }
135
136 /**
137 * Reports when the set contains the given constant condition node
138 * @param {ASTNode} node The AST node to check.
139 * @returns {void}
140 * @private
141 */
142 function checkConstantConditionLoopInSet(node) {
143 if (loopsInCurrentScope.has(node)) {
144 loopsInCurrentScope.delete(node);
145 context.report({ node: node.test, messageId: "unexpected" });
146 }
147 }
148
149 /**
150 * Reports when the given node contains a constant condition.
151 * @param {ASTNode} node The AST node to check.
152 * @returns {void}
153 * @private
154 */
155 function reportIfConstant(node) {
156 if (node.test && isConstant(node.test, true)) {
157 context.report({ node: node.test, messageId: "unexpected" });
158 }
159 }
160
161 /**
162 * Stores current set of constant loops in loopSetStack temporarily
163 * and uses a new set to track constant loops
164 * @returns {void}
165 * @private
166 */
167 function enterFunction() {
168 loopSetStack.push(loopsInCurrentScope);
169 loopsInCurrentScope = new Set();
170 }
171
172 /**
173 * Reports when the set still contains stored constant conditions
174 * @param {ASTNode} node The AST node to check.
175 * @returns {void}
176 * @private
177 */
178 function exitFunction() {
179 loopsInCurrentScope = loopSetStack.pop();
180 }
181
182 /**
183 * Checks node when checkLoops option is enabled
184 * @param {ASTNode} node The AST node to check.
185 * @returns {void}
186 * @private
187 */
188 function checkLoop(node) {
189 if (checkLoops) {
190 trackConstantConditionLoop(node);
191 }
192 }
193
194 //--------------------------------------------------------------------------
195 // Public
196 //--------------------------------------------------------------------------
197
198 return {
199 ConditionalExpression: reportIfConstant,
200 IfStatement: reportIfConstant,
201 WhileStatement: checkLoop,
202 "WhileStatement:exit": checkConstantConditionLoopInSet,
203 DoWhileStatement: checkLoop,
204 "DoWhileStatement:exit": checkConstantConditionLoopInSet,
205 ForStatement: checkLoop,
206 "ForStatement > .test": node => checkLoop(node.parent),
207 "ForStatement:exit": checkConstantConditionLoopInSet,
208 FunctionDeclaration: enterFunction,
209 "FunctionDeclaration:exit": exitFunction,
210 YieldExpression: () => loopsInCurrentScope.clear()
211 };
212
213 }
214};