Repository: eslint/eslint (js)

Issue:
Warn about mixing logical and ternary operators
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for requesting a rule change. If you are here for another reason, please see below:

    1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
    2. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
    3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
    4. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

    Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**What rule do you want to change?**
`no-mixed-operators`

**Does this change cause the rule to produce more or fewer warnings?**
more

**How will the change be implemented? (New option, new default behavior, etc.)?**
new option with default behaviour to warn

**Please provide some example code that this change will affect:**

<!-- Put your code examples here -->
```js
// what is priority of this? rule should warn of using brackets explicitly
obj.someValue || conditionSatisfied ? 'good' : 'bad'

// 1 option - rule should not warn
(obj.someValue || conditionSatisfied) ? 'good' : 'bad'

// 2 option - rule should not warn
obj.someValue || (conditionSatisfied ? 'good' : 'bad')
```

**What does the rule currently do for this code?**
it does not warn user about mixed logical and ternary expressions

**What will the rule do after it's changed?**
warn to use brackets around to make it explicit what the priority is

**Are you willing to submit a pull request to implement this change?**
nope, with this issue, I am just checking if this would make sense
Might be related to https://github.com/eslint/eslint/issues/7903

Pull request description:
Update: warn about mixing ternary and logical operators (fixes #11704)
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[X ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->
https://github.com/eslint/eslint/issues/11704

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**
Added additional check to see if the parent is a conditional expression and see if it's mixed and added messages for the same

**Is there anything you'd like reviewers to focus on?**
Nothing in particular.



Patch:
diff --git a/README.md b/README.md
index 02c875ec6..061eafefc 100644
--- a/README.md
+++ b/README.md
@@ -59,8 +59,6 @@ After that, you can run ESLint on any file or directory like this:
 $ ./node_modules/.bin/eslint yourfile.js
 ```
 
-It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, any plugins or shareable configs that you use must be installed locally in either case.
-
 ## <a name="configuration"></a>Configuration
 
 After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this:
diff --git a/docs/rules/no-mixed-operators.md b/docs/rules/no-mixed-operators.md
index 3c0bb2fab..3206d18fb 100644
--- a/docs/rules/no-mixed-operators.md
+++ b/docs/rules/no-mixed-operators.md
@@ -5,6 +5,8 @@ This rule warns when different operators are used consecutively without parenthe
 
 ```js
 var foo = a && b || c || d;    /*BAD: Unexpected mix of '&&' and '||'.*/
+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*/
 var foo = a && (b || c || d);  /*GOOD*/
 ```
@@ -23,10 +25,21 @@ will generate
 1:18  Unexpected mix of '&&' and '||'. (no-mixed-operators)
 ```
 
+```js
+var foo = a && b ? c : d;
+```
+
+will generate
+
+```sh
+1:13  Unexpected mix of '&&' and '?:'. (no-mixed-operators)
+1:18  Unexpected mix of '&&' and '?:'. (no-mixed-operators)
+```
+
 
 ## Rule Details
 
-This rule checks `BinaryExpression` and `LogicalExpression`.
+This rule checks `BinaryExpression`, `LogicalExpression` and `ConditionalExpression`.
 
 This rule may conflict with [no-extra-parens](no-extra-parens.md) rule.
 If you use both this and [no-extra-parens](no-extra-parens.md) rule together, you need to use the `nestedBinaryExpressions` option of [no-extra-parens](no-extra-parens.md) rule.
@@ -75,7 +88,8 @@ var foo = (a + b) * c;
 
 This rule has 2 options.
 
-* `groups` (`string[][]`) - specifies operator groups to be checked. The `groups` option is a list of groups, and a group is a list of binary operators. Default operator groups are defined as arithmetic, bitwise, comparison, logical, and relational operators.
+* `groups` (`string[][]`) - specifies operator groups to be checked. The `groups` option is a list of groups, and a group is a list of binary operators. Default operator groups are defined as arithmetic, bitwise, comparison, logical, and relational operators. Note: Ternary operator(?:) can be part of any group and by default is allowed to be mixed with other operators.
+
 * `allowSamePrecedence` (`boolean`) - specifies whether to allow mixed operators if they are of equal precedence. Default is `true`.
 
 ### groups
@@ -87,6 +101,7 @@ The following operators can be used in `groups` option:
 * Comparison Operators: `"=="`, `"!="`, `"==="`, `"!=="`, `">"`, `">="`, `"<"`, `"<="`
 * Logical Operators: `"&&"`, `"||"`
 * Relational Operators: `"in"`, `"instanceof"`
+* Ternary Operator: `?:`
 
 Now, consider the following group configuration: `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}`.
 There are 2 groups specified in this configuration: bitwise operators and logical operators.
@@ -102,6 +117,12 @@ var foo = a && b < 0 || c > 0 || d + 1 === 0;
 var foo = a & b | c;
 ```
 
+```js
+/*eslint no-mixed-operators: ["error", {"groups": [["&&", "||", "?:"]]}]*/
+
+var foo = a || b ? c : d;
+```
+
 Examples of **correct** code for this rule with `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` option:
 
 ```js
@@ -118,6 +139,13 @@ var foo = a + (b * c);
 var foo = (a + b) * c;
 ```
 
+```js
+/*eslint no-mixed-operators: ["error", {"groups": [["&&", "||", "?:"]]}]*/
+
+var foo = (a || b) ? c : d;
+var foo = a || (b ? c : d);
+```
+
 ### allowSamePrecedence
 
 Examples of **correct** code for this rule with `{"allowSamePrecedence": true}` option:
diff --git a/docs/rules/no-undef.md b/docs/rules/no-undef.md
index f5dc5a280..faaf978cc 100644
--- a/docs/rules/no-undef.md
+++ b/docs/rules/no-undef.md
@@ -11,32 +11,25 @@ Examples of **incorrect** code for this rule:
 ```js
 /*eslint no-undef: "error"*/
 
-var a = someFunction();
-b = 10;
+var foo = someFunction();
+var bar = a + 1;
 ```
 
 Examples of **correct** code for this rule with `global` declaration:
 
 ```js
-/*global someFunction b:writable*/
+/*global someFunction, a*/
 /*eslint no-undef: "error"*/
 
-var a = someFunction();
-b = 10;
+var foo = someFunction();
+var bar = a + 1;
 ```
 
-The `b:writable` syntax in `/*global */` indicates that assignment to `b` is correct.
+Note that this rule does not disallow assignments to read-only global variables.
+See [no-global-assign](no-global-assign.md) if you also want to disallow those assignments.
 
-Examples of **incorrect** code for this rule with `global` declaration:
-
-```js
-/*global b*/
-/*eslint no-undef: "error"*/
-
-b = 10;
-```
-
-By default, variables declared in `/*global */` are read-only, therefore assignment is incorrect.
+This rule also does not disallow redeclarations of global variables.
+See [no-redeclare](no-redeclare.md) if you also want to disallow those redeclarations.
 
 ## Options
 
@@ -111,3 +104,8 @@ If explicit declaration of global variables is not to your taste.
 ## Compatibility
 
 This rule provides compatibility with treatment of global variables in [JSHint](http://jshint.com/) and [JSLint](http://www.jslint.com).
+
+## Related Rules
+
+* [no-global-assign](no-global-assign.md)
+* [no-redeclare](no-redeclare.md)
diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md
index 45633f618..1bebf7d69 100644
--- a/docs/user-guide/getting-started.md
+++ b/docs/user-guide/getting-started.md
@@ -33,7 +33,7 @@ After that, you can run ESLint on any file or directory like this:
 $ ./node_modules/.bin/eslint yourfile.js
 ```
 
-It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, any plugins or shareable configs that you use must be installed locally in either case.
+It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, this is not recommended, and any plugins or shareable configs that you use must be installed locally in either case.
 
 ## Configuration
 
diff --git a/lib/rules/class-methods-use-this.js b/lib/rules/class-methods-use-this.js
index 0eb1da87f..c15f32e66 100644
--- a/lib/rules/class-methods-use-this.js
+++ b/lib/rules/class-methods-use-this.js
@@ -70,7 +70,8 @@ module.exports = {
          * @private
          */
         function isIncludedInstanceMethod(node) {
-            return isInstanceMethod(node) && !exceptMethods.has(node.key.name);
+            return isInstanceMethod(node) &&
+                (node.computed || !exceptMethods.has(node.key.name));
         }
 
         /**
diff --git a/lib/rules/no-mixed-operators.js b/lib/rules/no-mixed-operators.js
index 21e1d95c6..8d1c7a6af 100644
--- a/lib/rules/no-mixed-operators.js
+++ b/lib/rules/no-mixed-operators.js
@@ -20,12 +20,14 @@ const BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"];
 const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
 const LOGICAL_OPERATORS = ["&&", "||"];
 const RELATIONAL_OPERATORS = ["in", "instanceof"];
+const TERNARY_OPERATOR = ["?:"];
 const ALL_OPERATORS = [].concat(
     ARITHMETIC_OPERATORS,
     BITWISE_OPERATORS,
     COMPARISON_OPERATORS,
     LOGICAL_OPERATORS,
-    RELATIONAL_OPERATORS
+    RELATIONAL_OPERATORS,
+    TERNARY_OPERATOR
 );
 const DEFAULT_GROUPS = [
     ARITHMETIC_OPERATORS,
@@ -34,7 +36,7 @@ const DEFAULT_GROUPS = [
     LOGICAL_OPERATORS,
     RELATIONAL_OPERATORS
 ];
-const TARGET_NODE_TYPE = /^(?:Binary|Logical)Expression$/u;
+const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u;
 
 /**
  * Normalizes options.
@@ -65,6 +67,18 @@ function includesBothInAGroup(groups, left, right) {
     return groups.some(group => group.indexOf(left) !== -1 && group.indexOf(right) !== -1);
 }
 
+/**
+ * Checks whether the given node is a conditional expression and returns the test node else the left node.
+ *
+ * @param {ASTNode} node - A node which can be a BinaryExpression or a LogicalExpression node.
+ * This parent node can be BinaryExpression, LogicalExpression
+ *      , or a ConditionalExpression node
+ * @returns {ASTNode} node the appropriate node(left or test).
+ */
+function getChildNode(node) {
+    return node.type === "ConditionalExpression" ? node.test : node.left;
+}
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -121,7 +135,7 @@ module.exports = {
             const b = node.parent;
 
             return (
-                !includesBothInAGroup(options.groups, a.operator, b.operator) ||
+                !includesBothInAGroup(options.groups, a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) ||
                 (
                     options.allowSamePrecedence &&
                     astUtils.getPrecedence(a) === astUtils.getPrecedence(b)
@@ -139,12 +153,25 @@ module.exports = {
          * @returns {boolean} `true` if the node was mixed.
          */
         function isMixedWithParent(node) {
+
             return (
                 node.operator !== node.parent.operator &&
                 !astUtils.isParenthesised(sourceCode, node)
             );
         }
 
+        /**
+         * Checks whether the operator of a given node is mixed with a
+         * conditional expression.
+         *
+         * @param {ASTNode} node - A node to check. This is a conditional
+         *      expression node
+         * @returns {boolean} `true` if the node was mixed.
+         */
+        function isMixedWithConditionalParent(node) {
+            return !astUtils.isParenthesised(sourceCode, node) && !astUtils.isParenthesised(sourceCode, node.test);
+        }
+
         /**
          * Gets the operator token of a given node.
          *
@@ -153,7 +180,7 @@ module.exports = {
          * @returns {Token} The operator token of the node.
          */
         function getOperatorToken(node) {
-            return sourceCode.getTokenAfter(node.left, astUtils.isNotClosingParenToken);
+            return sourceCode.getTokenAfter(getChildNode(node), astUtils.isNotClosingParenToken);
         }
 
         /**
@@ -167,13 +194,13 @@ module.exports = {
          */
         function reportBothOperators(node) {
             const parent = node.parent;
-            const left = (parent.left === node) ? node : parent;
-            const right = (parent.left !== node) ? node : parent;
+            const left = (getChildNode(parent) === node) ? node : parent;
+            const right = (getChildNode(parent) !== node) ? node : parent;
             const message =
                 "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'.";
             const data = {
-                leftOperator: left.operator,
-                rightOperator: right.operator
+                leftOperator: left.operator || "?:",
+                rightOperator: right.operator || "?:"
             };
 
             context.report({
@@ -198,17 +225,25 @@ module.exports = {
          * @returns {void}
          */
         function check(node) {
-            if (TARGET_NODE_TYPE.test(node.parent.type) &&
-                isMixedWithParent(node) &&
-                !shouldIgnore(node)
-            ) {
-                reportBothOperators(node);
+            if (TARGET_NODE_TYPE.test(node.parent.type)) {
+                if (node.parent.type === "ConditionalExpression" && !shouldIgnore(node) && isMixedWithConditionalParent(node.parent)) {
+                    reportBothOperators(node);
+                } else {
+                    if (TARGET_NODE_TYPE.test(node.parent.type) &&
+                        isMixedWithParent(node) &&
+                        !shouldIgnore(node)
+                    ) {
+                        reportBothOperators(node);
+                    }
+                }
             }
+
         }
 
         return {
             BinaryExpression: check,
             LogicalExpression: check
+
         };
     }
 };
diff --git a/lib/rules/no-restricted-syntax.js b/lib/rules/no-restricted-syntax.js
index 74eea1478..41aa9fa39 100644
--- a/lib/rules/no-restricted-syntax.js
+++ b/lib/rules/no-restricted-syntax.js
@@ -21,7 +21,7 @@ module.exports = {
 
         schema: {
             type: "array",
-            items: [{
+            items: {
                 oneOf: [
                     {
                         type: "string"
@@ -36,7 +36,7 @@ module.exports = {
                         additionalProperties: false
                     }
                 ]
-            }],
+            },
             uniqueItems: true,
             minItems: 0
         }


Test patch:
diff --git a/tests/lib/rules/class-methods-use-this.js b/tests/lib/rules/class-methods-use-this.js
index 852124245..89af434fd 100644
--- a/tests/lib/rules/class-methods-use-this.js
+++ b/tests/lib/rules/class-methods-use-this.js
@@ -97,6 +97,14 @@ ruleTester.run("class-methods-use-this", rule, {
             errors: [
                 { type: "FunctionExpression", line: 1, column: 34, messageId: "missingThis", data: { name: "hasOwnProperty" } }
             ]
+        },
+        {
+            code: "class A { [foo]() {} }",
+            options: [{ exceptMethods: ["foo"] }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { type: "FunctionExpression", line: 1, column: 16, messageId: "missingThis", data: { name: "foo" } }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/no-mixed-operators.js b/tests/lib/rules/no-mixed-operators.js
index d740b8562..4e822181f 100644
--- a/tests/lib/rules/no-mixed-operators.js
+++ b/tests/lib/rules/no-mixed-operators.js
@@ -47,7 +47,18 @@ ruleTester.run("no-mixed-operators", rule, {
         {
             code: "a * b / c",
             options: [{ allowSamePrecedence: true }]
-        }
+        },
+        {
+            code: "(a || b) ? c : d",
+            options: [{ groups: [["&&", "||", "?:"]] }]
+        },
+        {
+            code: "a || (b ? c : d)",
+            options: [{ groups: [["&&", "||", "?:"]] }]
+        },
+        "a || (b ? c : d)",
+        "(a || b) ? c : d",
+        "a || b ? c : d"
     ],
     invalid: [
         {
@@ -110,6 +121,38 @@ ruleTester.run("no-mixed-operators", rule, {
                 { column: 3, message: "Unexpected mix of '*' and '/'." },
                 { column: 7, message: "Unexpected mix of '*' and '/'." }
             ]
+        },
+        {
+            code: "a || b ? c : d",
+            options: [{ groups: [["&&", "||", "?:"]] }],
+            errors: [
+                { column: 3, message: "Unexpected mix of '||' and '?:'." },
+                { column: 8, message: "Unexpected mix of '||' and '?:'." }
+            ]
+        },
+        {
+            code: "a && b ? 1 : 2",
+            options: [{ groups: [["&&", "||", "?:"]] }],
+            errors: [
+                { column: 3, message: "Unexpected mix of '&&' and '?:'." },
+                { column: 8, message: "Unexpected mix of '&&' and '?:'." }
+            ]
+        },
+        {
+            code: "x ? a && b : 0",
+            options: [{ groups: [["&&", "||", "?:"]] }],
+            errors: [
+                { column: 3, message: "Unexpected mix of '?:' and '&&'." },
+                { column: 7, message: "Unexpected mix of '?:' and '&&'." }
+            ]
+        },
+        {
+            code: "x ? 0 : a && b",
+            options: [{ groups: [["&&", "||", "?:"]] }],
+            errors: [
+                { column: 3, message: "Unexpected mix of '?:' and '&&'." },
+                { column: 11, message: "Unexpected mix of '?:' and '&&'." }
+            ]
         }
     ]
 });