Repository: eslint/eslint
Pull request #8176 (2017-03-02 06:44:26)

Fix: Don't autofix `+ +a` to `++a` in space-unary-ops
**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[X] 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:

**Tell us about your environment**

* **ESLint Version:** 3.16.1
* **Node Version:** 6.9.4
* **npm Version:** 3.10.10

**What parser (default, Babel-ESLint, etc.) are you using?** default

**Please show your full configuration:**
```
module.exports = {
  rules: {
    "space-unary-ops": ['error'],
  }
};
```

**What did you do? Please include the actual source code causing the issue.**
I ran `eslint --fix` on this code:
```
console.log(+ +1);
```

**What did you expect to happen?**
I expected it to result in correct code.

**What actually happened? Please include the actual, raw output from ESLint.**
It changed the code to `console.log(++1);` and there was a parse error in eslint.
```
➜  eslint_test_4 git:(master) ./node_modules/.bin/eslint --fix test.js

/Users/alanpierce/code/eslint_test_4/test.js
  1:15  error  Parsing error: Assigning to rvalue

✖ 1 problem (1 error, 0 warnings)
```

**What changes did you make? (Give an overview)**
I changed `space-unary-ops` autofixing to be more defensive and skip the
autofix in some cases where it was incorrect.

Most of the time, it's legal to remove spacing between adjacent operator tokens,
but in a case like `+ +a` it changes the behavior, so we should skip autofixing.
Another example is `+ ++a`, and the `-` versions of all of these. Since there
are relatively few unary operators and `++` and `--` are only allowed in limited
contexts, there aren't a ton of cases to think through here, so hopefully this
is all of them.

**Is there anything you'd like reviewers to focus on?**
Might be good to think through any other correctness issues like this.

eslintbot: LGTM
alangpierce: (Sorry, submitted too soon, will update with full details.)
alangpierce: Not sure why the node 7 build is failing, but it looks unrelated to my change.
eslintbot: LGTM

Patch:
diff --git a/lib/rules/space-unary-ops.js b/lib/rules/space-unary-ops.js
index 11c59c827..5b156fac2 100644
--- a/lib/rules/space-unary-ops.js
+++ b/lib/rules/space-unary-ops.js
@@ -68,6 +68,21 @@ module.exports = {
             return node.argument && node.argument.type && node.argument.type === "ObjectExpression";
         }
 
+        /**
+         * Check if it is safe to remove the spaces between the two tokens in
+         * the context of a non-word prefix unary operator. For example, `+ +1`
+         * cannot safely be changed to `++1`.
+         * @param {Token} firstToken The operator for a non-word prefix unary operator
+         * @param {Token} secondToken The first token of its operand
+         * @returns {boolean} Whether or not the spacing between the tokens can be removed
+         */
+        function canRemoveSpacesBetween(firstToken, secondToken) {
+            return !(
+                (firstToken.value === "+" && secondToken.value[0] === "+") ||
+                (firstToken.value === "-" && secondToken.value[0] === "-")
+            );
+        }
+
         /**
         * Checks if an override exists for a given operator.
         * @param {ASTnode} node AST node
@@ -244,7 +259,10 @@ module.exports = {
                             operator: firstToken.value
                         },
                         fix(fixer) {
-                            return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
+                            if (canRemoveSpacesBetween(firstToken, secondToken)) {
+                                return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
+                            }
+                            return null;
                         }
                     });
                 }


Test patch:
diff --git a/tests/lib/rules/space-unary-ops.js b/tests/lib/rules/space-unary-ops.js
index 8c1a3f102..0da1177b6 100644
--- a/tests/lib/rules/space-unary-ops.js
+++ b/tests/lib/rules/space-unary-ops.js
@@ -417,6 +417,46 @@ ruleTester.run("space-unary-ops", rule, {
                 message: "Unary operator '++' must be followed by whitespace."
             }]
         },
+        {
+            code: "+ +foo",
+            output: null,
+            options: [{ nonwords: false }],
+            errors: [{
+                message: "Unexpected space after unary operator '+'."
+            }]
+        },
+        {
+            code: "+ ++foo",
+            output: null,
+            options: [{ nonwords: false }],
+            errors: [{
+                message: "Unexpected space after unary operator '+'."
+            }]
+        },
+        {
+            code: "- -foo",
+            output: null,
+            options: [{ nonwords: false }],
+            errors: [{
+                message: "Unexpected space after unary operator '-'."
+            }]
+        },
+        {
+            code: "- --foo",
+            output: null,
+            options: [{ nonwords: false }],
+            errors: [{
+                message: "Unexpected space after unary operator '-'."
+            }]
+        },
+        {
+            code: "+ -foo",
+            output: "+-foo",
+            options: [{ nonwords: false }],
+            errors: [{
+                message: "Unexpected space after unary operator '+'."
+            }]
+        },
         {
             code: "function *foo() { yield(0) }",
             output: "function *foo() { yield (0) }",