UNPKG

4.38 kBJavaScriptView Raw
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
12const astUtils = require("../ast-utils");
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18module.exports = {
19 meta: {
20 docs: {
21 description: "disallow unnecessary boolean casts",
22 category: "Possible Errors",
23 recommended: true,
24 url: "https://eslint.org/docs/rules/no-extra-boolean-cast"
25 },
26
27 schema: [],
28
29 fixable: "code",
30
31 messages: {
32 unexpectedCall: "Redundant Boolean call.",
33 unexpectedNegation: "Redundant double negation."
34 }
35 },
36
37 create(context) {
38 const sourceCode = context.getSourceCode();
39
40 // Node types which have a test which will coerce values to booleans.
41 const BOOLEAN_NODE_TYPES = [
42 "IfStatement",
43 "DoWhileStatement",
44 "WhileStatement",
45 "ConditionalExpression",
46 "ForStatement"
47 ];
48
49 /**
50 * Check if a node is in a context where its value would be coerced to a boolean at runtime.
51 *
52 * @param {Object} node The node
53 * @param {Object} parent Its parent
54 * @returns {boolean} If it is in a boolean context
55 */
56 function isInBooleanContext(node, parent) {
57 return (
58 (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 &&
59 node === parent.test) ||
60
61 // !<bool>
62 (parent.type === "UnaryExpression" &&
63 parent.operator === "!")
64 );
65 }
66
67
68 return {
69 UnaryExpression(node) {
70 const ancestors = context.getAncestors(),
71 parent = ancestors.pop(),
72 grandparent = ancestors.pop();
73
74 // Exit early if it's guaranteed not to match
75 if (node.operator !== "!" ||
76 parent.type !== "UnaryExpression" ||
77 parent.operator !== "!") {
78 return;
79 }
80
81 if (isInBooleanContext(parent, grandparent) ||
82
83 // Boolean(<bool>) and new Boolean(<bool>)
84 ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") &&
85 grandparent.callee.type === "Identifier" &&
86 grandparent.callee.name === "Boolean")
87 ) {
88 context.report({
89 node,
90 messageId: "unexpectedNegation",
91 fix: fixer => fixer.replaceText(parent, sourceCode.getText(node.argument))
92 });
93 }
94 },
95 CallExpression(node) {
96 const parent = node.parent;
97
98 if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") {
99 return;
100 }
101
102 if (isInBooleanContext(node, parent)) {
103 context.report({
104 node,
105 messageId: "unexpectedCall",
106 fix: fixer => {
107 if (!node.arguments.length) {
108 return fixer.replaceText(parent, "true");
109 }
110
111 if (node.arguments.length > 1 || node.arguments[0].type === "SpreadElement") {
112 return null;
113 }
114
115 const argument = node.arguments[0];
116
117 if (astUtils.getPrecedence(argument) < astUtils.getPrecedence(node.parent)) {
118 return fixer.replaceText(node, `(${sourceCode.getText(argument)})`);
119 }
120 return fixer.replaceText(node, sourceCode.getText(argument));
121 }
122 });
123 }
124 }
125 };
126
127 }
128};