Repository: eslint/eslint (JavaScript)

Issue:
Fix range bug with no-extra-boolean-cast
**Tell us about your environment**

* **ESLint Version:** 5.12.1
* **Node Version:** 8.11.4
* **npm Version:** 5.6.0

**What parser (default, Babel-ESLint, etc.) are you using?** default

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```json
{
  "extends": ["eslint:recommended"],
  "rules": {
    "no-extra-boolean-cast": "error"
  }
}
```

</details>

**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.**

<!-- Paste the source code below: -->
```js
var bar = 1;
var foo = !!bar ? 'true' : 'false';
```

<!-- Paste the command you used to run ESLint: -->
```bash
npm run lint -- --format=json
```

**What did you expect to happen?**
The information about the location of the fix appears to be off. Running `--fix` applies the fix correctly but the response from the JSON output is off (which means that other external tools cannot apply the fix).

I would expect the location information to be something like:
```json
{
  "line": 2,
  "column": 11,
  "nodeType": "UnaryExpression",
  "messageId": "unexpectedNegation",
  "endLine": 2,
  "endColumn": 16,
  "fix": { "range": [24, 29], "text": "bar" }
}
```

Looking at AST Explorer seems to have the right location information.

https://astexplorer.net/#/gist/b5517bff697af4446176699497517e36/8f1a35f7594886e63d855960a1649120071963d4
**What actually happened? Please include the actual, raw output from ESLint.**
```json
[
  {
    "filePath": "/Volumes/Freelancer/scratch/eslint/test.js",
    "messages": [
      {
        "ruleId": "no-unused-vars",
        "severity": 2,
        "message": "'foo' is assigned a value but never used.",
        "line": 2,
        "column": 5,
        "nodeType": "Identifier",
        "endLine": 2,
        "endColumn": 8
      },
      {
        "ruleId": "no-extra-boolean-cast",
        "severity": 2,
        "message": "Redundant double negation.",
        "line": 2,
        "column": 12,
        "nodeType": "UnaryExpression",
        "messageId": "unexpectedNegation",
        "endLine": 2,
        "endColumn": 16,
        "fix": { "range": [23, 28], "text": "bar" }
      }
    ],
    "errorCount": 2,
    "warningCount": 0,
    "fixableErrorCount": 1,
    "fixableWarningCount": 0,
    "source": "var bar = 1;\nvar foo = !!bar ? 'true' : 'false';\n"
  }
]
```

**Are you willing to submit a pull request to fix this bug?** With help potentially. I'm unfamiliar with how rules output the position information.



Discussion:
Does the source file have a byte-order-mark at the start? Fix ranges from ESLint don't include a BOM when calculating positions, so if you're piping results from ESLint into an external tool to apply fixes, you might need to remove the BOM from the source text yourself before passing it to the external tool.
I don't have a byte order mark and other offsets work fine for me.

