Repository: eslint/eslint (js)

Issue:
no-unneeded-ternary incorrect documentation for defaultAssignment option
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for requesting a change that is not a bug fix, rule change, or new rule. 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 request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
    3. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
    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.
-->

This is a "documentation bug".

**The version of ESLint you are using.**

6.1.0

**The problem you want to solve.**

Documentation for `defaultAssignment` option of the [`no-unneeded-ternary`](https://eslint.org/docs/rules/no-unneeded-ternary) rule is incorrect.

`defaultAssignment = true` means don't report expressions such as `x ? x : y`.

`defaultAssignment = false` means report expressions such as `x ? x : y`.

Default is `true`.

The rule doesn't actually check if the expression is in an assignment context. That's by design from the start (#3232 and #3260). It simply searches for all ternary expressions where test and consequent are same identifier.

Incorrect parts of the documentation are:

1. The `incorrect` example:

```js
/*eslint no-unneeded-ternary: "error"*/

var a = x === 2 ? true : false;

var a = x ? true : false;

var a = f(x ? x : 1);
```

The example is wrong, `var a = f(x ? x : 1);` is not a warning because of the default option value.

2. The `correct` example:

```js
/*eslint no-unneeded-ternary: "error"*/

var a = x === 2 ? "Yes" : "No";

var a = x !== false;

var a = x ? "Yes" : "No";

var a = x ? y : x;

var a = x ? x : 1;  // Note that this is only allowed as it on the right hand side of an assignment; this type of ternary is disallowed everywhere else. See defaultAssignment option below for more details.
```

The comment is wrong.

3. defaultAssignment section

> The defaultAssignment option allows expressions of the form x ? x : expr (where x is any identifier and expr is any expression) as the right hand side of assignments (but nowhere else).

The option (when `true`) allows such expressions everywhere.

**Your take on the correct solution to problem.**

Fix the documentation.

~~Perhaps also consider changing the name of the option for two reasons:~~

* ~~The `Assignment` part is confusing.~~
* ~~When the option name doesn't have an explicit prefix, `true` ususally means "enforce on", rather than "allow".~~

~~Maybe `allowSameConsequent` or `allowSameIfTrue`.~~

**Are you willing to submit a pull request to implement this change?**

Yes, I would be glad to do it.


Pull request description:
Docs: fix doc for no-unneeded-ternary rule (fixes #12098)
<!--
    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)**

[X] 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))
[ ] 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.)
-->

<!--
    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)**
Corrected docs for defaultAssignment option of no-unneeded-ternary rule as per this issue: https://github.com/eslint/eslint/issues/12098. Also added extra test cases to show more clearly how the option works.


**Is there anything you'd like reviewers to focus on?**




Patch:
diff --git a/README.md b/README.md
index dd414f1d1..8c75122ed 100644
--- a/README.md
+++ b/README.md
@@ -265,7 +265,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <h3>Gold Sponsors</h3>
 <p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/eeb91aa/logo.png" alt="Shopify" height="96"></a> <a href="http://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://badoo.com/team?utm_source=eslint"><img src="https://images.opencollective.com/badoo/2826a3b/logo.png" alt="Badoo" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://opensource.facebook.com"><img src="https://images.opencollective.com/fbopensource/fbb8a5b/logo.png" alt="Facebook Open Source" height="96"></a></p><h3>Silver Sponsors</h3>
 <p><a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://clay.global"><img src="https://images.opencollective.com/clayglobal/2468f34/logo.png" alt="clay" height="32"></a> <a href="https://www.crosswordsolver.com"><img src="https://images.opencollective.com/crosswordsolver/d4481d6/logo.png" alt="Crosswordsolver" height="32"></a> <a href="https://www.codacy.com/?utm_source=eslint&utm_medium=cpm&utm_campaign=eslint-sponsorship"><img src="https://images.opencollective.com/codacy/ed22716/logo.png" alt="Codacy" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://uxplanet.org/top-ui-ux-design-agencies-user-experience-firms-8c54697e290"><img src="https://images.opencollective.com/ui-ux-design-agencies/cae5dfe/logo.png" alt="UI UX Design Agencies" height="32"></a> <a href="https://www.minitool.com"><img src="https://images.opencollective.com/minitool-software-ltd/6d68e16/logo.png" alt="MiniTool Software Ltd" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://jsheroes.io/"><img src="https://images.opencollective.com/jsheroes1/9fedf0b/logo.png" alt="JSHeroes " height="32"></a></p>
