Skip to content
This repository was archived by the owner on Oct 3, 2024. It is now read-only.

Commit d09d63d

Browse files
add rule no-inverted-boolean-check (#74)
1 parent 25540b6 commit d09d63d

File tree

7 files changed

+211
-0
lines changed

7 files changed

+211
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Code Smells, or maintainability issues, are raised for places of code which migh
2525
* Cognitive Complexity of functions should not be too high ([`cognitive-complexity`])
2626
* Two branches in a conditional structure should not have exactly the same implementation ([`no-duplicated-branches`])
2727
* Functions should not have identical implementations ([`no-identical-functions`])
28+
* Boolean checks should not be inverted ([`no-inverted-boolean-check`])
2829
* Boolean literals should not be redundant ([`no-redundant-boolean`])
2930
* "switch" statements should have at least 3 "case" clauses ([`no-small-switch`])
3031
* Local variables should not be declared and then immediately returned or thrown ([`prefer-immediate-return`]) (:wrench: *fixable*)
@@ -39,6 +40,7 @@ Code Smells, or maintainability issues, are raised for places of code which migh
3940
[`no-identical-conditions`]: ./docs/rules/no-identical-conditions.md
4041
[`no-identical-expressions`]: ./docs/rules/no-identical-expressions.md
4142
[`no-identical-functions`]: ./docs/rules/no-identical-functions.md
43+
[`no-inverted-boolean-check`]: ./docs/rules/no-inverted-boolean-check.md
4244
[`no-one-iteration-loop`]: ./docs/rules/no-one-iteration-loop.md
4345
[`no-redundant-boolean`]: ./docs/rules/no-redundant-boolean.md
4446
[`no-small-switch`]: ./docs/rules/no-small-switch.md
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# no-inverted-boolean-check
2+
3+
It is needlessly complex to invert the result of a boolean comparison. The opposite comparison should be made instead.
4+
5+
## Noncompliant Code Example
6+
7+
```javascript
8+
if (!(a === 2)) { ... } // Noncompliant
9+
```
10+
11+
## Compliant Solution
12+
13+
```javascript
14+
if (a !== 2) { ... }
15+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
src/angular.js/src/ng/sniffer.js: 61
2+
src/jest/packages/expect/src/matchers.js: 523
3+
src/react-native/Libraries/Renderer/ReactFabric-dev.js: 1447,5567,5796,8656,8743
4+
src/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js: 1602,5903,6132,8992,9079
5+
src/three.js/src/math/Interpolant.js: 57,95

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const sonarjsRules: [string, Linter.RuleLevel][] = [
2828
["no-identical-conditions", "error"],
2929
["no-identical-functions", "error"],
3030
["no-identical-expressions", "error"],
31+
["no-inverted-boolean-check", "error"],
3132
["no-one-iteration-loop", "error"],
3233
["no-redundant-boolean", "error"],
3334
["no-small-switch", "error"],
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* eslint-plugin-sonarjs
3+
* Copyright (C) 2018 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
// https://jira.sonarsource.com/browse/RSPEC-1940
21+
22+
import { Rule } from "eslint";
23+
import { Node, UnaryExpression } from "estree";
24+
import { isBinaryExpression } from "../utils/nodes";
25+
26+
const MESSAGE = "Use the opposite operator ({{invertedOperator}}) instead.";
27+
28+
const invertedOperators: { [operator: string]: string } = {
29+
"==": "!=",
30+
"!=": "==",
31+
"===": "!==",
32+
"!==": "===",
33+
">": "<=",
34+
"<": ">=",
35+
">=": "<",
36+
"<=": ">",
37+
};
38+
39+
const rule: Rule.RuleModule = {
40+
create(context: Rule.RuleContext) {
41+
return { UnaryExpression: (node: Node) => visitUnaryExpression(node as UnaryExpression, context) };
42+
},
43+
};
44+
45+
function visitUnaryExpression(unaryExpression: UnaryExpression, context: Rule.RuleContext) {
46+
if (unaryExpression.operator === "!" && isBinaryExpression(unaryExpression.argument)) {
47+
const condition = unaryExpression.argument;
48+
const invertedOperator = invertedOperators[condition.operator];
49+
if (invertedOperator) {
50+
const left = context.getSourceCode().getText(condition.left);
51+
const right = context.getSourceCode().getText(condition.right);
52+
context.report({
53+
message: MESSAGE,
54+
data: { invertedOperator },
55+
node: unaryExpression,
56+
fix: fixer => fixer.replaceText(unaryExpression, `${left} ${invertedOperator} ${right}`),
57+
});
58+
}
59+
}
60+
}
61+
62+
export = rule;

src/utils/nodes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export function isAssignmentExpression(node: estree.Node | undefined): node is e
3333
return node !== undefined && node.type === "AssignmentExpression";
3434
}
3535

36+
export function isBinaryExpression(node: estree.Node | undefined): node is estree.BinaryExpression {
37+
return node !== undefined && node.type === "BinaryExpression";
38+
}
39+
3640
export function isBlockStatement(node: estree.Node | undefined): node is estree.BlockStatement {
3741
return node !== undefined && node.type === "BlockStatement";
3842
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* eslint-plugin-sonarjs
3+
* Copyright (C) 2018 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
import { RuleTester } from "eslint";
21+
22+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
23+
import rule = require("../../src/rules/no-inverted-boolean-check");
24+
25+
ruleTester.run("no-inverted-boolean-check", rule, {
26+
valid: [
27+
{
28+
code: `if (!x) {}`,
29+
},
30+
{
31+
code: `if (x == 1) {}`,
32+
},
33+
{
34+
code: `if (!(x + 1)) {}`,
35+
},
36+
{
37+
code: `if (+(x == 1)) {}`,
38+
},
39+
{
40+
code: `!x ? 2 : 3`,
41+
},
42+
],
43+
invalid: [
44+
// `==` => `!=`
45+
{
46+
code: `if (!(x == 1)) {}`,
47+
errors: [
48+
{
49+
message: message("!="),
50+
line: 1,
51+
endLine: 1,
52+
column: 5,
53+
endColumn: 14,
54+
},
55+
],
56+
output: `if (x != 1) {}`,
57+
},
58+
// `!=` => `==`
59+
{
60+
code: `if (!(x != 1)) {}`,
61+
errors: [message("==")],
62+
output: `if (x == 1) {}`,
63+
},
64+
// `===` => `!==`
65+
{
66+
code: `if (!(x === 1)) {}`,
67+
errors: [message("!==")],
68+
output: `if (x !== 1) {}`,
69+
},
70+
// `!==` => `===`
71+
{
72+
code: `if (!(x !== 1)) {}`,
73+
errors: [message("===")],
74+
output: `if (x === 1) {}`,
75+
},
76+
// `>` => `<=`
77+
{
78+
code: `if (!(x > 1)) {}`,
79+
errors: [message("<=")],
80+
output: `if (x <= 1) {}`,
81+
},
82+
// `<` => `>=`
83+
{
84+
code: `if (!(x < 1)) {}`,
85+
errors: [message(">=")],
86+
output: `if (x >= 1) {}`,
87+
},
88+
// `>=` => `<`
89+
{
90+
code: `if (!(x >= 1)) {}`,
91+
errors: [message("<")],
92+
output: `if (x < 1) {}`,
93+
},
94+
// `<=` => `>`
95+
{
96+
code: `if (!(x <= 1)) {}`,
97+
errors: [message(">")],
98+
output: `if (x > 1) {}`,
99+
},
100+
// ternary operator
101+
{
102+
code: `!(x != 1) ? 1 : 2`,
103+
errors: [message("==")],
104+
output: `x == 1 ? 1 : 2`,
105+
},
106+
// not conditional
107+
{
108+
code: `foo(!(x === 1))`,
109+
errors: [message("!==")],
110+
output: `foo(x !== 1)`,
111+
},
112+
{
113+
code: `let foo = !(x <= 4)`,
114+
errors: [message(">")],
115+
output: `let foo = x > 4`,
116+
},
117+
],
118+
});
119+
120+
function message(oppositeOperator: string) {
121+
return `Use the opposite operator (${oppositeOperator}) instead.`;
122+
}

0 commit comments

Comments
 (0)