Skip to content

Commit 05f7dd5

Browse files
mdjermanovicbtmills
authored andcommitted
Update: Add suggestions for no-unsafe-negation (fixes #12591) (#12609)
1 parent d3e43f1 commit 05f7dd5

2 files changed

Lines changed: 183 additions & 37 deletions

File tree

lib/rules/no-unsafe-negation.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ module.exports = {
5454
description: "disallow negating the left operand of relational operators",
5555
category: "Possible Errors",
5656
recommended: true,
57-
url: "https://eslint.org/docs/rules/no-unsafe-negation"
57+
url: "https://eslint.org/docs/rules/no-unsafe-negation",
58+
suggestion: true
5859
},
5960

6061
schema: [
@@ -69,9 +70,13 @@ module.exports = {
6970
additionalProperties: false
7071
}
7172
],
73+
7274
fixable: null,
75+
7376
messages: {
74-
unexpected: "Unexpected negating the left operand of '{{operator}}' operator."
77+
unexpected: "Unexpected negating the left operand of '{{operator}}' operator.",
78+
suggestNegatedExpression: "Negate '{{operator}}' expression instead of its left operand. This changes the current behavior.",
79+
suggestParenthesisedNegation: "Wrap negation in '()' to make the intention explicit. This preserves the current behavior."
7580
}
7681
},
7782

