Skip to content

Commit

Permalink
New: no-unsafe-negation rule (fixes #2716) (#6789)
Browse files Browse the repository at this point in the history
* New: `no-unsafe-negation` rule (fixes #2716)

* Update: deprecate `no-negated-in-lhs` rule (refs #2716)
  • Loading branch information
mysticatea authored and nzakas committed Aug 2, 2016
1 parent d94e945 commit aef18b4
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 1 deletion.
1 change: 1 addition & 0 deletions conf/eslint.json
Expand Up @@ -114,6 +114,7 @@
"no-unneeded-ternary": "off",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "off",
"no-unused-expressions": "off",
"no-unused-labels": "error",
"no-unused-vars": "error",
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/no-negated-in-lhs.md
@@ -1,5 +1,7 @@
# disallow negating the left operand in `in` expressions (no-negated-in-lhs)

This rule was **deprecated** in ESLint v3.3.0 and replaced by the [no-unsafe-negation](no-unsafe-negation.md) rule.

## Rule Details

Just as developers might type `-a + b` when they mean `-(a + b)` for the negative of a sum, they might type `!key in object` by mistake when they almost certainly mean `!(key in object)` to test that a key is not in an object.
Expand Down
57 changes: 57 additions & 0 deletions docs/rules/no-unsafe-negation.md
@@ -0,0 +1,57 @@
# disallow negating the left operand of relational operators (no-unsafe-negation)

(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by this rule.

Just as developers might type `-a + b` when they mean `-(a + b)` for the negative of a sum, they might type `!key in object` by mistake when they almost certainly mean `!(key in object)` to test that a key is not in an object. `!obj instanceof Ctor` is similar.

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

Relational Operators are:

- `in` operator.
- `instanceof` operator.

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

```js
/*eslint no-unsafe-negation: "error"*/

if (!key in object) {
// operator precedence makes it equivalent to (!key) in object
// and type conversion makes it equivalent to (key ? "false" : "true") in object
}

if (!obj instanceof Ctor) {
// operator precedence makes it equivalent to (!obj) instanceof Ctor
// and it equivalent to always false since boolean values are not objects.
}
```

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

```js
/*eslint no-unsafe-negation: "error"*/

if (!(key in object)) {
// key is not in object
}

if (!(obj instanceof Ctor)) {
// obj is not an instance of Ctor
}

if(("" + !key) in object) {
// make operator precedence and type conversion explicit
// in a rare situation when that is the intended meaning
}
```

## Options

Nothing.

## When Not To Use It

If you don't want to notify unsafe logical negations, then it's safe to disable this rule.
5 changes: 4 additions & 1 deletion lib/rules/no-negated-in-lhs.js
@@ -1,6 +1,7 @@
/**
* @fileoverview A rule to disallow negated left operands of the `in` operator
* @author Michael Ficarra
* @deprecated in ESLint v3.3.0
*/

"use strict";
Expand All @@ -14,8 +15,10 @@ module.exports = {
docs: {
description: "disallow negating the left operand in `in` expressions",
category: "Possible Errors",
recommended: true
recommended: true,
replacedBy: ["no-unsafe-negation"]
},
deprecated: true,

schema: []
},
Expand Down
80 changes: 80 additions & 0 deletions lib/rules/no-unsafe-negation.js
@@ -0,0 +1,80 @@
/**
* @fileoverview Rule to disallow negating the left operand of relational operators
* @author Toru Nagashima
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const astUtils = require("../ast-utils");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
* Checks whether the given operator is a relational operator or not.
*
* @param {string} op - The operator type to check.
* @returns {boolean} `true` if the operator is a relational operator.
*/
function isRelationalOperator(op) {
return op === "in" || op === "instanceof";
}

/**
* Checks whether the given node is a logical negation expression or not.
*
* @param {ASTNode} node - The node to check.
* @returns {boolean} `true` if the node is a logical negation expression.
*/
function isNegation(node) {
return node.type === "UnaryExpression" && node.operator === "!";
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: "disallow negating the left operand of relational operators",
category: "Possible Errors",
recommended: false
},
schema: [],
fixable: "code"
},

create(context) {
const sourceCode = context.getSourceCode();

return {
BinaryExpression(node) {
if (isRelationalOperator(node.operator) &&
isNegation(node.left) &&
!astUtils.isParenthesised(sourceCode, node.left)
) {
context.report({
node: node,
loc: node.left.loc,
message: "Unexpected negating the left operand of '{{operator}}' operator.",
data: node,

fix(fixer) {
const negationToken = sourceCode.getFirstToken(node.left);
const fixRange = [negationToken.range[1], node.range[1]];
const text = sourceCode.text.slice(fixRange[0], fixRange[1]);

return fixer.replaceTextRange(fixRange, `(${text})`);
}
});
}
}
};
}
};
1 change: 1 addition & 0 deletions tests/lib/rules/no-negated-in-lhs.js
@@ -1,6 +1,7 @@
/**
* @fileoverview Tests for the no-negated-in-lhs rule
* @author Michael Ficarra
* @deprecated in ESLint v3.3.0
*/

"use strict";
Expand Down
64 changes: 64 additions & 0 deletions tests/lib/rules/no-unsafe-negation.js
@@ -0,0 +1,64 @@
/**
* @fileoverview Tests for no-unsafe-negation rule.
* @author Toru Nagashima
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/no-unsafe-negation"),
RuleTester = require("../../../lib/testers/rule-tester");

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester();

ruleTester.run("no-unsafe-negation", rule, {
valid: [
"a in b",
"a in b === false",
"!(a in b)",
"(!a) in b",
"a instanceof b",
"a instanceof b === false",
"!(a instanceof b)",
"(!a) instanceof b",
],
invalid: [
{
code: "!a in b",
output: "!(a in b)",
errors: ["Unexpected negating the left operand of 'in' operator."]
},
{
code: "(!a in b)",
output: "(!(a in b))",
errors: ["Unexpected negating the left operand of 'in' operator."]
},
{
code: "!(a) in b",
output: "!((a) in b)",
errors: ["Unexpected negating the left operand of 'in' operator."]
},
{
code: "!a instanceof b",
output: "!(a instanceof b)",
errors: ["Unexpected negating the left operand of 'instanceof' operator."]
},
{
code: "(!a instanceof b)",
output: "(!(a instanceof b))",
errors: ["Unexpected negating the left operand of 'instanceof' operator."]
},
{
code: "!(a) instanceof b",
output: "!((a) instanceof b)",
errors: ["Unexpected negating the left operand of 'instanceof' operator."]
},
]
});

0 comments on commit aef18b4

Please sign in to comment.