.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-extra-boolean-cast.js
1 /**
2  * @fileoverview Rule to flag unnecessary double negation in Boolean contexts
3  * @author Brandon Mills
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13 const eslintUtils = require("eslint-utils");
14
15 const precedence = astUtils.getPrecedence;
16
17 //------------------------------------------------------------------------------
18 // Rule Definition
19 //------------------------------------------------------------------------------
20
21 module.exports = {
22     meta: {
23         type: "suggestion",
24
25         docs: {
26             description: "disallow unnecessary boolean casts",
27             category: "Possible Errors",
28             recommended: true,
29             url: "https://eslint.org/docs/rules/no-extra-boolean-cast"
30         },
31
32         schema: [{
33             type: "object",
34             properties: {
35                 enforceForLogicalOperands: {
36                     type: "boolean",
37                     default: false
38                 }
39             },
40             additionalProperties: false
41         }],
42         fixable: "code",
43
44         messages: {
45             unexpectedCall: "Redundant Boolean call.",
46             unexpectedNegation: "Redundant double negation."
47         }
48     },
49
50     create(context) {
51         const sourceCode = context.getSourceCode();
52
53         // Node types which have a test which will coerce values to booleans.
54         const BOOLEAN_NODE_TYPES = [
55             "IfStatement",
56             "DoWhileStatement",
57             "WhileStatement",
58             "ConditionalExpression",
59             "ForStatement"
60         ];
61
62         /**
63          * Check if a node is a Boolean function or constructor.
64          * @param {ASTNode} node the node
65          * @returns {boolean} If the node is Boolean function or constructor
66          */
67         function isBooleanFunctionOrConstructorCall(node) {
68
69             // Boolean(<bool>) and new Boolean(<bool>)
70             return (node.type === "CallExpression" || node.type === "NewExpression") &&
71                     node.callee.type === "Identifier" &&
72                         node.callee.name === "Boolean";
73         }
74
75         /**
76          * Checks whether the node is a logical expression and that the option is enabled
77          * @param {ASTNode} node the node
78          * @returns {boolean} if the node is a logical expression and option is enabled
79          */
80         function isLogicalContext(node) {
81             return node.type === "LogicalExpression" &&
82             (node.operator === "||" || node.operator === "&&") &&
83             (context.options.length && context.options[0].enforceForLogicalOperands === true);
84
85         }
86
87
88         /**
89          * Check if a node is in a context where its value would be coerced to a boolean at runtime.
90          * @param {ASTNode} node The node
91          * @returns {boolean} If it is in a boolean context
92          */
93         function isInBooleanContext(node) {
94             return (
95                 (isBooleanFunctionOrConstructorCall(node.parent) &&
96                 node === node.parent.arguments[0]) ||
97
98                 (BOOLEAN_NODE_TYPES.indexOf(node.parent.type) !== -1 &&
99                     node === node.parent.test) ||
100
101                 // !<bool>
102                 (node.parent.type === "UnaryExpression" &&
103                     node.parent.operator === "!")
104             );
105         }
106
107         /**
108          * Checks whether the node is a context that should report an error
109          * Acts recursively if it is in a logical context
110          * @param {ASTNode} node the node
111          * @returns {boolean} If the node is in one of the flagged contexts
112          */
113         function isInFlaggedContext(node) {
114             if (node.parent.type === "ChainExpression") {
115                 return isInFlaggedContext(node.parent);
116             }
117
118             return isInBooleanContext(node) ||
119             (isLogicalContext(node.parent) &&
120
121             // For nested logical statements
122             isInFlaggedContext(node.parent)
123             );
124         }
125
126
127         /**
128          * Check if a node has comments inside.
129          * @param {ASTNode} node The node to check.
130          * @returns {boolean} `true` if it has comments inside.
131          */
132         function hasCommentsInside(node) {
133             return Boolean(sourceCode.getCommentsInside(node).length);
134         }
135
136         /**
137          * Checks if the given node is wrapped in grouping parentheses. Parentheses for constructs such as if() don't count.
138          * @param {ASTNode} node The node to check.
139          * @returns {boolean} `true` if the node is parenthesized.
140          * @private
141          */
142         function isParenthesized(node) {
143             return eslintUtils.isParenthesized(1, node, sourceCode);
144         }
145
146         /**
147          * Determines whether the given node needs to be parenthesized when replacing the previous node.
148          * It assumes that `previousNode` is the node to be reported by this rule, so it has a limited list
149          * of possible parent node types. By the same assumption, the node's role in a particular parent is already known.
150          * For example, if the parent is `ConditionalExpression`, `previousNode` must be its `test` child.
151          * @param {ASTNode} previousNode Previous node.
152          * @param {ASTNode} node The node to check.
153          * @returns {boolean} `true` if the node needs to be parenthesized.
154          */
155         function needsParens(previousNode, node) {
156             if (previousNode.parent.type === "ChainExpression") {
157                 return needsParens(previousNode.parent, node);
158             }
159             if (isParenthesized(previousNode)) {
160
161                 // parentheses around the previous node will stay, so there is no need for an additional pair
162                 return false;
163             }
164
165             // parent of the previous node will become parent of the replacement node
166             const parent = previousNode.parent;
167
168             switch (parent.type) {
169                 case "CallExpression":
170                 case "NewExpression":
171                     return node.type === "SequenceExpression";
172                 case "IfStatement":
173                 case "DoWhileStatement":
174                 case "WhileStatement":
175                 case "ForStatement":
176                     return false;
177                 case "ConditionalExpression":
178                     return precedence(node) <= precedence(parent);
179                 case "UnaryExpression":
180                     return precedence(node) < precedence(parent);
181                 case "LogicalExpression":
182                     if (astUtils.isMixedLogicalAndCoalesceExpressions(node, parent)) {
183                         return true;
184                     }
185                     if (previousNode === parent.left) {
186                         return precedence(node) < precedence(parent);
187                     }
188                     return precedence(node) <= precedence(parent);
189
190                 /* istanbul ignore next */
191                 default:
192                     throw new Error(`Unexpected parent type: ${parent.type}`);
193             }
194         }
195
196         return {
197             UnaryExpression(node) {
198                 const parent = node.parent;
199
200
201                 // Exit early if it's guaranteed not to match
202                 if (node.operator !== "!" ||
203                           parent.type !== "UnaryExpression" ||
204                           parent.operator !== "!") {
205                     return;
206                 }
207
208
209                 if (isInFlaggedContext(parent)) {
210                     context.report({
211                         node: parent,
212                         messageId: "unexpectedNegation",
213                         fix(fixer) {
214                             if (hasCommentsInside(parent)) {
215                                 return null;
216                             }
217
218                             if (needsParens(parent, node.argument)) {
219                                 return fixer.replaceText(parent, `(${sourceCode.getText(node.argument)})`);
220                             }
221
222                             let prefix = "";
223                             const tokenBefore = sourceCode.getTokenBefore(parent);
224                             const firstReplacementToken = sourceCode.getFirstToken(node.argument);
225
226                             if (
227                                 tokenBefore &&
228                                 tokenBefore.range[1] === parent.range[0] &&
229                                 !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken)
230                             ) {
231                                 prefix = " ";
232                             }
233
234                             return fixer.replaceText(parent, prefix + sourceCode.getText(node.argument));
235                         }
236                     });
237                 }
238             },
239
240             CallExpression(node) {
241                 if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") {
242                     return;
243                 }
244
245                 if (isInFlaggedContext(node)) {
246                     context.report({
247                         node,
248                         messageId: "unexpectedCall",
249                         fix(fixer) {
250                             const parent = node.parent;
251
252                             if (node.arguments.length === 0) {
253                                 if (parent.type === "UnaryExpression" && parent.operator === "!") {
254
255                                     /*
256                                      * !Boolean() -> true
257                                      */
258
259                                     if (hasCommentsInside(parent)) {
260                                         return null;
261                                     }
262
263                                     const replacement = "true";
264                                     let prefix = "";
265                                     const tokenBefore = sourceCode.getTokenBefore(parent);
266
267                                     if (
268                                         tokenBefore &&
269                                         tokenBefore.range[1] === parent.range[0] &&
270                                         !astUtils.canTokensBeAdjacent(tokenBefore, replacement)
271                                     ) {
272                                         prefix = " ";
273                                     }
274
275                                     return fixer.replaceText(parent, prefix + replacement);
276                                 }
277
278                                 /*
279                                  * Boolean() -> false
280                                  */
281
282                                 if (hasCommentsInside(node)) {
283                                     return null;
284                                 }
285
286                                 return fixer.replaceText(node, "false");
287                             }
288
289                             if (node.arguments.length === 1) {
290                                 const argument = node.arguments[0];
291
292                                 if (argument.type === "SpreadElement" || hasCommentsInside(node)) {
293                                     return null;
294                                 }
295
296                                 /*
297                                  * Boolean(expression) -> expression
298                                  */
299
300                                 if (needsParens(node, argument)) {
301                                     return fixer.replaceText(node, `(${sourceCode.getText(argument)})`);
302                                 }
303
304                                 return fixer.replaceText(node, sourceCode.getText(argument));
305                             }
306
307                             // two or more arguments
308                             return null;
309                         }
310                     });
311                 }
312             }
313         };
314
315     }
316 };