Skip to content

Commit 7c8bbe0

Browse files
samrae7platinumazure
authored andcommitted
Update: enforceForOrderingRelations no-unsafe-negation (fixes #12163) (#12414)
* Update: add option to no-unsafe-negation (fixes #12163) * Chore: undo autoformatting + rename function * Update: code review changes - typos - clarity of docs - extra test cases * Fix: fix failing tests * Update: code review changes - add backticks * Chore: remove comma
1 parent 349ed67 commit 7c8bbe0

3 files changed

Lines changed: 141 additions & 12 deletions

File tree

docs/rules/no-unsafe-negation.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@ Just as developers might type `-a + b` when they mean `-(a + b)` for the negativ
44

55
## Rule Details
66

7-
This rule disallows negating the left operand of [Relational Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Expressions_and_Operators#Relational_operators).
7+
This rule disallows negating the left operand of the following relational operators:
88

9-
Relational Operators are:
10-
11-
- `in` operator.
12-
- `instanceof` operator.
9+
- [`in` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in).
10+
- [`instanceof` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof).
1311

1412
Examples of **incorrect** code for this rule:
1513

@@ -72,6 +70,38 @@ if (!(foo) in object) {
7270
}
7371
```
7472

73+
## Options
74+
75+
This rule has an object option:
76+
77+
- `"enforceForOrderingRelations": false` (default) allows negation of the left-hand side of ordering relational operators (`<`, `>`, `<=`, `>=`)
78+
- `"enforceForOrderingRelations": true` disallows negation of the left-hand side of ordering relational operators
79+
80+
### enforceForOrderingRelations
81+
82+
With this option set to `true` the rule is additionally enforced for:
83+
84+
- `<` operator.
85+
- `>` operator.
86+
- `<=` operator.
87+
- `>=` operator.
88+
89+
The purpose is to avoid expressions such as `! a < b` (which is equivalent to `(a ? 0 : 1) < b`) when what is really intended is `!(a < b)`.
90+
91+
Examples of additional **incorrect** code for this rule with the `{ "enforceForOrderingRelations": true }` option:
92+
93+
```js
94+
/*eslint no-unsafe-negation: ["error", { "enforceForOrderingRelations": true }]*/
95+
96+
if (! a < b) {}
97+
98+
while (! a > b) {}
99+
100+
foo = ! a <= b;
101+
102+
foo = ! a >= b;
103+
```
104+
75105
## When Not To Use It
76106

77107
If you don't want to notify unsafe logical negations, then it's safe to disable this rule.

lib/rules/no-unsafe-negation.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,23 @@ const astUtils = require("./utils/ast-utils");
1616
//------------------------------------------------------------------------------
1717

1818
/**
19-
* Checks whether the given operator is a relational operator or not.
19+
* Checks whether the given operator is `in` or `instanceof`
2020
* @param {string} op The operator type to check.
21-
* @returns {boolean} `true` if the operator is a relational operator.
21+
* @returns {boolean} `true` if the operator is `in` or `instanceof`
2222
*/
23-
function isRelationalOperator(op) {
23+
function isInOrInstanceOfOperator(op) {
2424
return op === "in" || op === "instanceof";
2525
}
2626

27+
/**
28+
* Checks whether the given operator is an ordering relational operator or not.
29+
* @param {string} op The operator type to check.
30+
* @returns {boolean} `true` if the operator is an ordering relational operator.
31+
*/
32+
function isOrderingRelationalOperator(op) {
33+
return op === "<" || op === ">" || op === ">=" || op === "<=";
34+
}
35+
2736
/**
2837
* Checks whether the given node is a logical negation expression or not.
2938
* @param {ASTNode} node The node to check.
@@ -48,7 +57,18 @@ module.exports = {
4857
url: "https://eslint.org/docs/rules/no-unsafe-negation"
4958
},
5059

51-
schema: [],
60+
schema: [
61+
{
62+
type: "object",
63+
properties: {
64+
enforceForOrderingRelations: {
65+
type: "boolean",
66+
default: false
67+
}
68+
},
69+
additionalProperties: false
70+
}
71+
],
5272
fixable: null,
5373
messages: {
5474
unexpected: "Unexpected negating the left operand of '{{operator}}' operator."
@@ -57,10 +77,15 @@ module.exports = {
5777

5878
create(context) {
5979
const sourceCode = context.getSourceCode();
80+
const options = context.options[0] || {};
81+
const enforceForOrderingRelations = options.enforceForOrderingRelations === true;
6082

6183
return {
6284
BinaryExpression(node) {
63-
if (isRelationalOperator(node.operator) &&
85+
const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(node.operator);
86+
87+
if (
88+
(isInOrInstanceOfOperator(node.operator) || orderingRelationRuleApplies) &&
6489
isNegation(node.left) &&
6590
!astUtils.isParenthesised(sourceCode, node.left)
6691
) {

tests/lib/rules/no-unsafe-negation.js

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,26 @@ const rule = require("../../../lib/rules/no-unsafe-negation"),
1818

1919
const ruleTester = new RuleTester();
2020
const unexpectedInError = { messageId: "unexpected", data: { operator: "in" } };
21-
const unexpectedInstanceofError = { messageId: "unexpected", data: { operator: "instanceof" } };
21+
const unexpectedInstanceofError = {
22+
messageId: "unexpected",
23+
data: { operator: "instanceof" }
24+
};
25+
const unexpectedLessThanOperatorError = {
26+
messageId: "unexpected",
27+
data: { operator: "<" }
28+
};
29+
const unexpectedMoreThanOperatorError = {
30+
messageId: "unexpected",
31+
data: { operator: ">" }
32+
};
33+
const unexpectedMoreThanOrEqualOperatorError = {
34+
messageId: "unexpected",
35+
data: { operator: ">=" }
36+
};
37+
const unexpectedLessThanOrEqualOperatorError = {
38+
messageId: "unexpected",
39+
data: { operator: "<=" }
40+
};
2241

2342
ruleTester.run("no-unsafe-negation", rule, {
2443
valid: [
@@ -29,7 +48,37 @@ ruleTester.run("no-unsafe-negation", rule, {
2948
"a instanceof b",
3049
"a instanceof b === false",
3150
"!(a instanceof b)",
32-
"(!a) instanceof b"
51+
"(!a) instanceof b",
52+
53+
// tests cases for enforceForOrderingRelations option:
54+
"if (! a < b) {}",
55+
"while (! a > b) {}",
56+
"foo = ! a <= b;",
57+
"foo = ! a >= b;",
58+
{
59+
code: "! a <= b",
60+
options: [{}]
61+
},
62+
{
63+
code: "foo = ! a >= b;",
64+
options: [{ enforceForOrderingRelations: false }]
65+
},
66+
{
67+
code: "foo = (!a) >= b;",
68+
options: [{ enforceForOrderingRelations: true }]
69+
},
70+
{
71+
code: "a <= b",
72+
options: [{ enforceForOrderingRelations: true }]
73+
},
74+
{
75+
code: "!(a < b)",
76+
options: [{ enforceForOrderingRelations: true }]
77+
},
78+
{
79+
code: "foo = a > b;",
80+
options: [{ enforceForOrderingRelations: true }]
81+
}
3382
],
3483
invalid: [
3584
{
@@ -55,6 +104,31 @@ ruleTester.run("no-unsafe-negation", rule, {
55104
{
56105
code: "!(a) instanceof b",
57106
errors: [unexpectedInstanceofError]
107+
},
108+
{
109+
code: "if (! a < b) {}",
110+
options: [{ enforceForOrderingRelations: true }],
111+
errors: [unexpectedLessThanOperatorError]
112+
},
113+
{
114+
code: "while (! a > b) {}",
115+
options: [{ enforceForOrderingRelations: true }],
116+
errors: [unexpectedMoreThanOperatorError]
117+
},
118+
{
119+
code: "foo = ! a <= b;",
120+
options: [{ enforceForOrderingRelations: true }],
121+
errors: [unexpectedLessThanOrEqualOperatorError]
122+
},
123+
{
124+
code: "foo = ! a >= b;",
125+
options: [{ enforceForOrderingRelations: true }],
126+
errors: [unexpectedMoreThanOrEqualOperatorError]
127+
},
128+
{
129+
code: "! a <= b",
130+
options: [{ enforceForOrderingRelations: true }],
131+
errors: [unexpectedLessThanOrEqualOperatorError]
58132
}
59133
]
60134
});

0 commit comments

Comments
 (0)