@@ -82,18 +87,38 @@ module.exports = {
8287

8388
return {
8489
BinaryExpression(node) {
85-
const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(node.operator);
90+
const operator = node.operator;
91+
const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(operator);
8692

8793
if (
88-
(isInOrInstanceOfOperator(node.operator) || orderingRelationRuleApplies) &&
94+
(isInOrInstanceOfOperator(operator) || orderingRelationRuleApplies) &&
8995
isNegation(node.left) &&
9096
!astUtils.isParenthesised(sourceCode, node.left)
9197
) {
9298
context.report({
9399
node,
94100
loc: node.left.loc,
95101
messageId: "unexpected",
96-
data: { operator: node.operator }
102+
data: { operator },
103+
suggest: [
104+
{
105+
messageId: "suggestNegatedExpression",
106+
data: { operator },
107+
fix(fixer) {
108+
const negationToken = sourceCode.getFirstToken(node.left);
109+
const fixRange = [negationToken.range[1], node.range[1]];
110+
const text = sourceCode.text.slice(fixRange[0], fixRange[1]);
111+
112+
return fixer.replaceTextRange(fixRange, `(${text})`);
113+
}
114+
},
115+
{
116+
messageId: "suggestParenthesisedNegation",
117+
fix(fixer) {
118+
return fixer.replaceText(node.left, `(${sourceCode.getText(node.left)})`);
119+
}
120+
}
121+
]
97122
});
98123
}
99124
}

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

Lines changed: 153 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,6 @@ const rule = require("../../../lib/rules/no-unsafe-negation"),
1717
//------------------------------------------------------------------------------
1818

1919
const ruleTester = new RuleTester();
20-
const unexpectedInError = { messageId: "unexpected", data: { operator: "in" } };
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-
};
4120

4221
ruleTester.run("no-unsafe-negation", rule, {
4322
valid: [
@@ -83,52 +62,194 @@ ruleTester.run("no-unsafe-negation", rule, {
8362
invalid: [
8463
{
8564
code: "!a in b",
86-
errors: [unexpectedInError]
65+
errors: [{
66+
message: "Unexpected negating the left operand of 'in' operator.",
67+
suggestions: [
68+
{
69+
desc: "Negate 'in' expression instead of its left operand. This changes the current behavior.",
70+
output: "!(a in b)"
71+
},
72+
{
73+
desc: "Wrap negation in '()' to make the intention explicit. This preserves the current behavior.",
74+
output: "(!a) in b"
75+
}
76+
]
77+
}]
8778
},
8879
{
8980
code: "(!a in b)",
90-
errors: [unexpectedInError]
81+
errors: [{
82+
messageId: "unexpected",
83+
data: { operator: "in" },
84+
suggestions: [
85+
{
86+
messageId: "suggestNegatedExpression",
87+
output: "(!(a in b))"
88+
},
89+
{
90+
messageId: "suggestParenthesisedNegation",
91+
output: "((!a) in b)"
92+
}
93+
]
94+
}]
9195
},
9296
{
9397
code: "!(a) in b",
94-
errors: [unexpectedInError]
98+
errors: [{
99+
messageId: "unexpected",
100+
data: { operator: "in" },
101+
suggestions: [
102+
{
103+
messageId: "suggestNegatedExpression",
104+
output: "!((a) in b)"
105+
},
106+
{
107+
messageId: "suggestParenthesisedNegation",
108+
output: "(!(a)) in b"
109+
}
110+
]
111+
}]
95112
},
96113
{
97114
code: "!a instanceof b",
98-
errors: [unexpectedInstanceofError]
115+
errors: [{
116+
messageId: "unexpected",
117+
data: { operator: "instanceof" },
118+
suggestions: [
119+
{
120+
messageId: "suggestNegatedExpression",
121+
output: "!(a instanceof b)"
122+
},
123+
{
124+
messageId: "suggestParenthesisedNegation",
125+
output: "(!a) instanceof b"
126+
}
127+
]
128+
}]
99129
},
100130
{
101131
code: "(!a instanceof b)",
102-
errors: [unexpectedInstanceofError]
132+
errors: [{
133+
messageId: "unexpected",
134+
data: { operator: "instanceof" },
135+
suggestions: [
136+
{
137+
messageId: "suggestNegatedExpression",
138+
output: "(!(a instanceof b))"
139+
},
140+
{
141+
messageId: "suggestParenthesisedNegation",
142+
output: "((!a) instanceof b)"
143+
}
144+
]
145+
}]
103146
},
104147
{
105148
code: "!(a) instanceof b",
106-
errors: [unexpectedInstanceofError]
149+
errors: [{
150+
messageId: "unexpected",
151+
data: { operator: "instanceof" },
152+
suggestions: [
153+
{
154+
messageId: "suggestNegatedExpression",
155+
output: "!((a) instanceof b)"
156+
},
157+
{
158+
messageId: "suggestParenthesisedNegation",
159+
output: "(!(a)) instanceof b"
160+
}
161+
]
162+
}]
107163
},
108164
{
109165
code: "if (! a < b) {}",
110166
options: [{ enforceForOrderingRelations: true }],
111-
errors: [unexpectedLessThanOperatorError]
167+
errors: [{
168+
messageId: "unexpected",
169+
data: { operator: "<" },
170+
suggestions: [
171+
{
172+
messageId: "suggestNegatedExpression",
173+
output: "if (!( a < b)) {}"
174+
},
175+
{
176+
messageId: "suggestParenthesisedNegation",
177+
output: "if ((! a) < b) {}"
178+
}
179+
]
180+
}]
112181
},
113182
{
114183
code: "while (! a > b) {}",
115184
options: [{ enforceForOrderingRelations: true }],
116-
errors: [unexpectedMoreThanOperatorError]
185+
errors: [{
186+
messageId: "unexpected",
187+
data: { operator: ">" },
188+
suggestions: [
189+
{
190+
messageId: "suggestNegatedExpression",
191+
output: "while (!( a > b)) {}"
192+
},
193+
{
194+
messageId: "suggestParenthesisedNegation",
195+
output: "while ((! a) > b) {}"
196+
}
197+
]
198+
}]
117199
},
118200
{
119201
code: "foo = ! a <= b;",
120202
options: [{ enforceForOrderingRelations: true }],
121-
errors: [unexpectedLessThanOrEqualOperatorError]
203+
errors: [{
204+
messageId: "unexpected",
205+
data: { operator: "<=" },
206+
suggestions: [
207+
{
208+
messageId: "suggestNegatedExpression",
209+
output: "foo = !( a <= b);"
210+
},
211+
{
212+
messageId: "suggestParenthesisedNegation",
213+
output: "foo = (! a) <= b;"
214+
}
215+
]
216+
}]
122217
},
123218
{
124219
code: "foo = ! a >= b;",
125220
options: [{ enforceForOrderingRelations: true }],
126-
errors: [unexpectedMoreThanOrEqualOperatorError]
221+
errors: [{
222+
messageId: "unexpected",
223+
data: { operator: ">=" },
224+
suggestions: [
225+
{
226+
messageId: "suggestNegatedExpression",
227+
output: "foo = !( a >= b);"
228+
},
229+
{
230+
messageId: "suggestParenthesisedNegation",
231+
output: "foo = (! a) >= b;"
232+
}
233+
]
234+
}]
127235
},
128236
{
129237
code: "! a <= b",
130238
options: [{ enforceForOrderingRelations: true }],
131-
errors: [unexpectedLessThanOrEqualOperatorError]
239+
errors: [{
240+
messageId: "unexpected",
241+
data: { operator: "<=" },
242+
suggestions: [
243+
{
244+
messageId: "suggestNegatedExpression",
245+
output: "!( a <= b)"
246+
},
247+
{
248+
messageId: "suggestParenthesisedNegation",
249+
output: "(! a) <= b"
250+
}
251+
]
252+
}]
132253
}
133254
]
134255
});

0 commit comments

Comments
 (0)