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