Skip to content

Commit

Permalink
New: no-mixed-operators rule (fixes #6023) (#6241)
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea authored and ilyavolodin committed Jun 9, 2016
1 parent 6e03c4b commit 0e14016
Show file tree
Hide file tree
Showing 7 changed files with 559 additions and 92 deletions.
1 change: 1 addition & 0 deletions conf/eslint.json
Expand Up @@ -55,6 +55,7 @@
"no-lone-blocks": "off",
"no-lonely-if": "off",
"no-loop-func": "off",
"no-mixed-operators": "off",
"no-mixed-requires": "off",
"no-mixed-spaces-and-tabs": "error",
"linebreak-style": "off",
Expand Down
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -188,6 +188,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
* [no-continue](no-continue.md): disallow `continue` statements
* [no-inline-comments](no-inline-comments.md): disallow inline comments after code
* [no-lonely-if](no-lonely-if.md): disallow `if` statements as the only statement in `else` blocks
* [no-mixed-operators](no-mixed-operators.md): disallow mixes of different operators
* [no-mixed-spaces-and-tabs](no-mixed-spaces-and-tabs.md): disallow mixed spaces and tabs for indentation (recommended)
* [no-multiple-empty-lines](no-multiple-empty-lines.md): disallow multiple empty lines
* [no-negated-condition](no-negated-condition.md): disallow negated conditions
Expand Down
138 changes: 138 additions & 0 deletions docs/rules/no-mixed-operators.md
@@ -0,0 +1,138 @@
# Disallow mixes of different operators (no-mixed-operators)

Enclosing complex expressions by parentheses clarifies the developer's intention, which makes the code more readable.
This rule warns when different operators are used consecutively without parentheses in an expression.

```js
var foo = a && b || c || d; /*BAD: Unexpected mix of '&&' and '||'.*/
var foo = (a && b) || c || d; /*GOOD*/
var foo = a && (b || c || d); /*GOOD*/
```

## Rule Details

This rule checks `BinaryExpression` and `LogicalExpression`.

This rule may conflict with [no-extra-parens] rule.
If you use both this and [no-extra-parens] rule together, you need to use the `nestedBinaryExpressions` option of [no-extra-parens] rule.

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

```js
/*eslint no-mixed-operators: "error"*/

var foo = a && b < 0 || c > 0 || d + 1 === 0;
var foo = a + b * c;
```

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

```js
/*eslint no-mixed-operators: "error"*/

var foo = a || b || c;
var foo = a && b && c;
var foo = (a && b < 0) || c > 0 || d + 1 === 0;
var foo = a && (b < 0 || c > 0 || d + 1 === 0);
var foo = a + (b * c);
var foo = (a + b) * c;
```

## Options

```json
{
"no-mixed-operators": [
"error",
{
"groups": [
["+", "-", "*", "/", "%", "**"],
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["&&", "||"],
["in", "instanceof"]
],
"allowSamePrecedence": true
}
]
}
```

This rule has 2 options.

* `groups` (`string[][]`) - specifies groups to compare operators.
When this rule compares two operators, if both operators are included in a same group, this rule checks it. Otherwise, this rule ignores it.
This value is a list of groups. The group is a list of binary operators.
Default is the groups for each kind of operators.
* `allowSamePrecedence` (`boolean`) - specifies to allow mix of 2 operators if those have the same precedence. Default is `true`.

### groups

The following operators can be used in `groups` option:

* Arithmetic Operators: `"+"`, `"-"`, `"*"`, `"/"`, `"%"`, `"**"`
* Bitwise Operators: `"&"`, `"|"`, `"^"`, `"~"`, `"<<"`, `">>"`, `">>>"`
* Comparison Operators: `"=="`, `"!="`, `"==="`, `"!=="`, `">"`, `">="`, `"<"`, `"<="`
* Logical Operators: `"&&"`, `"||"`
* Relational Operators: `"in"`, `"instanceof"`

Now, considers about `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` configure.
This configure has 2 groups: bitwise operators and logical operators.
This rule checks only if both operators are included in a same group.
So, in this case, this rule comes to check between bitwise operators and between logical operators.
This rule ignores other operators.

Examples of **incorrect** code for this rule with `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` option:

```js
/*eslint no-mixed-operators: ["error", {"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}]*/

var foo = a && b < 0 || c > 0 || d + 1 === 0;
var foo = a & b | c;
```

Examples of **correct** code for this rule with `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` option:

```js
/*eslint no-mixed-operators: ["error", {"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}]*/

var foo = a || b > 0 || c + 1 === 0;
var foo = a && b > 0 && c + 1 === 0;
var foo = (a && b < 0) || c > 0 || d + 1 === 0;
var foo = a && (b < 0 || c > 0 || d + 1 === 0);
var foo = (a & b) | c;
var foo = a & (b | c);
var foo = a + b * c;
var foo = a + (b * c);
var foo = (a + b) * c;
```

### allowSamePrecedence

Examples of **correct** code for this rule with `{"allowSamePrecedence": true}` option:

```js
/*eslint no-mixed-operators: ["error", {"allowSamePrecedence": true}]*/

// + and - have the same precedence.
var foo = a + b - c;
```

Examples of **incorrect** code for this rule with `{"allowSamePrecedence": false}` option:

```js
/*eslint no-mixed-operators: ["error", {"allowSamePrecedence": false}]*/

// + and - have the same precedence.
var foo = a + b - c;
```

## When Not To Use It

If you don't want to be notified about mixed operators, then it's safe to disable this rule.

## Related Rules

* [no-extra-parens]

[no-extra-parens]: no-extra-parens.md
91 changes: 91 additions & 0 deletions lib/ast-utils.js
Expand Up @@ -460,5 +460,96 @@ module.exports = {

/* istanbul ignore next */
return true;
},

/**
* Get the precedence level based on the node type
* @param {ASTNode} node node to evaluate
* @returns {int} precedence level
* @private
*/
getPrecedence: function(node) {
switch (node.type) {
case "SequenceExpression":
return 0;

case "AssignmentExpression":
case "ArrowFunctionExpression":
case "YieldExpression":
return 1;

case "ConditionalExpression":
return 3;

case "LogicalExpression":
switch (node.operator) {
case "||":
return 4;
case "&&":
return 5;

// no default
}

/* falls through */

case "BinaryExpression":

switch (node.operator) {
case "|":
return 6;
case "^":
return 7;
case "&":
return 8;
case "==":
case "!=":
case "===":
case "!==":
return 9;
case "<":
case "<=":
case ">":
case ">=":
case "in":
case "instanceof":
return 10;
case "<<":
case ">>":
case ">>>":
return 11;
case "+":
case "-":
return 12;
case "*":
case "/":
case "%":
return 13;

// no default
}

/* falls through */

case "UnaryExpression":
return 14;

case "UpdateExpression":
return 15;

case "CallExpression":

// IIFE is allowed to have parens in any position (#655)
if (node.callee.type === "FunctionExpression") {
return -1;
}
return 16;

case "NewExpression":
return 17;

// no default
}
return 18;
}
};
93 changes: 1 addition & 92 deletions lib/rules/no-extra-parens.js
Expand Up @@ -57,6 +57,7 @@ module.exports = {
var sourceCode = context.getSourceCode();

var isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);
var precedence = astUtils.getPrecedence;
var ALL_NODES = context.options[0] !== "functions";
var EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
var NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
Expand Down Expand Up @@ -255,98 +256,6 @@ module.exports = {
throw new Error("unreachable");
}