+<p><a href="https://uxplanet.org/top-ui-ux-design-agencies-user-experience-firms-8c54697e290"><img src="https://images.opencollective.com/ui-ux-design-agencies/cae5dfe/logo.png" alt="UI UX Design Agencies" height="32"></a> <a href="https://www.bugsnag.com/platforms/react-error-reporting?utm_source=MobX&utm_medium=Website&utm_content=open-source&utm_campaign=2019-community&utm_term=20190913"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://www.minitool.com"><img src="https://images.opencollective.com/minitool-software-ltd/6d68e16/logo.png" alt="MiniTool Software Ltd" height="32"></a> <a href="https://www.codacy.com/?utm_source=eslint&utm_medium=cpm&utm_campaign=eslint-sponsorship"><img src="https://images.opencollective.com/codacy/ed22716/logo.png" alt="Codacy" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://www.crosswordsolver.com"><img src="https://images.opencollective.com/crosswordsolver/d4481d6/logo.png" alt="Crosswordsolver" height="32"></a> <a href="https://clay.global"><img src="https://images.opencollective.com/clayglobal/2468f34/logo.png" alt="clay" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://jsheroes.io/"><img src="https://images.opencollective.com/jsheroes1/9fedf0b/logo.png" alt="JSHeroes " height="32"></a></p>
 <!--sponsorsend-->
 
 ## <a name="technology-sponsors"></a>Technology Sponsors
diff --git a/docs/rules/no-unneeded-ternary.md b/docs/rules/no-unneeded-ternary.md
index b4caddf58..e5d4e8011 100644
--- a/docs/rules/no-unneeded-ternary.md
+++ b/docs/rules/no-unneeded-ternary.md
@@ -41,8 +41,6 @@ Examples of **incorrect** code for this rule:
 var a = x === 2 ? true : false;
 
 var a = x ? true : false;
-
-var a = f(x ? x : 1);
 ```
 
 Examples of **correct** code for this rule:
@@ -58,7 +56,7 @@ var a = x ? "Yes" : "No";
 
 var a = x ? y : x;
 
-var a = x ? x : 1;  // Note that this is only allowed as it on the right hand side of an assignment; this type of ternary is disallowed everywhere else. See defaultAssignment option below for more details.
+f(x ? x : 1); // default assignment - would be disallowed if defaultAssignment option set to false. See option details below.
 ```
 
 ## Options
@@ -70,7 +68,7 @@ This rule has an object option:
 
 ### defaultAssignment
 
-The defaultAssignment option allows expressions of the form `x ? x : expr` (where `x` is any identifier and `expr` is any expression) as the right hand side of assignments (but nowhere else).
+When set to `true`, which it is by default, The defaultAssignment option allows expressions of the form `x ? x : expr` (where `x` is any identifier and `expr` is any expression).
 
 Examples of additional **incorrect** code for this rule with the `{ "defaultAssignment": false }` option:
 
@@ -78,8 +76,12 @@ Examples of additional **incorrect** code for this rule with the `{ "defaultAssi
 /*eslint no-unneeded-ternary: ["error", { "defaultAssignment": false }]*/
 
 var a = x ? x : 1;
+
+f(x ? x : 1);
 ```
 
+Note that `defaultAssignment: false` still allows expressions of the form `x ? expr : x` (where the identifier is on the right hand side of the ternary).
+
 ## When Not To Use It
 
 You can turn this rule off if you are not concerned with unnecessary complexity in conditional expressions.
diff --git a/docs/rules/no-unsafe-negation.md b/docs/rules/no-unsafe-negation.md
index 880e9f644..38bb9b02f 100644
--- a/docs/rules/no-unsafe-negation.md
+++ b/docs/rules/no-unsafe-negation.md
@@ -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:
 
