From dc05d569dc2f8bd72c6564069e930b2fb4788993 Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Wed, 1 Jan 2020 17:37:18 -0500 Subject: [PATCH 01/11] Working rule, updated documentation --- docs/rules/no-extra-boolean-cast.md | 50 ++ lib/rules/no-extra-boolean-cast.js | 69 ++- tests/lib/rules/no-extra-boolean-cast.js | 680 ++++++++++++++++++++++- 3 files changed, 786 insertions(+), 13 deletions(-) diff --git a/docs/rules/no-extra-boolean-cast.md b/docs/rules/no-extra-boolean-cast.md index baf795a4faa..3dcfcbef62f 100644 --- a/docs/rules/no-extra-boolean-cast.md +++ b/docs/rules/no-extra-boolean-cast.md @@ -68,3 +68,53 @@ function foo() { var foo = bar ? !!baz : !!bat; ``` + +## Options + +This rule has an object option: + +* `"enforceForLogicalOperands"` when set to `true`, checks whether the extra boolean is contained within a logical expression. Default is `false`, meaning that this rule by default does not warn about extra booleans inside logical expression. + +### enforceForLogicalOperands + +Examples of **incorrect** code for this rule with `"enforceForLogicalOperands"` option set to `true`: + +```js +/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ + +if (!!foo || bar) { + //... +} + +if ((!!foo || bar) && baz) { + //... +} + +var foo = new Boolean(!!bar || baz) + + +if (Boolean(foo || bar)) { + // ... +} +``` + +Examples of **correct** code for this rule with `"enforceForLogicalOperands"` option set to `true`: + +```js +/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ + +if (foo || bar) { + //... +} + +if ((foo || bar) && baz) { + //... +} + +var foo = new Boolean(bar || baz) + + +if (foo || bar) { + // ... +} +``` diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index 336f601d165..4200660cf9f 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -26,7 +26,15 @@ module.exports = { url: "https://eslint.org/docs/rules/no-extra-boolean-cast" }, - schema: [], + schema: [{ + type: "object", + properties: { + enforceForLogicalOperands: { + type: "boolean" + } + }, + additionalProperties: false + }], fixable: "code", messages: { @@ -47,6 +55,31 @@ module.exports = { "ForStatement" ]; + /** + * Check if a node is inside a Boolean function or constructor. + * @param {ASTNode} node the node + * @returns {boolean} If the node is in the context + */ + function isInFunctionOrConstructorContext(node) { + return (node.type === "CallExpression" || node.type === "NewExpression") && + node.callee.type === "Identifier" && + node.callee.name === "Boolean"; + + + } + + /** + * Checks whether the node is a logical expression and that the option is enabled + * @param {ASTNode} node the node + * @returns {boolean} if the node is a logical expression and option is enabled + */ + function isInLogicalContext(node) { + return node.type === "LogicalExpression" && + (context.options.length && context.options[0].enforceForLogicalOperands === true); + + } + + /** * Check if a node is in a context where its value would be coerced to a boolean at runtime. * @param {ASTNode} node The node @@ -54,6 +87,8 @@ module.exports = { * @returns {boolean} If it is in a boolean context */ function isInBooleanContext(node, parent) { + + // Boolean() and new Boolean() return ( (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 && node === parent.test) || @@ -64,6 +99,24 @@ module.exports = { ); } + /** + * Checks whether the node is in a context that should report an error + * Acts recursively if it is in a logical context + * @param {ASTNode} node the node + * @param {ASTNode} parent the parent + * @returns {boolean} If the node is in one of the flagged contexts + */ + function isInFlaggedContext(node, parent) { + return isInBooleanContext(node, parent) || + isInFunctionOrConstructorContext(parent) || + (isInLogicalContext(parent) && + + // For nested logical statements + isInFlaggedContext(parent, parent.parent) + ); + } + + /** * Check if a node has comments inside. * @param {ASTNode} node The node to check. @@ -79,20 +132,16 @@ module.exports = { parent = ancestors.pop(), grandparent = ancestors.pop(); + // Exit early if it's guaranteed not to match if (node.operator !== "!" || - parent.type !== "UnaryExpression" || - parent.operator !== "!") { + parent.type !== "UnaryExpression" || + parent.operator !== "!") { return; } - if (isInBooleanContext(parent, grandparent) || - // Boolean() and new Boolean() - ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") && - grandparent.callee.type === "Identifier" && - grandparent.callee.name === "Boolean") - ) { + if (isInFlaggedContext(parent, grandparent)) { context.report({ node: parent, messageId: "unexpectedNegation", @@ -122,7 +171,7 @@ module.exports = { return; } - if (isInBooleanContext(node, parent)) { + if (isInFlaggedContext(node, parent)) { context.report({ node, messageId: "unexpectedCall", diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index a530f81f603..4fd618e46d3 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for no-extra-boolean-cast rule. - * @author Brandon Mills + *@fileoverview Tests for no-extra-boolean-cast rule. + *@author Brandon Mills */ "use strict"; @@ -21,6 +21,8 @@ const ruleTester = new RuleTester(); ruleTester.run("no-extra-boolean-cast", rule, { valid: [ + + "var foo = !!bar;", "function foo() { return !!bar; }", "var foo = bar() ? !!baz : !!bat", @@ -31,10 +33,29 @@ ruleTester.run("no-extra-boolean-cast", rule, { "var foo = bar() ? Boolean(baz) : Boolean(bat)", "for(Boolean(foo);;) {}", "for(;; Boolean(foo)) {}", - "if (new Boolean(foo)) {}" + "if (new Boolean(foo)) {}", + "var foo = bar || !!baz", + "var foo = bar && !!baz", + "var foo = bar || (baz && !!bat)", + "function foo() { return (!!bar || baz); }", + "var foo = bar() ? (!!baz && bat) : (!!bat && qux)", + "for(!!(foo && bar);;) {}", + "for(;; !!(foo || bar)) {}", + "var foo = Boolean(bar) || baz;", + "var foo = bar || Boolean(baz);", + "var foo = Boolean(bar) || Boolean(baz);", + "function foo() { return (Boolean(bar) || baz); }", + "var foo = bar() ? Boolean(baz) || bat : Boolean(bat)", + "for(Boolean(foo) || bar;;) {}", + "for(;; Boolean(foo) || bar) {}", + "if (new Boolean(foo) || bar) {}" + + ], invalid: [ + + { code: "if (!!foo) {}", output: "if (foo) {}", @@ -278,6 +299,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { }] }, + // Adjacent tokens tests { code: "function *foo() { yield!!a ? b : c }", @@ -606,6 +628,658 @@ ruleTester.run("no-extra-boolean-cast", rule, { messageId: "unexpectedCall", type: "CallExpression" }] + }, + + + // In Logical context + { + code: "if (!!foo || bar) {}", + output: "if (foo || bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 5, + endColumn: 10 + }] + }, + { + code: "if (!!foo && bar) {}", + output: "if (foo && bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 5, + endColumn: 10 + }] + }, + + { + code: "if ((!!foo || bar) && bat) {}", + output: "if ((foo || bar) && bat) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 6, + endColumn: 11 + }] + }, + { + code: "if (foo && !!bar) {}", + output: "if (foo && bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 12, + endColumn: 17 + }] + }, + { + code: "do {} while (!!foo || bar)", + output: "do {} while (foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 14 + }] + }, + { + code: "while (!!foo || bar) {}", + output: "while (foo || bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 8 + }] + }, + { + code: "!!foo && bat ? bar : baz", + output: "foo && bat ? bar : baz", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 1 + }] + }, + { + code: "for (; !!foo || bar;) {}", + output: "for (; foo || bar;) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 8 + }] + }, + { + code: "!!!foo || bar", + output: "!foo || bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 2 + }] + }, + { + code: "Boolean(!!foo || bar)", + output: "Boolean(foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 9 + }] + }, + { + code: "new Boolean(!!foo || bar)", + output: "new Boolean(foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 13 + }] + }, + { + code: "if (Boolean(foo) || bar) {}", + output: "if (foo || bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "do {} while (Boolean(foo) || bar)", + output: "do {} while (foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "while (Boolean(foo) || bar) {}", + output: "while (foo || bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "Boolean(foo) || bat ? bar : baz", + output: "foo || bat ? bar : baz", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "for (; Boolean(foo) || bar;) {}", + output: "for (; foo || bar;) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo) || bar", + output: "!foo || bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo && bar) || bat", + output: "!(foo && bar) || bat", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo + bar) || bat", + output: "!(foo + bar) || bat", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(+foo) || bar", + output: "!+foo || bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo()) || bar", + output: "!foo() || bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo() || bar)", + output: "!(foo() || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo = bar) || bat", + output: "!(foo = bar) || bat", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(...foo) || bar;", + output: null, + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo, bar()) || bar;", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean((foo, bar()) || bat);", + output: "!((foo, bar()) || bat);", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean() || bar;", + output: "true || bar;", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!(Boolean()) || bar;", + output: "true || bar;", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if (!Boolean() || bar) { foo() }", + output: "if (true || bar) { foo() }", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "while (!Boolean() || bar) { foo() }", + output: "while (true || bar) { foo() }", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "var foo = Boolean() || bar ? bar() : baz()", + output: "var foo = false || bar ? bar() : baz()", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if (Boolean() || bar) { foo() }", + output: "if (false || bar) { foo() }", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "while (Boolean() || bar) { foo() }", + output: "while (false || bar) { foo() }", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + + + // Adjacent tokens tests + { + code: "function *foo() { yield(!!a || d) ? b : c }", + output: "function *foo() { yield(a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield(!! a || d) ? b : c }", + output: "function *foo() { yield(a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield(! !a || d) ? b : c }", + output: "function *foo() { yield(a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield (!!a || d) ? b : c }", + output: "function *foo() { yield (a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield/**/(!!a || d) ? b : c }", + output: "function *foo() { yield/**/(a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "x=!!a || d ? b : c ", + output: "x=a || d ? b : c ", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "void(!Boolean() || bar)", + output: "void(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "void(! Boolean() || bar)", + output: "void(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "typeof(!Boolean() || bar)", + output: "typeof(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(!Boolean() || bar)", + output: "(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "void/**/(!Boolean() || bar)", + output: "void/**/(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + + // Comments tests + { + code: "!/**/(!!foo || bar)", + output: "!/**/(foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!!/**/!foo || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!!!/**/foo || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!(!!foo || bar)/**/", + output: "!(foo || bar)/**/", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "if(!/**/!foo || bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "(!!/**/foo || bar ? 1 : 2)", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!/**/(Boolean(foo) || bar)", + output: "!/**/(foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean/**/(foo) || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(/**/foo) || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo/**/) || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!(Boolean(foo)|| bar)/**/", + output: "!(foo|| bar)/**/", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean/**/(foo) || bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(Boolean(foo/**/)|| bar ? 1 : 2)", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "/**/!Boolean()|| bar", + output: "/**/true|| bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!/**/Boolean()|| bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean/**/()|| bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(/**/)|| bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(!Boolean()|| bar)/**/", + output: "(true|| bar)/**/", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(!/**/Boolean()|| bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(!Boolean(/**/) || bar ? 1 : 2)", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(/**/Boolean()|| bar);", + output: "if(/**/false|| bar);", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean/**/()|| bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean(/**/)|| bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean()|| bar/**/);", + output: "if(false|| bar/**/);", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(Boolean/**/()|| bar ? 1 : 2)", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] } + ] }); From 288d20edda89139998c09b5300be27b40ee152f4 Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Wed, 1 Jan 2020 17:37:18 -0500 Subject: [PATCH 02/11] New: Added enforceForLogicalOperands option to no-extra-boolean-cast (fixes #12137) --- docs/rules/no-extra-boolean-cast.md | 50 ++ lib/rules/no-extra-boolean-cast.js | 69 ++- tests/lib/rules/no-extra-boolean-cast.js | 680 ++++++++++++++++++++++- 3 files changed, 786 insertions(+), 13 deletions(-) diff --git a/docs/rules/no-extra-boolean-cast.md b/docs/rules/no-extra-boolean-cast.md index baf795a4faa..3dcfcbef62f 100644 --- a/docs/rules/no-extra-boolean-cast.md +++ b/docs/rules/no-extra-boolean-cast.md @@ -68,3 +68,53 @@ function foo() { var foo = bar ? !!baz : !!bat; ``` + +## Options + +This rule has an object option: + +* `"enforceForLogicalOperands"` when set to `true`, checks whether the extra boolean is contained within a logical expression. Default is `false`, meaning that this rule by default does not warn about extra booleans inside logical expression. + +### enforceForLogicalOperands + +Examples of **incorrect** code for this rule with `"enforceForLogicalOperands"` option set to `true`: + +```js +/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ + +if (!!foo || bar) { + //... +} + +if ((!!foo || bar) && baz) { + //... +} + +var foo = new Boolean(!!bar || baz) + + +if (Boolean(foo || bar)) { + // ... +} +``` + +Examples of **correct** code for this rule with `"enforceForLogicalOperands"` option set to `true`: + +```js +/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ + +if (foo || bar) { + //... +} + +if ((foo || bar) && baz) { + //... +} + +var foo = new Boolean(bar || baz) + + +if (foo || bar) { + // ... +} +``` diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index 336f601d165..4200660cf9f 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -26,7 +26,15 @@ module.exports = { url: "https://eslint.org/docs/rules/no-extra-boolean-cast" }, - schema: [], + schema: [{ + type: "object", + properties: { + enforceForLogicalOperands: { + type: "boolean" + } + }, + additionalProperties: false + }], fixable: "code", messages: { @@ -47,6 +55,31 @@ module.exports = { "ForStatement" ]; + /** + * Check if a node is inside a Boolean function or constructor. + * @param {ASTNode} node the node + * @returns {boolean} If the node is in the context + */ + function isInFunctionOrConstructorContext(node) { + return (node.type === "CallExpression" || node.type === "NewExpression") && + node.callee.type === "Identifier" && + node.callee.name === "Boolean"; + + + } + + /** + * Checks whether the node is a logical expression and that the option is enabled + * @param {ASTNode} node the node + * @returns {boolean} if the node is a logical expression and option is enabled + */ + function isInLogicalContext(node) { + return node.type === "LogicalExpression" && + (context.options.length && context.options[0].enforceForLogicalOperands === true); + + } + + /** * Check if a node is in a context where its value would be coerced to a boolean at runtime. * @param {ASTNode} node The node @@ -54,6 +87,8 @@ module.exports = { * @returns {boolean} If it is in a boolean context */ function isInBooleanContext(node, parent) { + + // Boolean() and new Boolean() return ( (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 && node === parent.test) || @@ -64,6 +99,24 @@ module.exports = { ); } + /** + * Checks whether the node is in a context that should report an error + * Acts recursively if it is in a logical context + * @param {ASTNode} node the node + * @param {ASTNode} parent the parent + * @returns {boolean} If the node is in one of the flagged contexts + */ + function isInFlaggedContext(node, parent) { + return isInBooleanContext(node, parent) || + isInFunctionOrConstructorContext(parent) || + (isInLogicalContext(parent) && + + // For nested logical statements + isInFlaggedContext(parent, parent.parent) + ); + } + + /** * Check if a node has comments inside. * @param {ASTNode} node The node to check. @@ -79,20 +132,16 @@ module.exports = { parent = ancestors.pop(), grandparent = ancestors.pop(); + // Exit early if it's guaranteed not to match if (node.operator !== "!" || - parent.type !== "UnaryExpression" || - parent.operator !== "!") { + parent.type !== "UnaryExpression" || + parent.operator !== "!") { return; } - if (isInBooleanContext(parent, grandparent) || - // Boolean() and new Boolean() - ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") && - grandparent.callee.type === "Identifier" && - grandparent.callee.name === "Boolean") - ) { + if (isInFlaggedContext(parent, grandparent)) { context.report({ node: parent, messageId: "unexpectedNegation", @@ -122,7 +171,7 @@ module.exports = { return; } - if (isInBooleanContext(node, parent)) { + if (isInFlaggedContext(node, parent)) { context.report({ node, messageId: "unexpectedCall", diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index a530f81f603..4fd618e46d3 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for no-extra-boolean-cast rule. - * @author Brandon Mills + *@fileoverview Tests for no-extra-boolean-cast rule. + *@author Brandon Mills */ "use strict"; @@ -21,6 +21,8 @@ const ruleTester = new RuleTester(); ruleTester.run("no-extra-boolean-cast", rule, { valid: [ + + "var foo = !!bar;", "function foo() { return !!bar; }", "var foo = bar() ? !!baz : !!bat", @@ -31,10 +33,29 @@ ruleTester.run("no-extra-boolean-cast", rule, { "var foo = bar() ? Boolean(baz) : Boolean(bat)", "for(Boolean(foo);;) {}", "for(;; Boolean(foo)) {}", - "if (new Boolean(foo)) {}" + "if (new Boolean(foo)) {}", + "var foo = bar || !!baz", + "var foo = bar && !!baz", + "var foo = bar || (baz && !!bat)", + "function foo() { return (!!bar || baz); }", + "var foo = bar() ? (!!baz && bat) : (!!bat && qux)", + "for(!!(foo && bar);;) {}", + "for(;; !!(foo || bar)) {}", + "var foo = Boolean(bar) || baz;", + "var foo = bar || Boolean(baz);", + "var foo = Boolean(bar) || Boolean(baz);", + "function foo() { return (Boolean(bar) || baz); }", + "var foo = bar() ? Boolean(baz) || bat : Boolean(bat)", + "for(Boolean(foo) || bar;;) {}", + "for(;; Boolean(foo) || bar) {}", + "if (new Boolean(foo) || bar) {}" + + ], invalid: [ + + { code: "if (!!foo) {}", output: "if (foo) {}", @@ -278,6 +299,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { }] }, + // Adjacent tokens tests { code: "function *foo() { yield!!a ? b : c }", @@ -606,6 +628,658 @@ ruleTester.run("no-extra-boolean-cast", rule, { messageId: "unexpectedCall", type: "CallExpression" }] + }, + + + // In Logical context + { + code: "if (!!foo || bar) {}", + output: "if (foo || bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 5, + endColumn: 10 + }] + }, + { + code: "if (!!foo && bar) {}", + output: "if (foo && bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 5, + endColumn: 10 + }] + }, + + { + code: "if ((!!foo || bar) && bat) {}", + output: "if ((foo || bar) && bat) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 6, + endColumn: 11 + }] + }, + { + code: "if (foo && !!bar) {}", + output: "if (foo && bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 12, + endColumn: 17 + }] + }, + { + code: "do {} while (!!foo || bar)", + output: "do {} while (foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 14 + }] + }, + { + code: "while (!!foo || bar) {}", + output: "while (foo || bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 8 + }] + }, + { + code: "!!foo && bat ? bar : baz", + output: "foo && bat ? bar : baz", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 1 + }] + }, + { + code: "for (; !!foo || bar;) {}", + output: "for (; foo || bar;) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 8 + }] + }, + { + code: "!!!foo || bar", + output: "!foo || bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 2 + }] + }, + { + code: "Boolean(!!foo || bar)", + output: "Boolean(foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 9 + }] + }, + { + code: "new Boolean(!!foo || bar)", + output: "new Boolean(foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 13 + }] + }, + { + code: "if (Boolean(foo) || bar) {}", + output: "if (foo || bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "do {} while (Boolean(foo) || bar)", + output: "do {} while (foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "while (Boolean(foo) || bar) {}", + output: "while (foo || bar) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "Boolean(foo) || bat ? bar : baz", + output: "foo || bat ? bar : baz", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "for (; Boolean(foo) || bar;) {}", + output: "for (; foo || bar;) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo) || bar", + output: "!foo || bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo && bar) || bat", + output: "!(foo && bar) || bat", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo + bar) || bat", + output: "!(foo + bar) || bat", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(+foo) || bar", + output: "!+foo || bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo()) || bar", + output: "!foo() || bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo() || bar)", + output: "!(foo() || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo = bar) || bat", + output: "!(foo = bar) || bat", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(...foo) || bar;", + output: null, + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo, bar()) || bar;", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean((foo, bar()) || bat);", + output: "!((foo, bar()) || bat);", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean() || bar;", + output: "true || bar;", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!(Boolean()) || bar;", + output: "true || bar;", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if (!Boolean() || bar) { foo() }", + output: "if (true || bar) { foo() }", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "while (!Boolean() || bar) { foo() }", + output: "while (true || bar) { foo() }", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "var foo = Boolean() || bar ? bar() : baz()", + output: "var foo = false || bar ? bar() : baz()", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if (Boolean() || bar) { foo() }", + output: "if (false || bar) { foo() }", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "while (Boolean() || bar) { foo() }", + output: "while (false || bar) { foo() }", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + + + // Adjacent tokens tests + { + code: "function *foo() { yield(!!a || d) ? b : c }", + output: "function *foo() { yield(a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield(!! a || d) ? b : c }", + output: "function *foo() { yield(a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield(! !a || d) ? b : c }", + output: "function *foo() { yield(a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield (!!a || d) ? b : c }", + output: "function *foo() { yield (a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield/**/(!!a || d) ? b : c }", + output: "function *foo() { yield/**/(a || d) ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "x=!!a || d ? b : c ", + output: "x=a || d ? b : c ", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "void(!Boolean() || bar)", + output: "void(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "void(! Boolean() || bar)", + output: "void(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "typeof(!Boolean() || bar)", + output: "typeof(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(!Boolean() || bar)", + output: "(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "void/**/(!Boolean() || bar)", + output: "void/**/(true || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + + // Comments tests + { + code: "!/**/(!!foo || bar)", + output: "!/**/(foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!!/**/!foo || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!!!/**/foo || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!(!!foo || bar)/**/", + output: "!(foo || bar)/**/", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "if(!/**/!foo || bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "(!!/**/foo || bar ? 1 : 2)", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!/**/(Boolean(foo) || bar)", + output: "!/**/(foo || bar)", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean/**/(foo) || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(/**/foo) || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo/**/) || bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!(Boolean(foo)|| bar)/**/", + output: "!(foo|| bar)/**/", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean/**/(foo) || bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(Boolean(foo/**/)|| bar ? 1 : 2)", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "/**/!Boolean()|| bar", + output: "/**/true|| bar", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!/**/Boolean()|| bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean/**/()|| bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(/**/)|| bar", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(!Boolean()|| bar)/**/", + output: "(true|| bar)/**/", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(!/**/Boolean()|| bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(!Boolean(/**/) || bar ? 1 : 2)", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(/**/Boolean()|| bar);", + output: "if(/**/false|| bar);", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean/**/()|| bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean(/**/)|| bar);", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean()|| bar/**/);", + output: "if(false|| bar/**/);", + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(Boolean/**/()|| bar ? 1 : 2)", + output: null, + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] } + ] }); From 9d501495e5a8bdd2e4e3627e162b69d512df7e7d Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Sat, 11 Jan 2020 17:07:27 -0500 Subject: [PATCH 03/11] Add default for schema option; renamed functions for clarity; pass fewer parameters to functions; check precedence in fixer --- lib/rules/no-extra-boolean-cast.js | 45 ++++++++-------- tests/lib/rules/no-extra-boolean-cast.js | 69 ++++++++++++++---------- 2 files changed, 65 insertions(+), 49 deletions(-) diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index 4200660cf9f..05056a896bb 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -30,7 +30,8 @@ module.exports = { type: "object", properties: { enforceForLogicalOperands: { - type: "boolean" + type: "boolean", + default: false } }, additionalProperties: false @@ -56,11 +57,11 @@ module.exports = { ]; /** - * Check if a node is inside a Boolean function or constructor. + * Check if a node is a Boolean function or constructor. * @param {ASTNode} node the node * @returns {boolean} If the node is in the context */ - function isInFunctionOrConstructorContext(node) { + function isFunctionOrConstructorContext(node) { return (node.type === "CallExpression" || node.type === "NewExpression") && node.callee.type === "Identifier" && node.callee.name === "Boolean"; @@ -73,8 +74,9 @@ module.exports = { * @param {ASTNode} node the node * @returns {boolean} if the node is a logical expression and option is enabled */ - function isInLogicalContext(node) { + function isLogicalContext(node) { return node.type === "LogicalExpression" && + (node.operator === "||" || node.operator === "&&") && (context.options.length && context.options[0].enforceForLogicalOperands === true); } @@ -83,36 +85,34 @@ module.exports = { /** * Check if a node is in a context where its value would be coerced to a boolean at runtime. * @param {ASTNode} node The node - * @param {ASTNode} parent Its parent * @returns {boolean} If it is in a boolean context */ - function isInBooleanContext(node, parent) { + function isBooleanContext(node) { // Boolean() and new Boolean() return ( - (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 && - node === parent.test) || + (BOOLEAN_NODE_TYPES.indexOf(node.parent.type) !== -1 && + node === node.parent.test) || // ! - (parent.type === "UnaryExpression" && - parent.operator === "!") + (node.parent.type === "UnaryExpression" && + node.parent.operator === "!") ); } /** - * Checks whether the node is in a context that should report an error + * Checks whether the node is a context that should report an error * Acts recursively if it is in a logical context * @param {ASTNode} node the node - * @param {ASTNode} parent the parent * @returns {boolean} If the node is in one of the flagged contexts */ - function isInFlaggedContext(node, parent) { - return isInBooleanContext(node, parent) || - isInFunctionOrConstructorContext(parent) || - (isInLogicalContext(parent) && + function isFlaggedContext(node) { + return isBooleanContext(node) || + isFunctionOrConstructorContext(node.parent) || + (isLogicalContext(node.parent) && // For nested logical statements - isInFlaggedContext(parent, parent.parent) + isFlaggedContext(node.parent, node.parent.parent) ); } @@ -129,8 +129,7 @@ module.exports = { return { UnaryExpression(node) { const ancestors = context.getAncestors(), - parent = ancestors.pop(), - grandparent = ancestors.pop(); + parent = ancestors.pop(); // Exit early if it's guaranteed not to match @@ -141,7 +140,7 @@ module.exports = { } - if (isInFlaggedContext(parent, grandparent)) { + if (isFlaggedContext(parent)) { context.report({ node: parent, messageId: "unexpectedNegation", @@ -159,6 +158,10 @@ module.exports = { prefix = " "; } + if (astUtils.getPrecedence(node.argument) < astUtils.getPrecedence(parent)) { + return fixer.replaceText(parent, `(${prefix + sourceCode.getText(node.argument)})`); + } + return fixer.replaceText(parent, prefix + sourceCode.getText(node.argument)); } }); @@ -171,7 +174,7 @@ module.exports = { return; } - if (isInFlaggedContext(node, parent)) { + if (isFlaggedContext(node)) { context.report({ node, messageId: "unexpectedCall", diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index 4fd618e46d3..783fa06c399 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -23,39 +23,39 @@ ruleTester.run("no-extra-boolean-cast", rule, { valid: [ - "var foo = !!bar;", - "function foo() { return !!bar; }", - "var foo = bar() ? !!baz : !!bat", - "for(!!foo;;) {}", - "for(;; !!foo) {}", - "var foo = Boolean(bar);", - "function foo() { return Boolean(bar); }", - "var foo = bar() ? Boolean(baz) : Boolean(bat)", - "for(Boolean(foo);;) {}", - "for(;; Boolean(foo)) {}", - "if (new Boolean(foo)) {}", - "var foo = bar || !!baz", - "var foo = bar && !!baz", - "var foo = bar || (baz && !!bat)", - "function foo() { return (!!bar || baz); }", - "var foo = bar() ? (!!baz && bat) : (!!bat && qux)", - "for(!!(foo && bar);;) {}", - "for(;; !!(foo || bar)) {}", - "var foo = Boolean(bar) || baz;", - "var foo = bar || Boolean(baz);", - "var foo = Boolean(bar) || Boolean(baz);", - "function foo() { return (Boolean(bar) || baz); }", - "var foo = bar() ? Boolean(baz) || bat : Boolean(bat)", - "for(Boolean(foo) || bar;;) {}", - "for(;; Boolean(foo) || bar) {}", - "if (new Boolean(foo) || bar) {}" + /* + * "var foo = !!bar;", + * "function foo() { return !!bar; }", + * "var foo = bar() ? !!baz : !!bat", + * "for(!!foo;;) {}", + * "for(;; !!foo) {}", + * "var foo = Boolean(bar);", + * "function foo() { return Boolean(bar); }", + * "var foo = bar() ? Boolean(baz) : Boolean(bat)", + * "for(Boolean(foo);;) {}", + * "for(;; Boolean(foo)) {}", + * "if (new Boolean(foo)) {}", + * "var foo = bar || !!baz", + * "var foo = bar && !!baz", + * "var foo = bar || (baz && !!bat)", + * "function foo() { return (!!bar || baz); }", + * "var foo = bar() ? (!!baz && bat) : (!!bat && qux)", + * "for(!!(foo && bar);;) {}", + * "for(;; !!(foo || bar)) {}", + * "var foo = Boolean(bar) || baz;", + * "var foo = bar || Boolean(baz);", + * "var foo = Boolean(bar) || Boolean(baz);", + * "function foo() { return (Boolean(bar) || baz); }", + * "var foo = bar() ? Boolean(baz) || bat : Boolean(bat)", + * "for(Boolean(foo) || bar;;) {}", + * "for(;; Boolean(foo) || bar) {}", + * "if (new Boolean(foo) || bar) {}" + */ ], invalid: [ - - { code: "if (!!foo) {}", output: "if (foo) {}", @@ -1279,6 +1279,19 @@ ruleTester.run("no-extra-boolean-cast", rule, { messageId: "unexpectedCall", type: "CallExpression" }] + }, + + { + code: "if (!!(foo && bar) || baz){}", + output: "if ((foo && bar) || baz){}", + + options: [{ enforceForLogicalOperands: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 5, + endColumn: 19 + }] } ] From 00639b4ec0b5693a0cb7fa0c90909bec438d4bf1 Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Fri, 17 Jan 2020 16:23:14 -0500 Subject: [PATCH 04/11] Removed extra variable from function; fixed function name; fixed precedence check; added additional example --- docs/rules/no-extra-boolean-cast.md | 6 +- lib/rules/no-extra-boolean-cast.js | 14 +-- tests/lib/rules/no-extra-boolean-cast.js | 111 ++++++++++++++++------- 3 files changed, 90 insertions(+), 41 deletions(-) diff --git a/docs/rules/no-extra-boolean-cast.md b/docs/rules/no-extra-boolean-cast.md index 3dcfcbef62f..9532f082ad0 100644 --- a/docs/rules/no-extra-boolean-cast.md +++ b/docs/rules/no-extra-boolean-cast.md @@ -86,6 +86,10 @@ if (!!foo || bar) { //... } +if (!!foo && bar) { + //... +} + if ((!!foo || bar) && baz) { //... } @@ -93,7 +97,7 @@ if ((!!foo || bar) && baz) { var foo = new Boolean(!!bar || baz) -if (Boolean(foo || bar)) { +if (Boolean(foo) || bar) { // ... } ``` diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index 05056a896bb..d0ae5ddeb4c 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -61,7 +61,7 @@ module.exports = { * @param {ASTNode} node the node * @returns {boolean} If the node is in the context */ - function isFunctionOrConstructorContext(node) { + function isBooleanFunctionOrConstructorCall(node) { return (node.type === "CallExpression" || node.type === "NewExpression") && node.callee.type === "Identifier" && node.callee.name === "Boolean"; @@ -87,10 +87,13 @@ module.exports = { * @param {ASTNode} node The node * @returns {boolean} If it is in a boolean context */ - function isBooleanContext(node) { + function isInBooleanContext(node) { // Boolean() and new Boolean() return ( + (isBooleanFunctionOrConstructorCall(node.parent) && + node === node.parent.arguments[0]) || + (BOOLEAN_NODE_TYPES.indexOf(node.parent.type) !== -1 && node === node.parent.test) || @@ -107,12 +110,11 @@ module.exports = { * @returns {boolean} If the node is in one of the flagged contexts */ function isFlaggedContext(node) { - return isBooleanContext(node) || - isFunctionOrConstructorContext(node.parent) || + return isInBooleanContext(node) || (isLogicalContext(node.parent) && // For nested logical statements - isFlaggedContext(node.parent, node.parent.parent) + isFlaggedContext(node.parent) ); } @@ -158,7 +160,7 @@ module.exports = { prefix = " "; } - if (astUtils.getPrecedence(node.argument) < astUtils.getPrecedence(parent)) { + if (astUtils.getPrecedence(node.argument) < astUtils.getPrecedence(parent.parent)) { return fixer.replaceText(parent, `(${prefix + sourceCode.getText(node.argument)})`); } diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index 783fa06c399..a0a6d2e18ab 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -21,36 +21,79 @@ const ruleTester = new RuleTester(); ruleTester.run("no-extra-boolean-cast", rule, { valid: [ - - - /* - * "var foo = !!bar;", - * "function foo() { return !!bar; }", - * "var foo = bar() ? !!baz : !!bat", - * "for(!!foo;;) {}", - * "for(;; !!foo) {}", - * "var foo = Boolean(bar);", - * "function foo() { return Boolean(bar); }", - * "var foo = bar() ? Boolean(baz) : Boolean(bat)", - * "for(Boolean(foo);;) {}", - * "for(;; Boolean(foo)) {}", - * "if (new Boolean(foo)) {}", - * "var foo = bar || !!baz", - * "var foo = bar && !!baz", - * "var foo = bar || (baz && !!bat)", - * "function foo() { return (!!bar || baz); }", - * "var foo = bar() ? (!!baz && bat) : (!!bat && qux)", - * "for(!!(foo && bar);;) {}", - * "for(;; !!(foo || bar)) {}", - * "var foo = Boolean(bar) || baz;", - * "var foo = bar || Boolean(baz);", - * "var foo = Boolean(bar) || Boolean(baz);", - * "function foo() { return (Boolean(bar) || baz); }", - * "var foo = bar() ? Boolean(baz) || bat : Boolean(bat)", - * "for(Boolean(foo) || bar;;) {}", - * "for(;; Boolean(foo) || bar) {}", - * "if (new Boolean(foo) || bar) {}" - */ + "Boolean(bar, !!baz);", + "var foo = !!bar;", + "function foo() { return !!bar; }", + "var foo = bar() ? !!baz : !!bat", + "for(!!foo;;) {}", + "for(;; !!foo) {}", + "var foo = Boolean(bar);", + "function foo() { return Boolean(bar); }", + "var foo = bar() ? Boolean(baz) : Boolean(bat)", + "for(Boolean(foo);;) {}", + "for(;; Boolean(foo)) {}", + "if (new Boolean(foo)) {}", + { + code: "var foo = bar || !!baz", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "var foo = bar && !!baz", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "var foo = bar || (baz && !!bat)", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "function foo() { return (!!bar || baz); }", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "var foo = bar() ? (!!baz && bat) : (!!bat && qux)", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "for(!!(foo && bar);;) {}", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "for(;; !!(foo || bar)) {}", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "var foo = Boolean(bar) || baz;", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "var foo = bar || Boolean(baz);", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "var foo = Boolean(bar) || Boolean(baz);", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "function foo() { return (Boolean(bar) || baz); }", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "var foo = bar() ? Boolean(baz) || bat : Boolean(bat)", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "for(Boolean(foo) || bar;;) {}", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "for(;; Boolean(foo) || bar) {}", + options: [{ enforceForLogicalOperands: true }] + }, + { + code: "if (new Boolean(foo) || bar) {}", + options: [{ enforceForLogicalOperands: true }] + }, + "if (!!foo || bar) {}" ], @@ -1282,15 +1325,15 @@ ruleTester.run("no-extra-boolean-cast", rule, { }, { - code: "if (!!(foo && bar) || baz){}", - output: "if ((foo && bar) || baz){}", + code: "if (a && !!(b ? c : d)){}", + output: "if (a && (b ? c : d)){}", options: [{ enforceForLogicalOperands: true }], errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression", - column: 5, - endColumn: 19 + column: 10, + endColumn: 23 }] } From f086905e0c85e709c5324614daae84e6dc3a6a82 Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Sat, 25 Jan 2020 08:41:23 -0500 Subject: [PATCH 05/11] Updated documentation --- docs/rules/no-extra-boolean-cast.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/rules/no-extra-boolean-cast.md b/docs/rules/no-extra-boolean-cast.md index 9532f082ad0..4056b6e8345 100644 --- a/docs/rules/no-extra-boolean-cast.md +++ b/docs/rules/no-extra-boolean-cast.md @@ -73,7 +73,7 @@ var foo = bar ? !!baz : !!bat; This rule has an object option: -* `"enforceForLogicalOperands"` when set to `true`, checks whether the extra boolean is contained within a logical expression. Default is `false`, meaning that this rule by default does not warn about extra booleans inside logical expression. +* `"enforceForLogicalOperands"` when set to `true`, checks whether the extra boolean cast is contained within a logical expression. Default is `false`, meaning that this rule by default does not warn about extra booleans cast inside logical expression. ### enforceForLogicalOperands @@ -86,7 +86,7 @@ if (!!foo || bar) { //... } -if (!!foo && bar) { +while (!!foo && bar) { //... } @@ -111,6 +111,10 @@ if (foo || bar) { //... } +while (foo && bar) { + //... +} + if ((foo || bar) && baz) { //... } @@ -121,4 +125,6 @@ var foo = new Boolean(bar || baz) if (foo || bar) { // ... } + +var foo = !!bar || baz; ``` From 917217c5e900d59823bbc34a704b35cb9c49ebaa Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Sat, 25 Jan 2020 08:43:36 -0500 Subject: [PATCH 06/11] Removed outdated legacy code method --- lib/rules/no-extra-boolean-cast.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index d0ae5ddeb4c..fa68b144110 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -130,8 +130,7 @@ module.exports = { return { UnaryExpression(node) { - const ancestors = context.getAncestors(), - parent = ancestors.pop(); + const parent = node.parent; // Exit early if it's guaranteed not to match From a0926cc2c04dc33e60344ce776ee6daa2117985f Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Sat, 25 Jan 2020 08:55:21 -0500 Subject: [PATCH 07/11] Added additional tests --- tests/lib/rules/no-extra-boolean-cast.js | 26 +++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index a0a6d2e18ab..06cdd1c21d3 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -93,7 +93,15 @@ ruleTester.run("no-extra-boolean-cast", rule, { code: "if (new Boolean(foo) || bar) {}", options: [{ enforceForLogicalOperands: true }] }, - "if (!!foo || bar) {}" + "if (!!foo || bar) {}", + { + code: "if (!!foo || bar) {}", + options: [{}] + }, + { + code: "if (!!foo || bar) {}", + options: [{ enforceForLogicalOperands: false }] + } ], @@ -341,6 +349,22 @@ ruleTester.run("no-extra-boolean-cast", rule, { type: "CallExpression" }] }, + { + code: "Boolean(Boolean(foo))", + output: "Boolean(foo)", + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "Boolean(!!foo, bar)", + output: "Boolean(foo, bar)", + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, // Adjacent tokens tests From 47d82db9ab75d09bd2e8ed8a7c35d0c4f7cf866d Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Sat, 25 Jan 2020 09:01:32 -0500 Subject: [PATCH 08/11] Renamed function --- lib/rules/no-extra-boolean-cast.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index fa68b144110..134388c56d0 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -109,12 +109,12 @@ module.exports = { * @param {ASTNode} node the node * @returns {boolean} If the node is in one of the flagged contexts */ - function isFlaggedContext(node) { + function isInFlaggedContext(node) { return isInBooleanContext(node) || (isLogicalContext(node.parent) && // For nested logical statements - isFlaggedContext(node.parent) + isInFlaggedContext(node.parent) ); } @@ -141,7 +141,7 @@ module.exports = { } - if (isFlaggedContext(parent)) { + if (isInFlaggedContext(parent)) { context.report({ node: parent, messageId: "unexpectedNegation", @@ -175,7 +175,7 @@ module.exports = { return; } - if (isFlaggedContext(node)) { + if (isInFlaggedContext(node)) { context.report({ node, messageId: "unexpectedCall", From a34483102de560b37fc638ee819754b4936f3ed4 Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Sun, 26 Jan 2020 14:39:52 -0500 Subject: [PATCH 09/11] Added additional test; updated documentation --- docs/rules/no-extra-boolean-cast.md | 14 ++++---------- tests/lib/rules/no-extra-boolean-cast.js | 6 ++++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/rules/no-extra-boolean-cast.md b/docs/rules/no-extra-boolean-cast.md index 4056b6e8345..09baa4314d6 100644 --- a/docs/rules/no-extra-boolean-cast.md +++ b/docs/rules/no-extra-boolean-cast.md @@ -73,7 +73,7 @@ var foo = bar ? !!baz : !!bat; This rule has an object option: -* `"enforceForLogicalOperands"` when set to `true`, checks whether the extra boolean cast is contained within a logical expression. Default is `false`, meaning that this rule by default does not warn about extra booleans cast inside logical expression. +* `"enforceForLogicalOperands"` when set to `true`, in addition to checking default contexts, checks whether the extra boolean cast is contained within a logical expression. Default is `false`, meaning that this rule by default does not warn about extra booleans cast inside logical expression. ### enforceForLogicalOperands @@ -94,12 +94,9 @@ if ((!!foo || bar) && baz) { //... } -var foo = new Boolean(!!bar || baz) - +foo && Boolean(bar) ? baz : bat -if (Boolean(foo) || bar) { - // ... -} +var foo = new Boolean(!!bar || baz) ``` Examples of **correct** code for this rule with `"enforceForLogicalOperands"` option set to `true`: @@ -121,10 +118,7 @@ if ((foo || bar) && baz) { var foo = new Boolean(bar || baz) - -if (foo || bar) { - // ... -} +foo && bar ? baz : bat var foo = !!bar || baz; ``` diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index 06cdd1c21d3..79738f4df7e 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -101,9 +101,11 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!foo || bar) {}", options: [{ enforceForLogicalOperands: false }] + }, + { + code: "if ((!!foo || bar) === baz) {}", + options: [{ enforceForLogicalOperands: true }] } - - ], invalid: [ From 1af8aac5ca16be39b98c7f936980cf9dcdc18cb1 Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Mon, 27 Jan 2020 08:02:05 -0500 Subject: [PATCH 10/11] Switched docs examples order; fixed comments in rule; added test for inserting space --- docs/rules/no-extra-boolean-cast.md | 4 ++-- lib/rules/no-extra-boolean-cast.js | 10 ++++------ tests/lib/rules/no-extra-boolean-cast.js | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/rules/no-extra-boolean-cast.md b/docs/rules/no-extra-boolean-cast.md index 09baa4314d6..5c7d2007fe8 100644 --- a/docs/rules/no-extra-boolean-cast.md +++ b/docs/rules/no-extra-boolean-cast.md @@ -116,9 +116,9 @@ if ((foo || bar) && baz) { //... } -var foo = new Boolean(bar || baz) - foo && bar ? baz : bat +var foo = new Boolean(bar || baz) + var foo = !!bar || baz; ``` diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index 134388c56d0..8ccd0bce906 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -59,14 +59,14 @@ module.exports = { /** * Check if a node is a Boolean function or constructor. * @param {ASTNode} node the node - * @returns {boolean} If the node is in the context + * @returns {boolean} If the node is Boolean function or constructor */ function isBooleanFunctionOrConstructorCall(node) { + + // Boolean() and new Boolean() return (node.type === "CallExpression" || node.type === "NewExpression") && node.callee.type === "Identifier" && node.callee.name === "Boolean"; - - } /** @@ -88,8 +88,6 @@ module.exports = { * @returns {boolean} If it is in a boolean context */ function isInBooleanContext(node) { - - // Boolean() and new Boolean() return ( (isBooleanFunctionOrConstructorCall(node.parent) && node === node.parent.arguments[0]) || @@ -160,7 +158,7 @@ module.exports = { } if (astUtils.getPrecedence(node.argument) < astUtils.getPrecedence(parent.parent)) { - return fixer.replaceText(parent, `(${prefix + sourceCode.getText(node.argument)})`); + return fixer.replaceText(parent, `(${sourceCode.getText(node.argument)})`); } return fixer.replaceText(parent, prefix + sourceCode.getText(node.argument)); diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index 79738f4df7e..a58fe76286c 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -1349,7 +1349,6 @@ ruleTester.run("no-extra-boolean-cast", rule, { type: "CallExpression" }] }, - { code: "if (a && !!(b ? c : d)){}", output: "if (a && (b ? c : d)){}", @@ -1361,7 +1360,18 @@ ruleTester.run("no-extra-boolean-cast", rule, { column: 10, endColumn: 23 }] + }, + { + code: "function *foo() { yield!!a || d ? b : c }", + output: "function *foo() { yield a || d ? b : c }", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 24, + endColumn: 27 + }] } - ] }); From fd1ed9070f015cfbe8159b13efcc043005d2cdf3 Mon Sep 17 00:00:00 2001 From: jmoore914 Date: Sun, 9 Feb 2020 16:16:53 -0500 Subject: [PATCH 11/11] Fixed accidentally edited lines --- tests/lib/rules/no-extra-boolean-cast.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index a58fe76286c..08605d15aa1 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -1,6 +1,6 @@ /** - *@fileoverview Tests for no-extra-boolean-cast rule. - *@author Brandon Mills + * @fileoverview Tests for no-extra-boolean-cast rule. + * @author Brandon Mills */ "use strict";