Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add enforceForInnerExpressions option to no-extra-boolean-cast #18222

13 changes: 13 additions & 0 deletions docs/src/rules/no-extra-boolean-cast.md
Expand Up @@ -61,6 +61,19 @@ do {
for (; !!foo; ) {
// ...
}

// Complex expressions are also checked for their resulting expression(s).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tanujkanti4441 Added some examples 👍


const ternary = Boolean(bar ? !!baz : bat);

const commaOperator = Boolean((bar, baz, !!bat));

// another comma operator example
for (let i = 0; (console.log(i), Boolean(i < 10)); i++) {
// ...
}

const nullishCoalescingOperator = Boolean(bar ?? Boolean(baz));
```

:::
Expand Down
30 changes: 28 additions & 2 deletions lib/rules/no-extra-boolean-cast.js
Expand Up @@ -115,6 +115,23 @@ module.exports = {
return isInFlaggedContext(node.parent);
}

if (node.parent.type === "ConditionalExpression" && (node.parent.consequent === node || node.parent.alternate === node)) {
return isInFlaggedContext(node.parent);
}

/*
* Check last expression only in a sequence, i.e. if ((1, 2, Boolean(3))) {}, since
* the others don't affect the result of the expression.
*/
if (node.parent.type === "SequenceExpression" && node.parent.expressions.at(-1) === node) {
return isInFlaggedContext(node.parent);
}

// Check the right hand side of a `??` operator.
if (node.parent.type === "LogicalExpression" && node.parent.operator === "??" && node.parent.right === node) {
return isInFlaggedContext(node.parent);
}

return isInBooleanContext(node) ||
(isLogicalContext(node.parent) &&

Expand Down Expand Up @@ -147,7 +164,6 @@ module.exports = {
* Determines whether the given node needs to be parenthesized when replacing the previous node.
* It assumes that `previousNode` is the node to be reported by this rule, so it has a limited list
* of possible parent node types. By the same assumption, the node's role in a particular parent is already known.
* For example, if the parent is `ConditionalExpression`, `previousNode` must be its `test` child.
* @param {ASTNode} previousNode Previous node.
* @param {ASTNode} node The node to check.
* @throws {Error} (Unreachable.)
Expand All @@ -157,6 +173,7 @@ module.exports = {
if (previousNode.parent.type === "ChainExpression") {
return needsParens(previousNode.parent, node);
}

if (isParenthesized(previousNode)) {

// parentheses around the previous node will stay, so there is no need for an additional pair
Expand All @@ -174,9 +191,18 @@ module.exports = {
case "DoWhileStatement":
case "WhileStatement":
case "ForStatement":
case "SequenceExpression":
return false;
case "ConditionalExpression":
return precedence(node) <= precedence(parent);
if (previousNode === parent.test) {
return precedence(node) <= precedence(parent);
}
if (previousNode === parent.consequent || previousNode === parent.alternate) {
return precedence(node) < precedence({ type: "AssignmentExpression" });
mdjermanovic marked this conversation as resolved.
Show resolved Hide resolved
}

/* c8 ignore next */
throw new Error("Ternary child must be test, consequent, or alternate.");
case "UnaryExpression":
return precedence(node) < precedence(parent);
case "LogicalExpression":
Expand Down
76 changes: 76 additions & 0 deletions tests/lib/rules/no-extra-boolean-cast.js
Expand Up @@ -34,6 +34,7 @@ ruleTester.run("no-extra-boolean-cast", rule, {
"for(Boolean(foo);;) {}",
"for(;; Boolean(foo)) {}",
"if (new Boolean(foo)) {}",
"if ((Boolean(1), 2)) {}",
{
code: "var foo = bar || !!baz",
options: [{ enforceForLogicalOperands: true }]
Expand Down Expand Up @@ -2435,6 +2436,81 @@ ruleTester.run("no-extra-boolean-cast", rule, {
ecmaVersion: 2020
},
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "if ((1, 2, Boolean(3))) {}",
output: "if ((1, 2, 3)) {}",
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "if (a ?? Boolean(b)) {}",
output: "if (a ?? b) {}",
options: [{ enforceForLogicalOperands: true }],
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "if (a ?? Boolean(b)) {}",
output: "if (a ?? b) {}",
options: [{ enforceForLogicalOperands: false }],
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "if ((a, b, c ?? (d, e, f ?? Boolean(g)))) {}",
output: "if ((a, b, c ?? (d, e, f ?? g))) {}",
options: [{ enforceForLogicalOperands: false }],
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "if ((a, b, c ?? (d, e, f ?? Boolean(g)))) {}",
output: "if ((a, b, c ?? (d, e, f ?? g))) {}",
options: [{ enforceForLogicalOperands: true }],
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "if (a ? Boolean(b) : c) {}",
output: "if (a ? b : c) {}",
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "if (a ? b : Boolean(c)) {}",
output: "if (a ? b : c) {}",
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "if (a ? b : Boolean(c ? d : e)) {}",
output: "if (a ? b : c ? d : e) {}",
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "const ternary = Boolean(bar ? !!baz : bat);",
output: "const ternary = Boolean(bar ? baz : bat);",
errors: [{ messageId: "unexpectedNegation" }]
},
{
code: "const commaOperator = Boolean((bar, baz, !!bat));",
output: "const commaOperator = Boolean((bar, baz, bat));",
errors: [{ messageId: "unexpectedNegation" }]
},
{
code: `
for (let i = 0; (console.log(i), Boolean(i < 10)); i++) {
// ...
}`,
output: `
for (let i = 0; (console.log(i), i < 10); i++) {
// ...
}`,
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "const nullishCoalescingOperator = Boolean(bar ?? Boolean(baz));",
output: "const nullishCoalescingOperator = Boolean(bar ?? baz);",
errors: [{ messageId: "unexpectedCall" }]
},
{
code: "if (a ? Boolean(b = c) : Boolean(d = e));",
output: "if (a ? b = c : d = e);",
errors: [{ messageId: "unexpectedCall" }, { messageId: "unexpectedCall" }]
}
]
});