Repository: azat-io/eslint-plugin-perfectionist (ts)

Issue:
Bug: Shebang comments are not ignored
### Describe the bug

Issue detected here: https://github.com/azat-io/eslint-plugin-perfectionist/issues/479#issuecomment-2841808874

Shebang comments are not ignored and will be moved around, resulting in code that does not compile. See code example.

### Code example

Code input (alphabetical sort):

```ts
#!/usr/bin/node
import { b } from "b";
import { a } from "a";
```

💥 Result:

```ts
import { b } from "b";
#!/usr/bin/node
import { a } from "a";
```

### ESLint version

8.57.0

### ESLint Plugin Perfectionist version

4.12.3

### Additional comments

_No response_

### Validations

- [x] Read the [docs](https://perfectionist.dev).
- [x] Check that there isn't [already an issue](https://github.com/azat-io/eslint-plugin-perfectionist/issues) that reports the same bug to avoid creating a duplicate.

Pull request:
fix: ignore Shebang comments
Fixes #539.

### Description

This PR makes comment fixes ignore shebang comments.

### What is the purpose of this pull request?

- [x] Bug fix


Patch:
diff --git a/utils/get-comments-before.ts b/utils/get-comments-before.ts
index 24a6af9..b7e882a 100644
--- a/utils/get-comments-before.ts
+++ b/utils/get-comments-before.ts
@@ -9,7 +9,7 @@ interface GetCommentsBeforeParameters {
 
 /**
  * Returns a list of comments before a given node, excluding ones that are
- * right after code. Includes comment blocks.
+ * right after code. Includes comment blocks, ignore shebang comments.
  * @param {object} params - Parameters object.
  * @param {TSESTree.Node} params.node - The node to get comments before.
  * @param {TSESLint.SourceCode} params.sourceCode - The source code object.
@@ -22,7 +22,7 @@ export let getCommentsBefore = ({
   sourceCode,
   node,
 }: GetCommentsBeforeParameters): TSESTree.Comment[] => {
-  let commentsBefore = getCommentsBeforeNodeOrToken(sourceCode, node)
+  let commentsBefore = getRelevantCommentsBeforeNodeOrToken(sourceCode, node)
   let tokenBeforeNode = sourceCode.getTokenBefore(node)
   if (
     commentsBefore.length > 0 ||
@@ -31,18 +31,25 @@ export let getCommentsBefore = ({
   ) {
     return commentsBefore
   }
-  return getCommentsBeforeNodeOrToken(sourceCode, tokenBeforeNode)
+  return getRelevantCommentsBeforeNodeOrToken(sourceCode, tokenBeforeNode)
 }
 
-let getCommentsBeforeNodeOrToken = (
+let getRelevantCommentsBeforeNodeOrToken = (
   source: TSESLint.SourceCode,
   node: TSESTree.Token | TSESTree.Node,
 ): TSESTree.Comment[] =>
-  source.getCommentsBefore(node).filter(comment => {
-    /**
-     * `getCommentsBefore` also returns comments that are right after code,
-     * filter those out
-     */
-    let tokenBeforeComment = source.getTokenBefore(comment)
-    return tokenBeforeComment?.loc.end.line !== comment.loc.end.line
-  })
+  source
+    .getCommentsBefore(node)
+    .filter(comment => !isShebangComment(comment))
+    .filter(comment => {
+      /**
+       * `getCommentsBefore` also returns comments that are right after code,
+       * filter those out
+       */
+      let tokenBeforeComment = source.getTokenBefore(comment)
+      return tokenBeforeComment?.loc.end.line !== comment.loc.end.line
+    })
+
+let isShebangComment = (comment: TSESTree.Comment): boolean =>
+  comment.type === ('Shebang' as unknown) ||
+  comment.type === ('Hashbang' as unknown)


Test patch:
diff --git a/test/rules/sort-imports.test.ts b/test/rules/sort-imports.test.ts
index 304b667..3ca3346 100644
--- a/test/rules/sort-imports.test.ts
+++ b/test/rules/sort-imports.test.ts
@@ -3934,6 +3934,34 @@ describe(ruleName, () => {
         },
       )
     })
+
+    ruleTester.run(`${ruleName}(${type}): ignores shebang comments`, rule, {
+      invalid: [
+        {
+          errors: [
+            {
+              data: {
+                right: 'a',
+                left: 'b',
+              },
+              messageId: 'unexpectedImportsOrder',
+            },
+          ],
+          output: dedent`
+            #!/usr/bin/node
+            import { a } from 'a'
+            import { b } from 'b'
+          `,
+          code: dedent`
+            #!/usr/bin/node
+            import { b } from 'b'
+            import { a } from 'a'
+          `,
+          options: [options],
+        },
+      ],
+      valid: [],
+    })
   })
 
   describe(`${ruleName}: sorting by natural order`, () => {