@@ -72,6 +70,38 @@ 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
+
+With this option set to `true` the rule is additionally enforced for:
+
+- `<` 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-unsafe-negation: ["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.
diff --git a/lib/linter/linter.js b/lib/linter/linter.js
index 8bd29b947..6d88cb5aa 100644
--- a/lib/linter/linter.js
+++ b/lib/linter/linter.js
@@ -666,6 +666,8 @@ function parse(text, parser, providedParserOptions, filePath) {
         // If the message includes a leading line number, strip it:
         const message = `Parsing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
 
+        debug("%s\n%s", message, ex.stack);
+
         return {
             success: false,
             error: {
diff --git a/lib/rules/no-unsafe-negation.js b/lib/rules/no-unsafe-negation.js
index 5226b367d..5c8f569d7 100644
--- a/lib/rules/no-unsafe-negation.js
+++ b/lib/rules/no-unsafe-negation.js
@@ -16,14 +16,23 @@ const astUtils = require("./utils/ast-utils");
 //------------------------------------------------------------------------------
 
 /**
- * Checks whether the given operator is a relational operator or not.
+ * Checks whether the given operator is `in` or `instanceof`
  * @param {string} op The operator type to check.
- * @returns {boolean} `true` if the operator is a relational operator.
+ * @returns {boolean} `true` if the operator is `in` or `instanceof`
  */
-function isRelationalOperator(op) {
+function isInOrInstanceOfOperator(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.
@@ -48,7 +57,18 @@ module.exports = {
             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."
@@ -57,10 +77,15 @@ module.exports = {
 
     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 (
+                    (isInOrInstanceOfOperator(node.operator) || orderingRelationRuleApplies) &&
                     isNegation(node.left) &&
                     !astUtils.isParenthesised(sourceCode, node.left)
                 ) {


Test patch:
diff --git a/tests/lib/rules/no-unneeded-ternary.js b/tests/lib/rules/no-unneeded-ternary.js
index 243526c22..a297b211b 100644
--- a/tests/lib/rules/no-unneeded-ternary.js
+++ b/tests/lib/rules/no-unneeded-ternary.js
@@ -27,6 +27,12 @@ ruleTester.run("no-unneeded-ternary", rule, {
         "var a = x === 2 ? 'true' : 'false';",
         "var a = foo ? foo : bar;",
         "var value = 'a';var canSet = true;var result = value || (canSet ? 'unset' : 'can not set')",
+        "var a = foo ? bar : foo;",
+        "foo ? bar : foo;",
+        "var a = f(x ? x : 1)",
+        "f(x ? x : 1);",
+        "foo ? foo : bar;",
+        "var a = foo ? 'Yes' : foo;",
         {
             code: "var a = foo ? 'Yes' : foo;",
             options: [{ defaultAssignment: false }]
@@ -34,6 +40,10 @@ ruleTester.run("no-unneeded-ternary", rule, {
         {
             code: "var a = foo ? bar : foo;",
             options: [{ defaultAssignment: false }]
+        },
+        {
+            code: "foo ? bar : foo;",
+            options: [{ defaultAssignment: false }]
         }
     ],
     invalid: [
@@ -314,6 +324,39 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 line: 1,
                 column: 13
             }]
+        },
+        {
+            code: "f(x ? x : 1);",
+            output: "f(x || 1);",
+            options: [{ defaultAssignment: false }],
+            errors: [{
+                message: "Unnecessary use of conditional expression for default assignment.",
+                type: "ConditionalExpression",
+                line: 1,
+                column: 7
+            }]
+        },
+        {
+            code: "x ? x : 1;",
+            output: "x || 1;",
+            options: [{ defaultAssignment: false }],
+            errors: [{
+                message: "Unnecessary use of conditional expression for default assignment.",
+                type: "ConditionalExpression",
+                line: 1,
+                column: 5
+            }]
+        },
+        {
+            code: "var a = foo ? foo : bar;",
+            output: "var a = foo || bar;",
+            options: [{ defaultAssignment: false }],
+            errors: [{
+                message: "Unnecessary use of conditional expression for default assignment.",
+                type: "ConditionalExpression",
+                line: 1,
+                column: 15
+            }]
         }
     ]
 });
diff --git a/tests/lib/rules/no-unsafe-negation.js b/tests/lib/rules/no-unsafe-negation.js
index 6ac245412..9493bbb72 100644
--- a/tests/lib/rules/no-unsafe-negation.js
+++ b/tests/lib/rules/no-unsafe-negation.js
@@ -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: [
@@ -29,7 +48,37 @@ ruleTester.run("no-unsafe-negation", rule, {
         "a instanceof b",
         "a instanceof b === false",
         "!(a instanceof b)",
-        "(!a) instanceof b"
+        "(!a) instanceof b",
+
+        // tests cases for enforceForOrderingRelations option:
+        "if (! a < b) {}",
+        "while (! a > b) {}",
+        "foo = ! a <= b;",
+        "foo = ! a >= b;",
+        {
+            code: "! a <= b",
+            options: [{}]
+        },
+        {
+            code: "foo = ! a >= b;",
+            options: [{ enforceForOrderingRelations: false }]
+        },
+        {
+            code: "foo = (!a) >= b;",
+            options: [{ enforceForOrderingRelations: true }]
+        },
+        {
+            code: "a <= b",
+            options: [{ enforceForOrderingRelations: true }]
+        },
+        {
+            code: "!(a < b)",
+            options: [{ enforceForOrderingRelations: true }]
+        },
+        {
+            code: "foo = a > b;",
+            options: [{ enforceForOrderingRelations: true }]
+        }
     ],
     invalid: [
         {
@@ -55,6 +104,31 @@ 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]
+        },
+        {
+            code: "! a <= b",
+            options: [{ enforceForOrderingRelations: true }],
+            errors: [unexpectedLessThanOrEqualOperatorError]
         }
     ]
 });