Repository: ota-meshi/eslint-plugin-astro (ts)

Issue:
Warning position is wrong when the warning is in script and TypeScript parser is used
### Before You File a Bug Report Please Confirm You Have Done The Following...

- [X] I have tried restarting my IDE and the issue persists.
- [X] I have updated to the latest version of the packages.

### What version of ESLint are you using?

8.43.0

### What version of `eslint-plugin-astro` are you using?

0.27.1

### What did you do?

<details>
<summary>Configuration</summary>

```json
{
  "root": true,
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:astro/recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 2022,
    "parser": "@typescript-eslint/parser"
  }
}
```
</details>

```astro
<script>


  const foo = document.querySelector('foo')!
  console.log(foo)
</script>
```


### What did you expect to happen?

`eslint .` outputs
```
4:14  warning  Forbidden non-null assertion  @typescript-eslint/no-non-null-assertion
```

### What actually happened?

`eslint .` outputs
```
2:14  warning  Forbidden non-null assertion  @typescript-eslint/no-non-null-assertion
```

### Link to Minimal Reproducible Example

1. Open https://github.com/sapphi-red-repros/eslint-plugin-astro-script-ts-pos
1. `npm i`
2. `npm run lint`


### Additional comments

The warning position is correct for `.ts` files. So I thought this is a bug in eslint-plugin-astro instead of typescript-eslint.

Pull request description:
fix: wrong report position in client script with empty lines
fixes #219

