Skip to content

Commit

Permalink
Update: add enforceForOrderingRelations option to no-unsafe-negation …
Browse files Browse the repository at this point in the history
…(addresses #12163)
  • Loading branch information
samrae7 committed Oct 13, 2019
1 parent 24ca088 commit a6d5e30
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 11 deletions.
44 changes: 39 additions & 5 deletions docs/rules/no-unsafe-negation.md
Expand Up @@ -4,12 +4,10 @@ Just as developers might type `-a + b` when they mean `-(a + b)` for the negativ

## Rule Details

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

Relational Operators are:

- `in` operator.
- `instanceof` operator.
- [`in` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in).
- [`instanceof` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof).

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

Expand Down Expand Up @@ -72,6 +70,42 @@ if (!(foo) in object) {
}
```

## Options

This rule has an object option:

- `"enforceForOrderingRelations": false` (default) allows negation of the left-hand side of ordering relational operators (`<`, `>`, `<=`, `>=`)
- `"enforceForOrderingRelations": true` disallows negation of the left-hand side of ordering relational operators

### enforceForOrderingRelations

The enforceForOrderingRelations option extends the rule to apply to all [Relational Operators](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-relational-operators), not just `in` and `instanceof`.

With this option set to true, the rule is enforced for all of the following:

- `in` operator.
- `instanceof` operator.
- `<` operator.
- `>` operator.
- `<=` operator.
- `>=` operator.

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)`.

Examples of additional **incorrect** code for this rule with the `{ "enforceForOrderingRelations": true }` option:

```js
/*eslint no-unneeded-ternary: ["error", { "enforceForOrderingRelations": true }]*/

if (! a < b) {}

while (! a > b) {}

foo = ! a <= b;

foo = ! a >= b;
```

## When Not To Use It

If you don't want to notify unsafe logical negations, then it's safe to disable this rule.
35 changes: 31 additions & 4 deletions lib/rules/no-unsafe-negation.js
Expand Up @@ -24,6 +24,15 @@ function isRelationalOperator(op) {
return op === "in" || op === "instanceof";
}

/**
* Checks whether the given operator is an ordering relational operator or not.
* @param {string} op The operator type to check.
* @returns {boolean} `true` if the operator is an ordering relational operator.
*/
function isOrderingRelationalOperator(op) {
return op === "<" || op === ">" || op === ">=" || op === "<=";
}

/**
* Checks whether the given node is a logical negation expression or not.
* @param {ASTNode} node The node to check.
Expand All @@ -42,25 +51,43 @@ module.exports = {
type: "problem",

docs: {
description: "disallow negating the left operand of relational operators",
description:
"disallow negating the left operand of relational operators",
category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-unsafe-negation"
},

schema: [],
schema: [
{
type: "object",
properties: {
enforceForOrderingRelations: {
type: "boolean",
default: false
}
},
additionalProperties: false
}
],
fixable: null,
messages: {
unexpected: "Unexpected negating the left operand of '{{operator}}' operator."
unexpected:
"Unexpected negating the left operand of '{{operator}}' operator."
}
},

create(context) {
const sourceCode = context.getSourceCode();
const options = context.options[0] || {};
const enforceForOrderingRelations = options.enforceForOrderingRelations === true;

return {
BinaryExpression(node) {
if (isRelationalOperator(node.operator) &&
const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(node.operator);

if (
(isRelationalOperator(node.operator) || orderingRelationRuleApplies) &&
isNegation(node.left) &&
!astUtils.isParenthesised(sourceCode, node.left)
) {
Expand Down
52 changes: 50 additions & 2 deletions tests/lib/rules/no-unsafe-negation.js
Expand Up @@ -18,7 +18,26 @@ const rule = require("../../../lib/rules/no-unsafe-negation"),

const ruleTester = new RuleTester();
const unexpectedInError = { messageId: "unexpected", data: { operator: "in" } };
const unexpectedInstanceofError = { messageId: "unexpected", data: { operator: "instanceof" } };
const unexpectedInstanceofError = {
messageId: "unexpected",
data: { operator: "instanceof" }
};
const unexpectedLessThanOperatorError = {
messageId: "unexpected",
data: { operator: "<" }
};
const unexpectedMoreThanOperatorError = {
messageId: "unexpected",
data: { operator: ">" }
};
const unexpectedMoreThanOrEqualOperatorError = {
messageId: "unexpected",
data: { operator: ">=" }
};
const unexpectedLessThanOrEqualOperatorError = {
messageId: "unexpected",
data: { operator: "<=" }
};

ruleTester.run("no-unsafe-negation", rule, {
valid: [
Expand All @@ -29,7 +48,16 @@ ruleTester.run("no-unsafe-negation", rule, {
"a instanceof b",
"a instanceof b === false",
"!(a instanceof b)",
"(!a) instanceof b"
"(!a) instanceof b",
"if (! a < b) {}",
"while (! a > b) {}",
"foo = ! a <= b;",
"foo = ! a >= b;",
{
code: "foo = (!a) >= b;",
options: [{ enforceForOrderingRelations: true }],
errors: [unexpectedMoreThanOrEqualOperatorError]
}
],
invalid: [
{
Expand All @@ -55,6 +83,26 @@ ruleTester.run("no-unsafe-negation", rule, {
{
code: "!(a) instanceof b",
errors: [unexpectedInstanceofError]
},
{
code: "if (! a < b) {}",
options: [{ enforceForOrderingRelations: true }],
errors: [unexpectedLessThanOperatorError]
},
{
code: "while (! a > b) {}",
options: [{ enforceForOrderingRelations: true }],
errors: [unexpectedMoreThanOperatorError]
},
{
code: "foo = ! a <= b;",
options: [{ enforceForOrderingRelations: true }],
errors: [unexpectedLessThanOrEqualOperatorError]
},
{
code: "foo = ! a >= b;",
options: [{ enforceForOrderingRelations: true }],
errors: [unexpectedMoreThanOrEqualOperatorError]
}
]
});

0 comments on commit a6d5e30

Please sign in to comment.