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

Issue:
Bug: sort-interfaces changes overload ordering for constructors
### Describe the bug

`sort-interfaces`  changes overload ordering of constructor in interfaces, which can cause existing code to break.

Method overloads do not change order, so constructors should not change too.

### Code example

```
interface Interface {
  new (value: number | string): number;
  new (value: number): unknown;
}
```

### ESLint version

latest

### ESLint Plugin Perfectionist version

latest

### Additional comments

I have written the following test to verify the issue in this repo.
The case with `new` clause fails, while other cases pass.

```js
ruleTester.run(`${ruleName}(${type}): sorts interface overloads`, rule, {
  invalid: [
    {
      errors: [
        {
          data: {
            right: 'method',
            left: 'methodA',
          },
          messageId: 'unexpectedInterfacePropertiesOrder',
        },
      ],
      output: dedent`
        interface Interface {
          method(value: number | string): number;
          method(value: number): unknown;

          methodA(value: number | string): number;
          methodA(value: number): unknown;
        }
      `,
      code: dedent`
        interface Interface {
          methodA(value: number | string): number;
          methodA(value: number): unknown;

          method(value: number | string): number;
          method(value: number): unknown;
        }
      `,
      options: [options],
    },
  ],
  valid: [
    {
       code: dedent`
         interface Interface {
           method(value: number | string): number;
           method(value: number): unknown;
         }
       `,
       options: [options],
     },
     {
       code: dedent`
         interface Interface {
           new (value: number | string): number;
           new (value: number): unknown;
         }
       `,
       options: [options],
     },
   ],
})
```

### 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(sort-interfaces): prevents constructor declarations from being sorted
Fixes #414

### Description

Similarly to call signatures, this PR prevents constructor signatures from being sorted together. 

```ts
interface Interface {
  new (value: number | string): number;
  new (value: number): unknown;
}
```

### Changes

- Fixes the bug
- Adds tests for both `sort-interfaces` and `sort-object-types`.
- Renames a few tests.

### What is the purpose of this pull request? <!-- (put an "X" next to an item) -->

- [x] Bug fix


Patch:
diff --git a/rules/sort-object-types.ts b/rules/sort-object-types.ts
index cbd5d69..3f6885c 100644
--- a/rules/sort-object-types.ts
+++ b/rules/sort-object-types.ts
@@ -210,7 +210,10 @@ export let sortObjectTypeElements = <MessageIds extends string>({
 
   let formattedMembers: SortObjectTypesSortingNode[][] = elements.reduce(
     (accumulator: SortObjectTypesSortingNode[][], typeElement) => {
-      if (typeElement.type === 'TSCallSignatureDeclaration') {
+      if (
+        typeElement.type === 'TSCallSignatureDeclaration' ||
+        typeElement.type === 'TSConstructSignatureDeclaration'
+      ) {
         accumulator.push([])
         return accumulator
       }
@@ -240,10 +243,8 @@ export let sortObjectTypeElements = <MessageIds extends string>({
         name = formatName(
           sourceCode.text.slice(typeElement.range.at(0), endIndex),
         )
-      } else if (
-        typeElement.type === 'TSMethodSignature' &&
-        'name' in typeElement.key
-      ) {
+      } else if ('name' in typeElement.key) {
+        // TSMethodSignature
         ;({ name } = typeElement.key)
         /* v8 ignore next 8 - Unsure if we can reach it */
       } else {


Test patch:
diff --git a/test/sort-interfaces.test.ts b/test/sort-interfaces.test.ts
index 3c79520..6acd0ef 100644
--- a/test/sort-interfaces.test.ts
+++ b/test/sort-interfaces.test.ts
@@ -477,7 +477,7 @@ describe(ruleName, () => {
     )
 
     ruleTester.run(
-      `${ruleName}(${type}): not sorts call signature declarations`,
+      `${ruleName}(${type}): does not sort call signature declarations`,
       rule,
       {
         valid: [
@@ -500,6 +500,34 @@ describe(ruleName, () => {
       },
     )
 
+    ruleTester.run(
+      `${ruleName}(${type}): does not sort constructor declarations`,
+      rule,
+      {
+        valid: [
+          {
+            code: dedent`
+              interface Interface {
+                new (value: number | string): number;
+                new (value: number): unknown;
+              }
+            `,
+            options: [options],
+          },
+          {
+            code: dedent`
+              interface Interface {
+                new (value: number): unknown;
+                new (value: number | string): number;
+              }
+            `,
+            options: [options],
+          },
+        ],
+        invalid: [],
+      },
+    )
+
     ruleTester.run(
       `${ruleName}(${type}): sorts complex predefined groups`,
       rule,
@@ -2462,7 +2490,7 @@ describe(ruleName, () => {
     )
 
     ruleTester.run(
-      `${ruleName}(${type}): not sorts call signature declarations`,
+      `${ruleName}(${type}): does not sort call signature declarations`,
       rule,
       {
         valid: [
@@ -3239,7 +3267,7 @@ describe(ruleName, () => {
     )
 
     ruleTester.run(
-      `${ruleName}(${type}): not sorts call signature declarations`,
+      `${ruleName}(${type}): does not sort call signature declarations`,
       rule,
       {
         valid: [
diff --git a/test/sort-object-types.test.ts b/test/sort-object-types.test.ts
index 8c8709f..52f347e 100644
--- a/test/sort-object-types.test.ts
+++ b/test/sort-object-types.test.ts
@@ -1834,6 +1834,57 @@ describe(ruleName, () => {
         valid: [],
       },
     )
+    ruleTester.run(
+      `${ruleName}(${type}): does not sort call signature declarations`,
+      rule,
+      {
+        valid: [
+          {
+            code: dedent`
+              type Type = {
+                <Parameters extends Record<string, number | string>>(
+                  input: AFunction<[Parameters], string>
+                ): Alternatives<Parameters>
+                <A extends CountA>(input: Input): AFunction<
+                  [number],
+                  A[keyof A]
+                >
+              }
+            `,
+            options: [options],
+          },
+        ],
+        invalid: [],
+      },
+    )
+
+    ruleTester.run(
+      `${ruleName}(${type}): does not sort constructor declarations`,
+      rule,
+      {
+        valid: [
+          {
+            code: dedent`
+              type Type = {
+                new (value: number | string): number;
+                new (value: number): unknown;
+              }
+            `,
+            options: [options],
+          },
+          {
+            code: dedent`
+              type Type = {
+                new (value: number): unknown;
+                new (value: number | string): number;
+              }
+            `,
+            options: [options],
+          },
+        ],
+        invalid: [],
+      },
+    )
   })
 
   describe(`${ruleName}: sorting by natural order`, () => {