.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-constant-condition.js
1 /**
2  * @fileoverview Rule to flag use constant conditions
3  * @author Christian Schulz <http://rndm.de>
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Helpers
10 //------------------------------------------------------------------------------
11
12 //------------------------------------------------------------------------------
13 // Rule Definition
14 //------------------------------------------------------------------------------
15
16 module.exports = {
17     meta: {
18         type: "problem",
19
20         docs: {
21             description: "disallow constant expressions in conditions",
22             category: "Possible Errors",
23             recommended: true,
24             url: "https://eslint.org/docs/rules/no-constant-condition"
25         },
26
27         schema: [
28             {
29                 type: "object",
30                 properties: {
31                     checkLoops: {
32                         type: "boolean",
33                         default: true
34                     }
35                 },
36                 additionalProperties: false
37             }
38         ],
39
40         messages: {
41             unexpected: "Unexpected constant condition."
42         }
43     },
44
45     create(context) {
46         const options = context.options[0] || {},
47             checkLoops = options.checkLoops !== false,
48             loopSetStack = [];
49
50         let loopsInCurrentScope = new Set();
51
52         //--------------------------------------------------------------------------
53         // Helpers
54         //--------------------------------------------------------------------------
55
56         /**
57          * Returns literal's value converted to the Boolean type
58          * @param {ASTNode} node any `Literal` node
59          * @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
60          *  `null` when it cannot be determined.
61          */
62         function getBooleanValue(node) {
63             if (node.value === null) {
64
65                 /*
66                  * it might be a null literal or bigint/regex literal in unsupported environments .
67                  * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
68                  * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
69                  */
70
71                 if (node.raw === "null") {
72                     return false;
73                 }
74
75                 // regex is always truthy
76                 if (typeof node.regex === "object") {
77                     return true;
78                 }
79
80                 return null;
81             }
82
83             return !!node.value;
84         }
85
86         /**
87          * Checks if a branch node of LogicalExpression short circuits the whole condition
88          * @param {ASTNode} node The branch of main condition which needs to be checked
89          * @param {string} operator The operator of the main LogicalExpression.
90          * @returns {boolean} true when condition short circuits whole condition
91          */
92         function isLogicalIdentity(node, operator) {
93             switch (node.type) {
94                 case "Literal":
95                     return (operator === "||" && getBooleanValue(node) === true) ||
96                            (operator === "&&" && getBooleanValue(node) === false);
97
98                 case "UnaryExpression":
99                     return (operator === "&&" && node.operator === "void");
100
101                 case "LogicalExpression":
102
103                     /*
104                      * handles `a && false || b`
105                      * `false` is an identity element of `&&` but not `||`
106                      */
107                     return operator === node.operator &&
108                              (
109                                  isLogicalIdentity(node.left, operator) ||
110                                  isLogicalIdentity(node.right, operator)
111                              );
112
113                 case "AssignmentExpression":
114                     return ["||=", "&&="].includes(node.operator) &&
115                         operator === node.operator.slice(0, -1) &&
116                         isLogicalIdentity(node.right, operator);
117
118                 // no default
119             }
120             return false;
121         }
122
123         /**
124          * Checks if a node has a constant truthiness value.
125          * @param {ASTNode} node The AST node to check.
126          * @param {boolean} inBooleanPosition `false` if checking branch of a condition.
127          *  `true` in all other cases
128          * @returns {Bool} true when node's truthiness is constant
129          * @private
130          */
131         function isConstant(node, inBooleanPosition) {
132
133             // node.elements can return null values in the case of sparse arrays ex. [,]
134             if (!node) {
135                 return true;
136             }
137             switch (node.type) {
138                 case "Literal":
139                 case "ArrowFunctionExpression":
140                 case "FunctionExpression":
141                 case "ObjectExpression":
142                     return true;
143                 case "TemplateLiteral":
144                     return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
145                         node.expressions.every(exp => isConstant(exp, inBooleanPosition));
146
147                 case "ArrayExpression": {
148                     if (node.parent.type === "BinaryExpression" && node.parent.operator === "+") {
149                         return node.elements.every(element => isConstant(element, false));
150                     }
151                     return true;
152                 }
153
154                 case "UnaryExpression":
155                     if (
156                         node.operator === "void" ||
157                         node.operator === "typeof" && inBooleanPosition
158                     ) {
159                         return true;
160                     }
161
162                     if (node.operator === "!") {
163                         return isConstant(node.argument, true);
164                     }
165
166                     return isConstant(node.argument, false);
167
168                 case "BinaryExpression":
169                     return isConstant(node.left, false) &&
170                             isConstant(node.right, false) &&
171                             node.operator !== "in";
172
173                 case "LogicalExpression": {
174                     const isLeftConstant = isConstant(node.left, inBooleanPosition);
175                     const isRightConstant = isConstant(node.right, inBooleanPosition);
176                     const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
177                     const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
178
179                     return (isLeftConstant && isRightConstant) ||
180                         isLeftShortCircuit ||
181                         isRightShortCircuit;
182                 }
183
184                 case "AssignmentExpression":
185                     if (node.operator === "=") {
186                         return isConstant(node.right, inBooleanPosition);
187                     }
188
189                     if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
190                         return isLogicalIdentity(node.right, node.operator.slice(0, -1));
191                     }
192
193                     return false;
194
195                 case "SequenceExpression":
196                     return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
197
198                 // no default
199             }
200             return false;
201         }
202
203         /**
204          * Tracks when the given node contains a constant condition.
205          * @param {ASTNode} node The AST node to check.
206          * @returns {void}
207          * @private
208          */
209         function trackConstantConditionLoop(node) {
210             if (node.test && isConstant(node.test, true)) {
211                 loopsInCurrentScope.add(node);
212             }
213         }
214
215         /**
216          * Reports when the set contains the given constant condition node
217          * @param {ASTNode} node The AST node to check.
218          * @returns {void}
219          * @private
220          */
221         function checkConstantConditionLoopInSet(node) {
222             if (loopsInCurrentScope.has(node)) {
223                 loopsInCurrentScope.delete(node);
224                 context.report({ node: node.test, messageId: "unexpected" });
225             }
226         }
227
228         /**
229          * Reports when the given node contains a constant condition.
230          * @param {ASTNode} node The AST node to check.
231          * @returns {void}
232          * @private
233          */
234         function reportIfConstant(node) {
235             if (node.test && isConstant(node.test, true)) {
236                 context.report({ node: node.test, messageId: "unexpected" });
237             }
238         }
239
240         /**
241          * Stores current set of constant loops in loopSetStack temporarily
242          * and uses a new set to track constant loops
243          * @returns {void}
244          * @private
245          */
246         function enterFunction() {
247             loopSetStack.push(loopsInCurrentScope);
248             loopsInCurrentScope = new Set();
249         }
250
251         /**
252          * Reports when the set still contains stored constant conditions
253          * @returns {void}
254          * @private
255          */
256         function exitFunction() {
257             loopsInCurrentScope = loopSetStack.pop();
258         }
259
260         /**
261          * Checks node when checkLoops option is enabled
262          * @param {ASTNode} node The AST node to check.
263          * @returns {void}
264          * @private
265          */
266         function checkLoop(node) {
267             if (checkLoops) {
268                 trackConstantConditionLoop(node);
269             }
270         }
271
272         //--------------------------------------------------------------------------
273         // Public
274         //--------------------------------------------------------------------------
275
276         return {
277             ConditionalExpression: reportIfConstant,
278             IfStatement: reportIfConstant,
279             WhileStatement: checkLoop,
280             "WhileStatement:exit": checkConstantConditionLoopInSet,
281             DoWhileStatement: checkLoop,
282             "DoWhileStatement:exit": checkConstantConditionLoopInSet,
283             ForStatement: checkLoop,
284             "ForStatement > .test": node => checkLoop(node.parent),
285             "ForStatement:exit": checkConstantConditionLoopInSet,
286             FunctionDeclaration: enterFunction,
287             "FunctionDeclaration:exit": exitFunction,
288             FunctionExpression: enterFunction,
289             "FunctionExpression:exit": exitFunction,
290             YieldExpression: () => loopsInCurrentScope.clear()
291         };
292
293     }
294 };