Unfortunately, it looks like there wasn't enough interest from the team
or community to implement this change. While we wish we'd be able to
accommodate everyone's requests, we do need to prioritize. We've found
that issues failing to reach accepted status after 21 days tend to
never be accepted, and as such, we [close those issues](https://eslint.org/docs/maintainer-guide/issues#when-to-close-an-issue).
This doesn't mean the idea isn't interesting or useful, just that it's
not something the team can commit to.

Thanks for contributing to ESLint and we appreciate your understanding.

[//]: # (auto-close)

Reopening as this slipped through the cracks. We at least need to try to reproduce this before closing.
Source:

```js
"var bar = 1;\nvar foo = !!bar ? 'true' : 'false';\n"
```

Error:

```js
{
    "ruleId": "no-extra-boolean-cast",
    "severity": 2,
    "message": "Redundant double negation.",
    "line": 2,
    "column": 12,
    "nodeType": "UnaryExpression",
    "messageId": "unexpectedNegation",
    "endLine": 2,
    "endColumn": 16,
     "fix": { "range": [23, 28], "text": "bar" }
}
```

I think that:

* `"column": 12` is a **bug**, it should be `11`. The rule is reporting `!bar` node instead of `!!bar` node.
* `"range": [23, 28]` is **correct**, range is 0-based and `\n` counts as only 1.

For comparison, this is `no-implicit-coercion` error on the same code:

```js
{
    "ruleId": "no-implicit-coercion",
    "severity": 2,
    "message": "use `Boolean(bar)` instead.",
    "line": 2,
    "column": 11,
    "nodeType": "UnaryExpression",
    "endLine": 2,
    "endColumn": 16,
    "fix": { "range": [23, 28], "text": "Boolean(bar)" }
}
```
@mdjermanovic Nice work. I think we can accept this issue based on that investigation.

...This raises an interesting thought. Should the autofix logic allow a fix range outside of the range of the reported node?
I'll fix this soon.

> ...This raises an interesting thought. Should the autofix logic allow a fix range outside of the range of the reported node?

Could be very interesting to test this, perhaps it would find similar bugs in other rules. 



Patch:
diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js
index 9bbd5546eddd..e818cd448c49 100644
--- a/lib/rules/no-extra-boolean-cast.js
+++ b/lib/rules/no-extra-boolean-cast.js
@@ -96,7 +96,7 @@ module.exports = {
                         grandparent.callee.name === "Boolean")
                 ) {
                     context.report({
-                        node,
+                        node: parent,
                         messageId: "unexpectedNegation",
                         fix: fixer => {
                             if (hasCommentsInside(parent)) {


Test patch:
diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js
index 1de914ca5850..a530f81f6037 100644
--- a/tests/lib/rules/no-extra-boolean-cast.js
+++ b/tests/lib/rules/no-extra-boolean-cast.js
@@ -40,7 +40,9 @@ ruleTester.run("no-extra-boolean-cast", rule, {
             output: "if (foo) {}",
             errors: [{
                 messageId: "unexpectedNegation",
-                type: "UnaryExpression"
+                type: "UnaryExpression",
+                column: 5,
+                endColumn: 10
             }]
         },
         {
@@ -48,7 +50,8 @@ ruleTester.run("no-extra-boolean-cast", rule, {
             output: "do {} while (foo)",
             errors: [{
                 messageId: "unexpectedNegation",
-                type: "UnaryExpression"
+                type: "UnaryExpression",
+                column: 14
             }]
         },
         {
@@ -56,7 +59,8 @@ ruleTester.run("no-extra-boolean-cast", rule, {
             output: "while (foo) {}",
             errors: [{
                 messageId: "unexpectedNegation",
-                type: "UnaryExpression"
+                type: "UnaryExpression",
+                column: 8
             }]
         },
         {
@@ -64,7 +68,8 @@ ruleTester.run("no-extra-boolean-cast", rule, {
             output: "foo ? bar : baz",
             errors: [{
                 messageId: "unexpectedNegation",
-                type: "UnaryExpression"
+                type: "UnaryExpression",
+                column: 1
             }]
         },
         {
@@ -72,7 +77,8 @@ ruleTester.run("no-extra-boolean-cast", rule, {
             output: "for (; foo;) {}",
             errors: [{
                 messageId: "unexpectedNegation",
-                type: "UnaryExpression"
+                type: "UnaryExpression",
+                column: 8
             }]
         },
         {
@@ -80,7 +86,8 @@ ruleTester.run("no-extra-boolean-cast", rule, {
             output: "!foo",
             errors: [{
                 messageId: "unexpectedNegation",
-                type: "UnaryExpression"
+                type: "UnaryExpression",
+                column: 2
             }]
         },
         {
@@ -88,7 +95,8 @@ ruleTester.run("no-extra-boolean-cast", rule, {
             output: "Boolean(foo)",
             errors: [{
                 messageId: "unexpectedNegation",
-                type: "UnaryExpression"
+                type: "UnaryExpression",
+                column: 9
             }]
         },
         {
@@ -96,7 +104,8 @@ ruleTester.run("no-extra-boolean-cast", rule, {
             output: "new Boolean(foo)",
             errors: [{
                 messageId: "unexpectedNegation",
-                type: "UnaryExpression"
+                type: "UnaryExpression",
+                column: 13
             }]
         },
         {