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

Issue:
Bug: `fallbackSort` causes crash when sort much items
### Describe the bug

ESLint crashed when sorting 50+ items by `perfectionist/sort-exports` with `fallbackSort` enabled.

```bash
RangeError: Maximum call stack size exceeded
Occurred while linting /Users/ntnyq/Desktop/github/eslint/repro-perfectionist-fallback-sort/index.ts:3
Rule: "perfectionist/sort-exports"
    at [Symbol.replace] (<anonymous>)
    at String.replaceAll (<anonymous>)
    at /Users/ntnyq/Desktop/github/eslint/repro-perfectionist-fallback-sort/node_modules/.pnpm/eslint-plugin-perfectionist@4.9.0_eslint@9.20.1_typescript@5.7.3/node_modules/eslint-plugin-perfectionist/dist/utils/compare.js:138:25
    at /Users/ntnyq/Desktop/github/eslint/repro-perfectionist-fallback-sort/node_modules/.pnpm/eslint-plugin-perfectionist@4.9.0_eslint@9.20.1_typescript@5.7.3/node_modules/eslint-plugin-perfectionist/dist/utils/compare.js:45:5
    at compare (/Users/ntnyq/Desktop/github/eslint/repro-perfectionist-fallback-sort/node_modules/.pnpm/eslint-plugin-perfectionist@4.9.0_eslint@9.20.1_typescript@5.7.3/node_modules/eslint-plugin-perfectionist/dist/utils/compare.js:27:5)
    at compare (/Users/ntnyq/Desktop/github/eslint/repro-perfectionist-fallback-sort/node_modules/.pnpm/eslint-plugin-perfectionist@4.9.0_eslint@9.20.1_typescript@5.7.3/node_modules/eslint-plugin-perfectionist/dist/utils/compare.js:32:10)
    at compare (/Users/ntnyq/Desktop/github/eslint/repro-perfectionist-fallback-sort/node_modules/.pnpm/eslint-plugin-perfectionist@4.9.0_eslint@9.20.1_typescript@5.7.3/node_modules/eslint-plugin-perfectionist/dist/utils/compare.js:32:10)
    at compare (/Users/ntnyq/Desktop/github/eslint/repro-perfectionist-fallback-sort/node_modules/.pnpm/eslint-plugin-perfectionist@4.9.0_eslint@9.20.1_typescript@5.7.3/node_modules/eslint-plugin-perfectionist/dist/utils/compare.js:32:10)
    at compare (/Users/ntnyq/Desktop/github/eslint/repro-perfectionist-fallback-sort/node_modules/.pnpm/eslint-plugin-perfectionist@4.9.0_eslint@9.20.1_typescript@5.7.3/node_modules/eslint-plugin-perfectionist/dist/utils/compare.js:32:10)
    at compare (/Users/ntnyq/Desktop/github/eslint/repro-perfectionist-fallback-sort/node_modules/.pnpm/eslint-plugin-perfectionist@4.9.0_eslint@9.20.1_typescript@5.7.3/node_modules/eslint-plugin-perfectionist/dist/utils/compare.js:32:10)
```

### Code example

## Config

```ts
// @ts-check

import { config, configs } from 'typescript-eslint'
import pluginPerfectionist from 'eslint-plugin-perfectionist'

export default config(
  {
    extends: configs.recommended,
  },
  {
    plugins: {
      perfectionist: pluginPerfectionist,
    },
    rules: {
      'perfectionist/sort-exports': [
        'error',
        {
          fallbackSort: { type: 'alphabetical' },
          type: 'line-length',
        },
      ],
    },
  },
)
```

## Code

```ts
// @ts-nocheck

export * from './sortAttrs'
export * from './prefixIds'
export * from './cleanupIds'
export * from './mergePaths'
export * from './removeDesc'
export * from './reusePaths'
export * from './removeTitle'
export * from './mergeStyles'
export * from './removeXMLNS'
export * from './removeXlink'
export * from './removeAttrs'
export * from './inlineStyles'
export * from './cleanupAttrs'
export * from './minifyStyles'
export * from './convertColors'
export * from './presetDefault'
export * from './removeDoctype'
export * from './removeViewBox'
export * from './removeComments'
export * from './removeMetadata'
export * from './collapseGroups'
export * from './removeUnusedNS'
export * from './removeEmptyText'
export * from './convertPathData'
export * from './removeDimensions'
export * from './convertTransform'
export * from './sortDefsChildren'
export * from './removeEmptyAttrs'
export * from './removeHiddenElems'
export * from './removeUselessDefs'
export * from './removeXMLProcInst'
export * from './removeStyleElement'
export * from './removeRasterImages'
export * from './convertShapeToPath'
export * from './removeEditorsNSData'
export * from './convertStyleToAttrs'
export * from './removeEditorsNSData'
export * from './removeScriptElement'
export * from './cleanupListOfValues'
export * from './removeElementsByAttr'
export * from './cleanupNumericValues'
export * from './removeOffCanvasPaths'
export * from './moveElemsAttrsToGroup'
export * from './moveGroupAttrsToElems'
export * from './removeDeprecatedAttrs'
export * from './removeEmptyContainers'
export * from './convertEllipseToCircle'
export * from './addClassesToSVGElement'
export * from './convertOneStopGradients'
export * from './cleanupEnableBackground'
export * from './removeUnknownsAndDefaults'
export * from './addAttributesToSVGElement'
export * from './removeUselessStrokeAndFill'
export * from './removeAttributesBySelector'
export * from './removeNonInheritableGroupAttrs'
```

There is a minimal reproduction repo:

https://github.com/ntnyq-dev/repro-perfectionist-fallback-sort

### ESLint version

Latest

### ESLint Plugin Perfectionist version

Latest

### 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: fix maximum call stack size with fallback sort
Fixes #472

# Description

Duplicate entries can lead to a maximum call stack size error when using `fallbackSort`.

This PR fixes this bug.

## Critical level of this issue

- Only happens when users write duplicate entries.
  - Should only happen for `exports`.
  - Relatively low chance of happening.
-  Linked to a new feature.

=> Low criticality level.

## What is the purpose of this pull request?

- [x] Bug fix

Patch:
diff --git a/utils/compare.ts b/utils/compare.ts
index 6b58ac2..8421e9d 100644
--- a/utils/compare.ts
+++ b/utils/compare.ts
@@ -101,9 +101,11 @@ export let compare = <T extends SortingNode>(
   let { fallbackSort, order } = options
   return compare(a, b, {
     ...options,
+    fallbackSort: {
+      type: 'unsorted',
+    },
     order: fallbackSort.order ?? order,
     type: fallbackSort.type,
-    fallbackSort,
   } as CompareOptions<T>)
 }
 


Test patch:
diff --git a/test/utils/compare.test.ts b/test/utils/compare.test.ts
index e4d5a31..cc30175 100644
--- a/test/utils/compare.test.ts
+++ b/test/utils/compare.test.ts
@@ -330,6 +330,20 @@ describe('compare', () => {
         }),
       ).toBe(-1)
     })
+
+    it("doesn't sort using the fallback configuration more than once", () => {
+      let node = createTestNode({ name: 'aaa' })
+      let duplicateNode = createTestNode({ name: 'aaa' })
+      expect(
+        compare(node, duplicateNode, {
+          ...compareOptions,
+          fallbackSort: {
+            type: 'alphabetical',
+            order: 'asc',
+          } as const,
+        }),
+      ).toBe(0)
+    })
   })
 
   let createTestNode = ({ name }: { name: string }): SortingNode =>