Patch:
diff --git a/.changeset/forty-files-add.md b/.changeset/forty-files-add.md
new file mode 100644
index 0000000..cfc195e
--- /dev/null
+++ b/.changeset/forty-files-add.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-astro": patch
+---
+
+fix: wrong report position in client script with empty lines
diff --git a/src/shared/client-script/index.ts b/src/shared/client-script/index.ts
index 962c3f9..0c64c9f 100644
--- a/src/shared/client-script/index.ts
+++ b/src/shared/client-script/index.ts
@@ -15,7 +15,7 @@ class Locs {
     const lineStartIndices = [0]
 
     let index = 0
-    for (const line of lines) {
+    for (const line of lines[lines.length - 1] ? lines : lines.slice(0, -1)) {
       index += line.length
       lineStartIndices.push(index)
     }
@@ -100,9 +100,19 @@ export class ClientScript {
     for (let index = 0; index < lines.length; index++) {
       const line = lines[index]
       const lineIndent = Math.min(indent, line.length)
-      textLines.push(line.slice(lineIndent))
+      const lineText = line.slice(lineIndent)
+      if (lineText) {
+        textLines.push(line.slice(lineIndent))
+        remapColumnOffsets.push(lineIndent)
+      } else if (line.endsWith("\n")) {
+        const eol = line.endsWith("\r\n") ? "\r\n" : "\n"
+        textLines.push(eol)
+        remapColumnOffsets.push(line.length - eol.length)
+      } else {
+        textLines.push("")
+        remapColumnOffsets.push(lineIndent)
+      }
       remapLines.push(startLoc.line + index + 1)
-      remapColumnOffsets.push(lineIndent)
     }
 
     const text = textLines.join("")
@@ -135,7 +145,11 @@ export class ClientScript {
     /** Remap range */
     const remapRange = (range: [number, number]): [number, number] | null => {
       const startLoc = textLocs.getLocFromIndex(range[0])
-      const endLoc = textLocs.getLocFromIndex(range[1])
+      const normalEndLoc = textLocs.getLocFromIndex(range[1])
+      const endLoc =
+        normalEndLoc.column > 0
+          ? normalEndLoc
+          : textLocs.getLocFromIndex(range[1] - 1)
       const remappedStartLoc = remapLoc(startLoc)
       const remappedEndLoc = remapLoc(endLoc)
       if (remappedStartLoc.line < 0 || remappedEndLoc.line < 0) {
@@ -143,7 +157,8 @@ export class ClientScript {
       }
       return [
         this.parsed.getIndexFromLoc(remappedStartLoc),
-        this.parsed.getIndexFromLoc(remappedEndLoc),
+        this.parsed.getIndexFromLoc(remappedEndLoc) +
+          (normalEndLoc.column > 0 ? 0 : 1),
       ]
     }
 


Test patch:
diff --git a/tests/src/integration/linebreak-style.ts b/tests/src/integration/linebreak-style.ts
new file mode 100644
index 0000000..6ef84b5
--- /dev/null
+++ b/tests/src/integration/linebreak-style.ts
@@ -0,0 +1,91 @@
+import { RuleTester, Linter } from "eslint"
+import { processor } from "../../../src/processor"
+
+describe("Integration test for linebreak-style", () => {
+  const ruleNoUnusedVars = new Linter().getRules().get("linebreak-style")!
+  const tester = new RuleTester({
+    parser: require.resolve("./auto-parser"),
+    parserOptions: {
+      ecmaVersion: 2020,
+      sourceType: "module",
+    },
+    globals: {
+      console: false,
+    },
+  })
+  tester.run("linebreak-style", ruleNoUnusedVars, {
+    valid: [
+      {
+        // @ts-expect-error -- fine name with processor
+        filename: {
+          filename: "foo.astro",
+          ...processor,
+        },
+        code: `
+        <script define:vars={{ foo: 42, bar: 42 }}>
+          console.log(foo)
+        </script>
+        `,
+      },
+      {
+        // @ts-expect-error -- fine name with processor
+        filename: {
+          filename: "foo.astro",
+          ...processor,
+        },
+        code: `
+<script define:vars={{ bar: 42 }}>
+
+
+  // empty lines
+  console.log(foo)
+</script>
+        `,
+      },
+    ],
+    invalid: [
+      {
+        // @ts-expect-error -- fine name with processor
+        filename: {
+          filename: "foo.astro",
+          ...processor,
+        },
+        code: `{/*eslint linebreak-style:0*/}
+        <script define:vars={{ bar: 42 }}>
+          console.log(foo)\r
+        </script>
+        `,
+        output: `{/*eslint linebreak-style:0*/}
+        <script define:vars={{ bar: 42 }}>
+          console.log(foo)
+        </script>
+        `,
+        errors: 1,
+      },
+      {
+        // @ts-expect-error -- fine name with processor
+        filename: {
+          filename: "foo.astro",
+          ...processor,
+        },
+        code: `{/*eslint linebreak-style:0*/}
+<script define:vars={{ bar: 42 }}>\r
+\r
+\r
+  // empty lines\r
+  console.log(foo)\r
+</script>
+        `,
+        output: `{/*eslint linebreak-style:0*/}
+<script define:vars={{ bar: 42 }}>\r
+
+\r
+  // empty lines
+  console.log(foo)
+</script>
+        `,
+        errors: 4,
+      },
+    ],
+  })
+})
diff --git a/tests/src/integration/no-undef.ts b/tests/src/integration/no-undef.ts
index 393da93..9cfc595 100644
--- a/tests/src/integration/no-undef.ts
+++ b/tests/src/integration/no-undef.ts
@@ -48,6 +48,50 @@ describe("Integration test for no-undef", () => {
           },
         ],
       },
+      {
+        // @ts-expect-error -- fine name with processor
+        filename: {
+          filename: "foo.astro",
+          ...processor,
+        },
+        code: `
+<script define:vars={{ bar: 42 }}>
+
+
+  // empty lines
+  console.log(foo)
+</script>
+        `,
+        errors: [
+          {
+            message: "'foo' is not defined.",
+            line: 6,
+            column: 15,
+          },
+        ],
+      },
+      {
+        // @ts-expect-error -- fine name with processor
+        filename: {
+          filename: "foo.astro",
+          ...processor,
+        },
+        code: `
+<script define:vars={{ bar: 42 }}>
+
+
+  // empty lines
+  console.log(foo)
+</script>
+        `.replace(/\n/gu, "\r\n"),
+        errors: [
+          {
+            message: "'foo' is not defined.",
+            line: 6,
+            column: 15,
+          },
+        ],
+      },
     ],
   })
 })