/**
* Get the precedence level based on the node type
* @param {ASTNode} node node to evaluate
* @returns {int} precedence level
* @private
*/
function precedence(node) {

switch (node.type) {
case "SequenceExpression":
return 0;

case "AssignmentExpression":
case "ArrowFunctionExpression":
case "YieldExpression":
return 1;

case "ConditionalExpression":
return 3;

case "LogicalExpression":
switch (node.operator) {
case "||":
return 4;
case "&&":
return 5;

// no default
}

/* falls through */

case "BinaryExpression":

switch (node.operator) {
case "|":
return 6;
case "^":
return 7;
case "&":
return 8;
case "==":
case "!=":
case "===":
case "!==":
return 9;
case "<":
case "<=":
case ">":
case ">=":
case "in":
case "instanceof":
return 10;
case "<<":
case ">>":
case ">>>":
return 11;
case "+":
case "-":
return 12;
case "*":
case "/":
case "%":
return 13;

// no default
}

/* falls through */

case "UnaryExpression":
return 14;

case "UpdateExpression":
return 15;

case "CallExpression":

// IIFE is allowed to have parens in any position (#655)
if (node.callee.type === "FunctionExpression") {
return -1;
}
return 16;

case "NewExpression":
return 17;

// no default
}
return 18;
}

/**
* Report the node
* @param {ASTNode} node node to evaluate
Expand Down

0 comments on commit 0e14016

Please sign in to comment.