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

Issue:
Bug: issue when migrating to the new `groups` for `sort-object-types` / `sort-object-interfacs`
### Describe the bug

You can see in the commit below that despite the following `groups`:
```ts
  groups: [
    'required-index-signature',
    'optional-index-signature',
    'required-property',
    'optional-property',
    'required-method',
    'optional-method',
  ],
```

- `onSync?` is moved ahead of required methods, although it's an optional method: [here](https://github.com/OlivierZal/melcloud-api/commit/777fce9deea2018089209b00a86d3833bd568ecf#diff-e0899f9bf34e50cd996d55c63b887d2fbd99d1b9cf18264928709c28f33700baR86)
- `tiles` is a required method but is moved ahead of all the other required methods: every occurrence, e.g. [here](https://github.com/OlivierZal/melcloud-api/commit/777fce9deea2018089209b00a86d3833bd568ecf#diff-caf90ffb40aa6dfd6a5b98f50a7b43825066d1eafc25be1b2a4ba7103cca40cdR48)

It seems they are both considered to be properties instead of methods, probably because:
- the first is typed with an alias (`OnSyncFunction`)
- the second includes an intersection in its type

### Code example

https://github.com/OlivierZal/melcloud-api/commit/777fce9deea2018089209b00a86d3833bd568ecf

### ESLint version

v9.16.0

### ESLint Plugin Perfectionist version

v4.2.0

### 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:
feat: improves function detection
Fixes #409.

### Description

Allows union and intersection types resulting in functions to be detected, such as

```ts
(((v: false) => 'false') | ((v: true) => 'true')) & ((v: any) => any)
```

### 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 451f9fa..95c6647 100644
--- a/rules/sort-object-types.ts
+++ b/rules/sort-object-types.ts
@@ -27,6 +27,7 @@ import { singleCustomGroupJsonSchema } from './sort-object-types.types'
 import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled'
 import { allModifiers, allSelectors } from './sort-object-types.types'
 import { hasPartitionComment } from '../utils/is-partition-comment'
+import { isNodeFunctionType } from '../utils/is-node-function-type'
 import { sortNodesByGroups } from '../utils/sort-nodes-by-groups'
 import { getCommentsBefore } from '../utils/get-comments-before'
 import { makeNewlinesFixes } from '../utils/make-newlines-fixes'
@@ -256,11 +257,7 @@ export let sortObjectTypeElements = <MessageIds extends string>({
         selectors.push('index-signature')
       }
 
-      if (
-        typeElement.type === 'TSMethodSignature' ||
-        (typeElement.type === 'TSPropertySignature' &&
-          typeElement.typeAnnotation?.typeAnnotation.type === 'TSFunctionType')
-      ) {
+      if (isNodeFunctionType(typeElement)) {
         selectors.push('method')
       }
 
diff --git a/utils/is-node-function-type.ts b/utils/is-node-function-type.ts
new file mode 100644
index 0000000..8b7885b
--- /dev/null
+++ b/utils/is-node-function-type.ts
@@ -0,0 +1,14 @@
+import type { TSESTree } from '@typescript-eslint/types'
+
+export let isNodeFunctionType = (node: TSESTree.Node): boolean => {
+  if (node.type === 'TSMethodSignature' || node.type === 'TSFunctionType') {
+    return true
+  }
+  if (node.type === 'TSUnionType' || node.type === 'TSIntersectionType') {
+    return node.types.every(isNodeFunctionType)
+  }
+  if (node.type === 'TSPropertySignature' && node.typeAnnotation) {
+    return isNodeFunctionType(node.typeAnnotation.typeAnnotation)
+  }
+  return false
+}


Test patch:
diff --git a/test/sort-interfaces.test.ts b/test/sort-interfaces.test.ts
index d2d21de..ec37d50 100644
--- a/test/sort-interfaces.test.ts
+++ b/test/sort-interfaces.test.ts
@@ -1789,7 +1789,7 @@ describe(ruleName, () => {
           code: dedent`
               interface Interface {
                 b(): void
-                c: () => void
+                c: (((v: false) => 'false') | ((v: true) => 'true')) & ((v: any) => any)
                 a: string
                 d: string
               }
@@ -2773,7 +2773,7 @@ describe(ruleName, () => {
           code: dedent`
               interface Interface {
                 b(): void
-                c: () => void
+                c: (((v: false) => 'false') | ((v: true) => 'true')) & ((v: any) => any)
                 a: string
                 d: string
               }
@@ -3758,7 +3758,7 @@ describe(ruleName, () => {
         {
           code: dedent`
               interface Interface {
-                c: () => void
+                c: (((v: false) => 'false') | ((v: true) => 'true')) & ((v: any) => any)
                 b(): void
                 a: string
                 d: string
diff --git a/test/sort-object-types.test.ts b/test/sort-object-types.test.ts
index d0a815f..6fb5b8b 100644
--- a/test/sort-object-types.test.ts
+++ b/test/sort-object-types.test.ts
@@ -1613,7 +1613,7 @@ describe(ruleName, () => {
           code: dedent`
               type Type = {
                 b(): void
-                c: () => void
+                c: (((v: false) => 'false') | ((v: true) => 'true')) & ((v: any) => any)
                 a: string
                 d: string
               }
@@ -2395,7 +2395,7 @@ describe(ruleName, () => {
           code: dedent`
               type Type = {
                 b(): void
-                c: () => void
+                c: (((v: false) => 'false') | ((v: true) => 'true')) & ((v: any) => any)
                 a: string
                 d: string
               }
@@ -2977,7 +2977,7 @@ describe(ruleName, () => {
         {
           code: dedent`
               type Type = {
-                c: () => void
+                c: (((v: false) => 'false') | ((v: true) => 'true')) & ((v: any) => any)
                 b(): void
                 a: string
                 d: string