Skip to content

Commit 8957a03

Browse files
authoredMay 24, 2024··
Add no-negation-in-equality-check rule (#2353)
1 parent 3a282ac commit 8957a03

6 files changed

+453
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Disallow negated expression in equality check
2+
3+
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs).
4+
5+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
6+
7+
<!-- end auto-generated rule header -->
8+
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
9+
10+
Using a negated expression in equality check is most likely a mistake.
11+
12+
## Fail
13+
14+
```js
15+
if (!foo === bar) {}
16+
```
17+
18+
```js
19+
if (!foo !== bar) {}
20+
```
21+
22+
## Pass
23+
24+
```js
25+
if (foo !== bar) {}
26+
```
27+
28+
```js
29+
if (!(foo === bar)) {}
30+
```

‎readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
145145
| [no-lonely-if](docs/rules/no-lonely-if.md) | Disallow `if` statements as the only statement in `if` blocks without `else`. || 🔧 | |
146146
| [no-magic-array-flat-depth](docs/rules/no-magic-array-flat-depth.md) | Disallow a magic number as the `depth` argument in `Array#flat(…).` || | |
147147
| [no-negated-condition](docs/rules/no-negated-condition.md) | Disallow negated conditions. || 🔧 | |
148+
| [no-negation-in-equality-check](docs/rules/no-negation-in-equality-check.md) | Disallow negated expression in equality check. || | 💡 |
148149
| [no-nested-ternary](docs/rules/no-nested-ternary.md) | Disallow nested ternary expressions. || 🔧 | |
149150
| [no-new-array](docs/rules/no-new-array.md) | Disallow `new Array()`. || 🔧 | 💡 |
150151
| [no-new-buffer](docs/rules/no-new-buffer.md) | Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. || 🔧 | 💡 |
+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
'use strict';
2+
const {
3+
fixSpaceAroundKeyword,
4+
addParenthesizesToReturnOrThrowExpression,
5+
} = require('./fix/index.js');
6+
const {
7+
needsSemicolon,
8+
isParenthesized,
9+
isOnSameLine,
10+
} = require('./utils/index.js');
11+
12+
const MESSAGE_ID_ERROR = 'no-negation-in-equality-check/error';
13+
const MESSAGE_ID_SUGGESTION = 'no-negation-in-equality-check/suggestion';
14+
const messages = {
15+
[MESSAGE_ID_ERROR]: 'Negated expression in not allowed in equality check.',
16+
[MESSAGE_ID_SUGGESTION]: 'Switch to \'{{operator}}\' check.',
17+
};
18+
19+
const EQUALITY_OPERATORS = new Set([
20+
'===',
21+
'!==',
22+
'==',
23+
'!=',
24+
]);
25+
26+
const isEqualityCheck = node => node.type === 'BinaryExpression' && EQUALITY_OPERATORS.has(node.operator);
27+
const isNegatedExpression = node => node.type === 'UnaryExpression' && node.prefix && node.operator === '!';
28+
29+
/** @param {import('eslint').Rule.RuleContext} context */
30+
const create = context => ({
31+
BinaryExpression(binaryExpression) {
32+
const {operator, left} = binaryExpression;
33+
34+
if (
35+
!isEqualityCheck(binaryExpression)
36+
|| !isNegatedExpression(left)
37+
) {
38+
return;
39+
}
40+
41+
const {sourceCode} = context;
42+
const bangToken = sourceCode.getFirstToken(left);
43+
const negatedOperator = `${operator.startsWith('!') ? '=' : '!'}${operator.slice(1)}`;
44+
45+
return {
46+
node: bangToken,
47+
messageId: MESSAGE_ID_ERROR,
48+
/** @param {import('eslint').Rule.RuleFixer} fixer */
49+
suggest: [
50+
{
51+
messageId: MESSAGE_ID_SUGGESTION,
52+
data: {
53+
operator: negatedOperator,
54+
},
55+
/** @param {import('eslint').Rule.RuleFixer} fixer */
56+
* fix(fixer) {
57+
yield * fixSpaceAroundKeyword(fixer, binaryExpression, sourceCode);
58+
59+
const tokenAfterBang = sourceCode.getTokenAfter(bangToken);
60+
61+
const {parent} = binaryExpression;
62+
if (
63+
(parent.type === 'ReturnStatement' || parent.type === 'ThrowStatement')
64+
&& !isParenthesized(binaryExpression, sourceCode)
65+
) {
66+
const returnToken = sourceCode.getFirstToken(parent);
67+
if (!isOnSameLine(returnToken, tokenAfterBang)) {
68+
yield * addParenthesizesToReturnOrThrowExpression(fixer, parent, sourceCode);
69+
}
70+
}
71+
72+
yield fixer.remove(bangToken);
73+
74+
const previousToken = sourceCode.getTokenBefore(bangToken);
75+
if (needsSemicolon(previousToken, sourceCode, tokenAfterBang.value)) {
76+
yield fixer.insertTextAfter(bangToken, ';');
77+
}
78+
79+
const operatorToken = sourceCode.getTokenAfter(
80+
left,
81+
token => token.type === 'Punctuator' && token.value === operator,
82+
);
83+
yield fixer.replaceText(operatorToken, negatedOperator);
84+
},
85+
},
86+
],
87+
};
88+
},
89+
});
90+
91+
/** @type {import('eslint').Rule.RuleModule} */
92+
module.exports = {
93+
create,
94+
meta: {
95+
type: 'problem',
96+
docs: {
97+
description: 'Disallow negated expression in equality check.',
98+
recommended: true,
99+
},
100+
101+
hasSuggestions: true,
102+
messages,
103+
},
104+
};
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import outdent from 'outdent';
2+
import {getTester} from './utils/test.mjs';
3+
4+
const {test} = getTester(import.meta);
5+
6+
test.snapshot({
7+
valid: [
8+
'!foo instanceof bar',
9+
'+foo === bar',
10+
'!(foo === bar)',
11+
// We are not checking right side
12+
'foo === !bar',
13+
],
14+
invalid: [
15+
'!foo === bar',
16+
'!foo !== bar',
17+
'!foo == bar',
18+
'!foo != bar',
19+
outdent`
20+
function x() {
21+
return!foo === bar;
22+
}
23+
`,
24+
outdent`
25+
function x() {
26+
return!
27+
foo === bar;
28+
throw!
29+
foo === bar;
30+
}
31+
`,
32+
outdent`
33+
foo
34+
!(a) === b
35+
`,
36+
outdent`
37+
foo
38+
![a, b].join('') === c
39+
`,
40+
outdent`
41+
foo
42+
! [a, b].join('') === c
43+
`,
44+
outdent`
45+
foo
46+
!/* comment */[a, b].join('') === c
47+
`,
48+
'!!foo === bar',
49+
],
50+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# Snapshot report for `test/no-negation-in-equality-check.mjs`
2+
3+
The actual snapshot is saved in `no-negation-in-equality-check.mjs.snap`.
4+
5+
Generated by [AVA](https://avajs.dev).
6+
7+
## invalid(1): !foo === bar
8+
9+
> Input
10+
11+
`␊
12+
1 | !foo === bar␊
13+
`
14+
15+
> Error 1/1
16+
17+
`␊
18+
> 1 | !foo === bar␊
19+
| ^ Negated expression in not allowed in equality check.␊
20+
21+
--------------------------------------------------------------------------------␊
22+
Suggestion 1/1: Switch to '!==' check.␊
23+
1 | foo !== bar␊
24+
`
25+
26+
## invalid(2): !foo !== bar
27+
28+
> Input
29+
30+
`␊
31+
1 | !foo !== bar␊
32+
`
33+
34+
> Error 1/1
35+
36+
`␊
37+
> 1 | !foo !== bar␊
38+
| ^ Negated expression in not allowed in equality check.␊
39+
40+
--------------------------------------------------------------------------------␊
41+
Suggestion 1/1: Switch to '===' check.␊
42+
1 | foo === bar␊
43+
`
44+
45+
## invalid(3): !foo == bar
46+
47+
> Input
48+
49+
`␊
50+
1 | !foo == bar␊
51+
`
52+
53+
> Error 1/1
54+
55+
`␊
56+
> 1 | !foo == bar␊
57+
| ^ Negated expression in not allowed in equality check.␊
58+
59+
--------------------------------------------------------------------------------␊
60+
Suggestion 1/1: Switch to '!=' check.␊
61+
1 | foo != bar␊
62+
`
63+
64+
## invalid(4): !foo != bar
65+
66+
> Input
67+
68+
`␊
69+
1 | !foo != bar␊
70+
`
71+
72+
> Error 1/1
73+
74+
`␊
75+
> 1 | !foo != bar␊
76+
| ^ Negated expression in not allowed in equality check.␊
77+
78+
--------------------------------------------------------------------------------␊
79+
Suggestion 1/1: Switch to '==' check.␊
80+
1 | foo == bar␊
81+
`
82+
83+
## invalid(5): function x() { return!foo === bar; }
84+
85+
> Input
86+
87+
`␊
88+
1 | function x() {␊
89+
2 | return!foo === bar;␊
90+
3 | }␊
91+
`
92+
93+
> Error 1/1
94+
95+
`␊
96+
1 | function x() {␊
97+
> 2 | return!foo === bar;␊
98+
| ^ Negated expression in not allowed in equality check.␊
99+
3 | }␊
100+
101+
--------------------------------------------------------------------------------␊
102+
Suggestion 1/1: Switch to '!==' check.␊
103+
1 | function x() {␊
104+
2 | return foo !== bar;␊
105+
3 | }␊
106+
`
107+
108+
## invalid(6): function x() { return! foo === bar; throw! foo === bar; }
109+
110+
> Input
111+
112+
`␊
113+
1 | function x() {␊
114+
2 | return!␊
115+
3 | foo === bar;␊
116+
4 | throw!␊
117+
5 | foo === bar;␊
118+
6 | }␊
119+
`
120+
121+
> Error 1/2
122+
123+
`␊
124+
1 | function x() {␊
125+
> 2 | return!␊
126+
| ^ Negated expression in not allowed in equality check.␊
127+
3 | foo === bar;␊
128+
4 | throw!␊
129+
5 | foo === bar;␊
130+
6 | }␊
131+
132+
--------------------------------------------------------------------------------␊
133+
Suggestion 1/1: Switch to '!==' check.␊
134+
1 | function x() {␊
135+
2 | return (␊
136+
3 | foo !== bar);␊
137+
4 | throw!␊
138+
5 | foo === bar;␊
139+
6 | }␊
140+
`
141+
142+
> Error 2/2
143+
144+
`␊
145+
1 | function x() {␊
146+
2 | return!␊
147+
3 | foo === bar;␊
148+
> 4 | throw!␊
149+
| ^ Negated expression in not allowed in equality check.␊
150+
5 | foo === bar;␊
151+
6 | }␊
152+
153+
--------------------------------------------------------------------------------␊
154+
Suggestion 1/1: Switch to '!==' check.␊
155+
1 | function x() {␊
156+
2 | return!␊
157+
3 | foo === bar;␊
158+
4 | throw (␊
159+
5 | foo !== bar);␊
160+
6 | }␊
161+
`
162+
163+
## invalid(7): foo !(a) === b
164+
165+
> Input
166+
167+
`␊
168+
1 | foo␊
169+
2 | !(a) === b␊
170+
`
171+
172+
> Error 1/1
173+
174+
`␊
175+
1 | foo␊
176+
> 2 | !(a) === b␊
177+
| ^ Negated expression in not allowed in equality check.␊
178+
179+
--------------------------------------------------------------------------------␊
180+
Suggestion 1/1: Switch to '!==' check.␊
181+
1 | foo␊
182+
2 | ;(a) !== b␊
183+
`
184+
185+
## invalid(8): foo ![a, b].join('') === c
186+
187+
> Input
188+
189+
`␊
190+
1 | foo␊
191+
2 | ![a, b].join('') === c␊
192+
`
193+
194+
> Error 1/1
195+
196+
`␊
197+
1 | foo␊
198+
> 2 | ![a, b].join('') === c␊
199+
| ^ Negated expression in not allowed in equality check.␊
200+
201+
--------------------------------------------------------------------------------␊
202+
Suggestion 1/1: Switch to '!==' check.␊
203+
1 | foo␊
204+
2 | ;[a, b].join('') !== c␊
205+
`
206+
207+
## invalid(9): foo ! [a, b].join('') === c
208+
209+
> Input
210+
211+
`␊
212+
1 | foo␊
213+
2 | ! [a, b].join('') === c␊
214+
`
215+
216+
> Error 1/1
217+
218+
`␊
219+
1 | foo␊
220+
> 2 | ! [a, b].join('') === c␊
221+
| ^ Negated expression in not allowed in equality check.␊
222+
223+
--------------------------------------------------------------------------------␊
224+
Suggestion 1/1: Switch to '!==' check.␊
225+
1 | foo␊
226+
2 | ; [a, b].join('') !== c␊
227+
`
228+
229+
## invalid(10): foo !/* comment */[a, b].join('') === c
230+
231+
> Input
232+
233+
`␊
234+
1 | foo␊
235+
2 | !/* comment */[a, b].join('') === c␊
236+
`
237+
238+
> Error 1/1
239+
240+
`␊
241+
1 | foo␊
242+
> 2 | !/* comment */[a, b].join('') === c␊
243+
| ^ Negated expression in not allowed in equality check.␊
244+
245+
--------------------------------------------------------------------------------␊
246+
Suggestion 1/1: Switch to '!==' check.␊
247+
1 | foo␊
248+
2 | ;/* comment */[a, b].join('') !== c␊
249+
`
250+
251+
## invalid(11): !!foo === bar
252+
253+
> Input
254+
255+
`␊
256+
1 | !!foo === bar␊
257+
`
258+
259+
> Error 1/1
260+
261+
`␊
262+
> 1 | !!foo === bar␊
263+
| ^ Negated expression in not allowed in equality check.␊
264+
265+
--------------------------------------------------------------------------------␊
266+
Suggestion 1/1: Switch to '!==' check.␊
267+
1 | !foo !== bar␊
268+
`
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.