Skip to content

Conversation

fasttime
Copy link
Member

@fasttime fasttime commented May 29, 2025

Prerequisites checklist

What is the purpose of this pull request? (put an "X" next to an item)

[ ] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofix to a rule
[ ] Add a CLI option
[X] Add something to the core
[ ] Other, please explain:

closes #3565

What changes did you make? (Give an overview)

This pull request implements eslint/rfcs#129, featuring multithread linting and option modules.

Is there anything you'd like reviewers to focus on?


Testing the prototype

To test this in a local package:

npm i -D eslint/eslint#rfc129
node ./node_modules/.bin/eslint --concurrency=auto

Debug Messages

These are some interesting debug messages.

Number of enumerated files, time spent to enumerate files in the main thread, and effective number of worker threads:

2025-06-06T06:12:17.489Z eslint:eslint 861 files found in: 1861ms
2025-06-06T06:12:17.489Z eslint:eslint Linting using 4 worker thread(s).

Alternatively, if no concurrency is used:

2025-06-06T06:30:37.419Z eslint:eslint Linting without concurrency.

Time to load dependencies in a worker thread:

2025-06-06T06:20:33.394Z eslint:worker:thread-1 Dependencies loaded in 206 ms
2025-06-06T06:20:33.400Z eslint:worker:thread-4 Dependencies loaded in 214 ms
2025-06-06T06:20:33.400Z eslint:worker:thread-2 Dependencies loaded in 211 ms
2025-06-06T06:20:33.407Z eslint:worker:thread-3 Dependencies loaded in 217 ms

Additional time to initialize a worker thread after dependencies are loaded:

2025-06-06T06:20:33.407Z eslint:worker:thread-1 Linting started 13 ms after dependencies loaded
2025-06-06T06:20:33.415Z eslint:worker:thread-4 Linting started 15 ms after dependencies loaded
2025-06-06T06:20:33.417Z eslint:worker:thread-2 Linting started 18 ms after dependencies loaded
2025-06-06T06:20:33.425Z eslint:worker:thread-3 Linting started 18 ms after dependencies loaded

Time to load a config array in a worker thread. It takes long to load a config array the first time, successive loads use the cache and are much faster:

2025-06-06T06:20:35.182Z eslint:worker:thread-3 Config array for file "...\eslint\.github\ISSUE_TEMPLATE\bug-report.yml" loaded in 1757 ms
2025-06-06T06:20:35.186Z eslint:worker:thread-4 Config array for file "...\eslint\.codeclimate.yml" loaded in 1771 ms
2025-06-06T06:20:35.194Z eslint:worker:thread-1 Config array for file "...\eslint\.c8rc" loaded in 1788 ms

Time to read a file to be linted:

2025-06-06T06:20:35.208Z eslint:eslint-helpers:thread-3 File "...\eslint\.github\ISSUE_TEMPLATE\bug-report.yml" read in 1 ms
2025-06-06T06:20:35.209Z eslint:eslint-helpers:thread-4 File "...\eslint\.codeclimate.yml" read in 1 ms
2025-06-06T06:20:35.211Z eslint:eslint-helpers:thread-1 File "...\eslint\.c8rc" read in 1 ms

Time spent by the linter on a file:

2025-06-06T06:20:35.218Z eslint:eslint-helpers:thread-1 File "...\eslint\.c8rc" linted in 7 ms

Total time spent on a file in a worker thread. This includes loading the config array, reading the file, linting the file, and a little overhead:

2025-06-06T06:20:35.218Z eslint:worker:thread-1 File "...\eslint\.c8rc" processed in 1811 ms

Benchmark References

ESLint
Repo: https://github.com/eslint/eslint
Files: <1000
Commands:

git clone --no-tags --single-branch https://github.com/eslint/eslint -b rfc129
cd eslint
npm i
npm --prefix docs i
node bin/eslint.js --concurrency=auto

OpenUI5
Repo: https://github.com/mauriciolauffer/openui5/tree/eslint-v9
Files: ~12500
Commands:

git clone --no-tags --single-branch https://github.com/mauriciolauffer/openui5 -b eslint-v9
cd openui5
npm i -D eslint/eslint#rfc129
node node_modules/eslint/bin/eslint.js --concurrency=auto src --quiet

core-js
Repo: https://github.com/zloirock/core-js
Files: ~8000
Commands:

git clone --no-tags --single-branch https://github.com/zloirock/core-js
cd core-js
npm i
npm --prefix tests/eslint i -D eslint/eslint#rfc129
# Fails on Windows
node tests/eslint/node_modules/eslint/bin/eslint.js --concurrency=auto --config=tests/eslint/eslint.config.js

Archived Topics

#### `ESLint#getRulesMetaForResults()`

This section describes three different solutions, each implemented in its own branch, for the handling of ESLint#getRulesMetaForResults() as discussed in the RFC (see ESLint#getRulesMetaForResults()).

To recap, the findFiles function currently loads config files for files it finds by resolving glob patterns, and it will continue to do so in multithread mode. But if a file path is passed explicitly (i.e. not matched by a glob pattern), then findFiles does not try to load a config file for it. The problem with the getRulesMetaForResults method in its present implementation, is that this method requires config files for all linted files to have been loaded in the current thread. This means that in order for getRulesMetaForResults to continue working in multithread mode, either config files for all explicitly passed files must be loaded in the controlling thread — additionally to worker threads — during the lintFiles invocation, or getRulesMetaForResults must be changed to have the information it requires calculated by worker threads.

rfc129 (this pull request)
This is based on Solution 1 shown in the RFC. This implementation ensures that the config files for all enumerated files are loaded in the controlling thread while worker threads are running. If getRulesMetaForResults is not called after lintFiles, it's possible that some config files will be loaded unnecessarily.

rfc129-b
Based on eslint/rfcs#129 (comment). All config files are loaded by findFiles before the worker threads are created, without using limited concurrency. This is similar to the previous implementation in terms of performance, but it simplifies the code and ensures that later access to config files can be done synchronously.

rfc129-c
Based on Solution 2 shown in the RFC.
Rules meta objects and usedDeprecatedRules for all results are precalculated in the worker threads. This solution requires some important refactiongs and it adds complexity to the existing logic, for example an additional field on the ESLint instance to store rules meta objects or a BroadcastChannel to send warnings to the main thread. The advantage is that config files for explicitly passed filenames don't need to be loaded in the controlling thread. This yields a measurable performance improvement especially when filenames are passed explicitly to lintFiles.

Design Questions

Handling --concurrency=1
Running ESLint from the CLI with --concurrency=1 performs worse than linting in the main thread (i.e. without the --concurrency flag or with --concurrency=off) because spinning up a worker thread adds extra overhead.

That said, a concurrency value of 1 can be useful for testing or debugging, and it could make sense when using the using the Node.js API, because if other asynchronous tasks are running in the main thread, linting in a separate thread can improve performance. However, the CLI already launches ESLint in its own dedicated process with no competing async work, so specifying --concurrency=1 there will always degrade performance.

To avoid confusion, we could forbid --concurrency=1 when ESLint is invoked via the CLI. Another option could be emitting a warning whenever concurrency is set to 1, regardless of how ESLint is started. What do you think is the best way to handle this case?

Should ESLint.fromOptionModule() accept Unix pathnames?
Per the RFC, ESLint.fromOptionModule() accepts only a URL. I have realized that the original design could be adapted to also accept a Unix pathname like other APIs do. For example, dynamic imports can use absolute paths (import("/path/to/file.js")) or relative paths (import("./file.js")) besides file URLs. This would be just a minor enhancement to the developer experience, as file paths would continue to be converted to file URLs behind the scenes. Should ESLint.fromOptionModule() accept Unix pathnames besides URLs? If so, should relative filenames be allowed and resolved from the current working directory?

Should TypeScript option modules be loaded like TypeScript config files?
Thanks to built-in type stripping, Node.js 24 permits loading TypeScript files as option modules out of the box, with a URL like file:///path/to/eslint-options.ts. Bun and Deno recognize TypeScript syntax natively. Nonetheless, if we wish to align the way option modules are loaded with how config files are loaded, we could consider allowing native loading of TypeScript files behind a new feature flag, similar to unstable_native_nodejs_ts_config, and using jiti otherwise. I'm not sure how useful this approach would be in practice, and I doubt it's worth the added complexity, but it's good to discuss it in advance. Once option modules are released, changing the way files are loaded will be probably a breaking change.

Should the concurrency option be forbidden in the ESLint constructor?
Another point raised in the RFC was the strategy to guarantee that all options are serializable when concurrency is enabled. One solution that we did not consider is to completely disallow the concurrency option in the ESLint constructor for users. This would imply that the only way to specify concurrency would be through an option module. While this would eliminate the need to check the options when concurrency is specified, it would also sacrifice usability, so I'm not sure if we want to do this.

@fasttime fasttime added the do not merge This pull request should not be merged yet label May 29, 2025
@github-project-automation github-project-automation bot moved this to Needs Triage in Triage May 29, 2025
@eslint-github-bot eslint-github-bot bot added the feature This change adds a new feature to ESLint label May 29, 2025
Copy link

netlify bot commented May 29, 2025

Deploy Preview for docs-eslint canceled.

Name Link
🔨 Latest commit 2aba68b
🔍 Latest deploy log https://app.netlify.com/projects/docs-eslint/deploys/6898e454d740ba0008a27fdc

@github-actions github-actions bot added cli Relates to ESLint's command-line interface core Relates to ESLint's core APIs and features github actions labels May 29, 2025
@lumirlumir
Copy link
Member

Would it make sense to link this issue to #3565?

@lumirlumir lumirlumir linked an issue May 30, 2025 that may be closed by this pull request
@fasttime
Copy link
Member Author

Sure! I forgot to add the link in the description.

@nzakas
Copy link
Member

nzakas commented Jun 4, 2025

I did some initial testing, and on my machine (Windows 11, 8 logical cores), concurrency seems to be slower. I just ran ESLint on itself.

Results
nzaka@X1-Laptop MINGW64 ~/projects/eslint/eslint (rfc129)
$ time node bin/eslint.js .
real    1m24.218s
user    0m0.514s
sys     0m1.014s

nzaka@X1-Laptop MINGW64 ~/projects/eslint/eslint (rfc129)
$ time node bin/eslint.js . --concurrency auto

real    2m2.273s
user    0m0.544s
sys     0m1.639s

nzaka@X1-Laptop MINGW64 ~/projects/eslint/eslint (rfc129)
$ time node bin/eslint.js . --concurrency auto

real    1m43.752s
user    0m0.468s
sys     0m1.872s

nzaka@X1-Laptop MINGW64 ~/projects/eslint/eslint (rfc129)
$ time node bin/eslint.js . --concurrency 2

real    1m34.724s
user    0m0.607s
sys     0m1.966s

$ time node bin/eslint.js . --concurrency 3

real    1m31.344s
user    0m0.622s
sys     0m1.232s

$ time node bin/eslint.js . --concurrency 4

real    1m46.929s
user    0m0.529s
sys     0m1.170s

$ time node bin/eslint.js . --concurrency 6

real    1m43.638s
user    0m0.592s
sys     0m1.451s

Would be good to get some benchmarking on other machines as well.

@faultyserver
Copy link

Benchmarked runnning on my M2 Max laptop (12 logical cores, 8 performance, 4 efficiency):

Results
% hyperfine --runs 5 --parameter-list threads 1,2,4,6,8,10,auto 'node bin/eslint.js . --concurrency {threads}'       
Benchmark 1: node bin/eslint.js . --concurrency 1
  Time (mean ± σ):     14.038 s ±  0.316 s    [User: 20.281 s, System: 1.362 s]
  Range (min … max):   13.592 s … 14.372 s    5 runs
 
Benchmark 2: node bin/eslint.js . --concurrency 2
  Time (mean ± σ):      8.672 s ±  0.097 s    [User: 24.727 s, System: 1.653 s]
  Range (min … max):    8.576 s …  8.784 s    5 runs
 
Benchmark 3: node bin/eslint.js . --concurrency 4
  Time (mean ± σ):      6.744 s ±  0.121 s    [User: 33.784 s, System: 2.490 s]
  Range (min … max):    6.589 s …  6.910 s    5 runs
 
Benchmark 4: node bin/eslint.js . --concurrency 6
  Time (mean ± σ):      6.609 s ±  0.057 s    [User: 44.733 s, System: 3.540 s]
  Range (min … max):    6.524 s …  6.672 s    5 runs
 
Benchmark 5: node bin/eslint.js . --concurrency 8
  Time (mean ± σ):      7.575 s ±  0.336 s    [User: 57.130 s, System: 5.370 s]
  Range (min … max):    7.201 s …  8.091 s    5 runs
 
Benchmark 6: node bin/eslint.js . --concurrency 10
  Time (mean ± σ):      7.621 s ±  0.199 s    [User: 63.501 s, System: 6.349 s]
  Range (min … max):    7.434 s …  7.950 s    5 runs
 
Benchmark 7: node bin/eslint.js . --concurrency auto
  Time (mean ± σ):      6.596 s ±  0.065 s    [User: 44.595 s, System: 3.664 s]
  Range (min … max):    6.553 s …  6.706 s    5 runs
 
  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
 
Summary
  node bin/eslint.js . --concurrency auto ran
    1.00 ± 0.01 times faster than node bin/eslint.js . --concurrency 6
    1.02 ± 0.02 times faster than node bin/eslint.js . --concurrency 4
    1.15 ± 0.05 times faster than node bin/eslint.js . --concurrency 8
    1.16 ± 0.03 times faster than node bin/eslint.js . --concurrency 10
    1.31 ± 0.02 times faster than node bin/eslint.js . --concurrency 2
    2.13 ± 0.05 times faster than node bin/eslint.js . --concurrency 1

% hyperfine --runs 5 'node bin/eslint.js .'                 
Benchmark 1: node bin/eslint.js .
  Time (mean ± σ):     13.218 s ±  0.250 s    [User: 19.310 s, System: 1.222 s]
  Range (min … max):   13.037 s … 13.652 s    5 runs
 
%

As expected, using about half of the available cores ended up being the fastest overall execution. Not using the --concurrency option at all does run faster than with concurrency of 1, which I believe is also expected to an extent since there's overhead of using a worker even if it's only a single one.

@nzakas
Copy link
Member

nzakas commented Jun 4, 2025

@fasttime it may be helpful to add in some debug statements with profiling data to see where the time is being spent.

@fasttime
Copy link
Member Author

fasttime commented Jun 6, 2025

Not using the --concurrency option at all does run faster than with concurrency of 1, which I believe is also expected to an extent since there's overhead of using a worker even if it's only a single one.

@faultyserver Indeed yes, it's expected. That's the reason why --concurrency=auto never picks 1 as the number of threads, and instead prefers running without multithreading when less than 4 cores are available. A concurrency value of 1 is primarily useful for Node.js API consumers that wish to run ESLint in a separate thread while keeping the calling thread free for other tasks. I'm not sure there's a utility in running with --concurrency=1 from the CLI, so maybe we could consider prohibiting that setting.

@nzakas I've added some debug messages, all details are in the PR description. Please check if this helps to make sense of the slow results.

Thanks for testing!

@nzakas nzakas moved this from Needs Triage to Implementing in Triage Jun 10, 2025
@nzakas
Copy link
Member

nzakas commented Jun 10, 2025

With the latest changes, concurrency=auto runs in roughly the same time as running without concurrency (1m55s), so that's an improvement.

As an experiment, I eliminated the use of the Retrier completely and that brought the time down to 1m30s. I don't think removing the Retrier completely will work on larger projects, but it at least gives us an idea of where some of the time is being spent.

@fasttime
Copy link
Member Author

As an experiment, I eliminated the use of the Retrier completely and that brought the time down to 1m30s. I don't think removing the Retrier completely will work on larger projects, but it at least gives us an idea of where some of the time is being spent.

Maybe there is room for improvement in the Retrier logic, as that should be a thin wrapper around the main functionality, I guess. In multithread mode, each worker thread will read at most one file at a time, such that no limited concurrency is necessary even when using the Retrier. This significantly lowers the chances of running into EMFILE and ENFILE errors. I think these errors could still occur if a plugin opens many files at the same time, or if the ESLint API is used to lint files in a process that has many other files open.

@nzakas
Copy link
Member

nzakas commented Jun 11, 2025

I agree, the chances are diminished inside of workers but aren't 0. Because removing the retrier sped things up significantly, it seems worth investigating a lighter-weight option to use inside of workers.

@DreierF
Copy link

DreierF commented Jun 12, 2025

As you asked for further benchmarks, I also gave the current version a try:

  • The codebase has around 2.1k Typescript files
  • The test was run on my M1 Max (8 performance, 2 efficiency cores)
  • Our config contains a bunch of plugins: typescript-eslint (with typed lining rules), react, react-hooks, storybook
Results $ hyperfine --runs 3 --parameter-list threads 1,2,4,6,8,10,auto 'node ./node_modules/.bin/eslint --concurrency {threads}' Benchmark 1: node ./node_modules/.bin/eslint --concurrency 1 Time (mean ± σ): 77.193 s ± 0.800 s [User: 106.509 s, System: 5.332 s] Range (min … max): 76.318 s … 77.885 s 3 runs

Benchmark 2: node ./node_modules/.bin/eslint --concurrency 2
Time (mean ± σ): 51.667 s ± 1.110 s [User: 134.878 s, System: 10.135 s]
Range (min … max): 50.388 s … 52.381 s 3 runs

Benchmark 3: node ./node_modules/.bin/eslint --concurrency 4
Time (mean ± σ): 41.928 s ± 0.499 s [User: 184.212 s, System: 24.178 s]
Range (min … max): 41.579 s … 42.499 s 3 runs

Benchmark 4: node ./node_modules/.bin/eslint --concurrency 6
Time (mean ± σ): 49.671 s ± 3.048 s [User: 248.316 s, System: 45.340 s]
Range (min … max): 46.422 s … 52.467 s 3 runs

Benchmark 5: node ./node_modules/.bin/eslint --concurrency 8
Time (mean ± σ): 62.900 s ± 0.948 s [User: 307.563 s, System: 68.535 s]
Range (min … max): 61.830 s … 63.636 s 3 runs

Benchmark 6: node ./node_modules/.bin/eslint --concurrency 10
Time (mean ± σ): 86.464 s ± 1.176 s [User: 397.564 s, System: 99.125 s]
Range (min … max): 85.337 s … 87.683 s 3 runs

Benchmark 7: node ./node_modules/.bin/eslint --concurrency auto
Time (mean ± σ): 47.486 s ± 2.193 s [User: 216.097 s, System: 39.932 s]
Range (min … max): 45.644 s … 49.911 s 3 runs

Summary
node ./node_modules/.bin/eslint --concurrency 4 ran
1.13 ± 0.05 times faster than node ./node_modules/.bin/eslint --concurrency auto
1.18 ± 0.07 times faster than node ./node_modules/.bin/eslint --concurrency 6
1.23 ± 0.03 times faster than node ./node_modules/.bin/eslint --concurrency 2
1.50 ± 0.03 times faster than node ./node_modules/.bin/eslint --concurrency 8
1.84 ± 0.03 times faster than node ./node_modules/.bin/eslint --concurrency 1
2.06 ± 0.04 times faster than node ./node_modules/.bin/eslint --concurrency 10

$ hyperfine --runs 3 'node ./node_modules/.bin/eslint'
Benchmark 1: node ./node_modules/.bin/eslint
Time (mean ± σ): 60.414 s ± 1.457 s [User: 88.568 s, System: 4.818 s]
Range (min … max): 58.772 s … 61.554 s 3 runs

@mauriciolauffer
Copy link

mauriciolauffer commented Jun 12, 2025

Benchmark from https://github.com/mauriciolauffer/openui5/tree/eslint-v9
Total files: over 40k
Windows 11
CPU: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz 4 cores - 8 logical processors
Memory: 16GB
Nodejs: v22.16.0
NPM: v10.9.2

Results > concurrency ON hyperfine --runs 5 --parameter-list threads 2,3,4,auto 'node .\node_modules\eslint\bin\eslint.js src --quiet --concurrency {threads}'

...

Benchmark 1: node .\node_modules\eslint\bin\eslint.js src --quiet --concurrency 2
Time (mean ± σ): 89.203 s ± 6.220 s [User: 234.854 s, System: 7.780 s]
Range (min … max): 81.790 s … 95.910 s 5 runs

Benchmark 2: node .\node_modules\eslint\bin\eslint.js src --quiet --concurrency 3
Time (mean ± σ): 96.720 s ± 37.947 s [User: 310.767 s, System: 9.552 s]
Range (min … max): 78.564 s … 164.549 s 5 runs

Benchmark 3: node .\node_modules\eslint\bin\eslint.js src --quiet --concurrency 4
Time (mean ± σ): 72.272 s ± 0.922 s [User: 347.995 s, System: 11.208 s]
Range (min … max): 70.700 s … 72.964 s 5 runs

Benchmark 4: node .\node_modules\eslint\bin\eslint.js src --quiet --concurrency auto
Time (mean ± σ): 86.999 s ± 26.697 s [User: 359.723 s, System: 11.605 s]
Range (min … max): 71.924 s … 134.154 s 5 runs

Summary
node .\node_modules\eslint\bin\eslint.js src --quiet --concurrency 4 ran
1.20 ± 0.37 times faster than node .\node_modules\eslint\bin\eslint.js src --quiet --concurrency auto
1.23 ± 0.09 times faster than node .\node_modules\eslint\bin\eslint.js src --quiet --concurrency 2
1.34 ± 0.53 times faster than node .\node_modules\eslint\bin\eslint.js src --quiet --concurrency 3

Results > concurrency OFF hyperfine --runs 5 'node .\node_modules\eslint\bin\eslint.js src --quiet'

...

Benchmark 1: node .\node_modules\eslint\bin\eslint.js src --quiet
Time (mean ± σ): 144.034 s ± 26.503 s [User: 206.331 s, System: 7.549 s]
Range (min … max): 118.646 s … 187.800 s 5 runs

@stefanmaric
Copy link

For this react project:

===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
 TSX                  1239       192203       173348          629        18226
 JSX                   204        39714        35415           95         4204
 TypeScript            529        27903        22839         1290         3774
 JavaScript             19          907          732           77           98
===============================================================================
 Total                1991       260727       232334         2091        26302
===============================================================================
Example ESLint config
{
  "settings": {
    "import-x/resolver-next": [
      {
        "interfaceVersion": 3,
        "name": "eslint-import-resolver-typescript"
      }
    ],
    "polyfills": [
      "navigator.serviceWorker",
      "Notification",
      "requestIdleCallback"
    ],
    "react": {
      "version": "18"
    },
    "import-x/extensions": [
      ".ts",
      ".tsx",
      ".cts",
      ".mts",
      ".js",
      ".jsx",
      ".cjs",
      ".mjs"
    ],
    "import-x/external-module-folders": [
      "node_modules",
      "node_modules/@types"
    ],
    "import-x/parsers": {
      "@typescript-eslint/parser": [
        ".ts",
        ".tsx",
        ".cts",
        ".mts"
      ]
    },
    "import-x/resolver": {
      "typescript": true
    }
  },
  "linterOptions": {
    "reportUnusedDisableDirectives": 1
  },
  "rules": {
    "constructor-super": [
      0
    ],
    "for-direction": [
      2
    ],
    "getter-return": [
      0,
      {
        "allowImplicit": false
      }
    ],
    "no-async-promise-executor": [
      2
    ],
    "no-case-declarations": [
      2
    ],
    "no-class-assign": [
      0
    ],
    "no-compare-neg-zero": [
      2
    ],
    "no-cond-assign": [
      2,
      "except-parens"
    ],
    "no-const-assign": [
      0
    ],
    "no-constant-binary-expression": [
      2
    ],
    "no-constant-condition": [
      2,
      {
        "checkLoops": "allExceptWhileTrue"
      }
    ],
    "no-control-regex": [
      2
    ],
    "no-debugger": [
      2
    ],
    "no-delete-var": [
      2
    ],
    "no-dupe-args": [
      0
    ],
    "no-dupe-class-members": [
      0
    ],
    "no-dupe-else-if": [
      2
    ],
    "no-dupe-keys": [
      0
    ],
    "no-duplicate-case": [
      2
    ],
    "no-empty": [
      2,
      {
        "allowEmptyCatch": false
      }
    ],
    "no-empty-character-class": [
      2
    ],
    "no-empty-pattern": [
      2,
      {
        "allowObjectPatternsAsParameters": false
      }
    ],
    "no-empty-static-block": [
      2
    ],
    "no-ex-assign": [
      2
    ],
    "no-extra-boolean-cast": [
      2,
      {}
    ],
    "no-fallthrough": [
      2,
      {
        "allowEmptyCase": false,
        "reportUnusedFallthroughComment": false
      }
    ],
    "no-func-assign": [
      0
    ],
    "no-global-assign": [
      2,
      {
        "exceptions": []
      }
    ],
    "no-import-assign": [
      0
    ],
    "no-invalid-regexp": [
      2,
      {}
    ],
    "no-irregular-whitespace": [
      2,
      {
        "skipComments": false,
        "skipJSXText": false,
        "skipRegExps": false,
        "skipStrings": true,
        "skipTemplates": false
      }
    ],
    "no-loss-of-precision": [
      2
    ],
    "no-misleading-character-class": [
      2
    ],
    "no-new-native-nonconstructor": [
      0
    ],
    "no-nonoctal-decimal-escape": [
      2
    ],
    "no-obj-calls": [
      0
    ],
    "no-octal": [
      2
    ],
    "no-prototype-builtins": [
      2
    ],
    "no-redeclare": [
      0,
      {
        "builtinGlobals": true
      }
    ],
    "no-regex-spaces": [
      2
    ],
    "no-self-assign": [
      2,
      {
        "props": true
      }
    ],
    "no-setter-return": [
      0
    ],
    "no-shadow-restricted-names": [
      2,
      {
        "reportGlobalThis": false
      }
    ],
    "no-sparse-arrays": [
      2
    ],
    "no-this-before-super": [
      0
    ],
    "no-undef": [
      0,
      {
        "typeof": false
      }
    ],
    "no-unexpected-multiline": [
      2
    ],
    "no-unreachable": [
      0
    ],
    "no-unsafe-finally": [
      2
    ],
    "no-unsafe-negation": [
      0,
      {
        "enforceForOrderingRelations": false
      }
    ],
    "no-unsafe-optional-chaining": [
      2,
      {
        "disallowArithmeticOperators": false
      }
    ],
    "no-unused-labels": [
      2
    ],
    "no-unused-private-class-members": [
      2
    ],
    "no-unused-vars": [
      0,
      {
        "ignoreRestSiblings": true
      }
    ],
    "no-useless-backreference": [
      2
    ],
    "no-useless-catch": [
      2
    ],
    "no-useless-escape": [
      2,
      {
        "allowRegexCharacters": []
      }
    ],
    "no-with": [
      0
    ],
    "require-yield": [
      2
    ],
    "use-isnan": [
      2,
      {
        "enforceForIndexOf": false,
        "enforceForSwitchCase": true
      }
    ],
    "valid-typeof": [
      2,
      {
        "requireStringLiterals": false
      }
    ],
    "accessor-pairs": [
      2,
      {
        "enforceForClassMembers": true,
        "getWithoutSet": false,
        "setWithoutGet": true
      }
    ],
    "array-callback-return": [
      2,
      {
        "allowImplicit": false,
        "checkForEach": false,
        "allowVoid": false
      }
    ],
    "block-scoped-var": [
      2
    ],
    "class-methods-use-this": [
      0,
      {
        "enforceForClassFields": true,
        "exceptMethods": [],
        "ignoreOverrideMethods": false
      }
    ],
    "consistent-return": [
      2,
      {
        "treatUndefinedAsUnspecified": false
      }
    ],
    "default-case-last": [
      2
    ],
    "default-case": [
      2,
      {}
    ],
    "default-param-last": [
      0
    ],
    "eqeqeq": [
      2,
      "always",
      {
        "null": "ignore"
      }
    ],
    "func-name-matching": [
      2
    ],
    "func-names": [
      2,
      "as-needed",
      {}
    ],
    "grouped-accessor-pairs": [
      2,
      "anyOrder"
    ],
    "no-alert": [
      2
    ],
    "no-array-constructor": [
      0
    ],
    "no-caller": [
      2
    ],
    "no-console": [
      1,
      {}
    ],
    "no-constructor-return": [
      2
    ],
    "no-div-regex": [
      2
    ],
    "no-duplicate-imports": [
      0,
      {
        "includeExports": false
      }
    ],
    "no-empty-function": [
      2,
      {
        "allow": []
      }
    ],
    "no-eq-null": [
      2
    ],
    "no-eval": [
      2,
      {
        "allowIndirect": false
      }
    ],
    "no-extend-native": [
      2,
      {
        "exceptions": []
      }
    ],
    "no-implicit-coercion": [
      2,
      {
        "allow": [],
        "boolean": true,
        "disallowTemplateShorthand": false,
        "number": true,
        "string": true
      }
    ],
    "no-implicit-globals": [
      0,
      {
        "lexicalBindings": false
      }
    ],
    "no-implied-eval": [
      0
    ],
    "no-invalid-this": [
      0,
      {
        "capIsConstructor": true
      }
    ],
    "no-iterator": [
      2
    ],
    "no-label-var": [
      2
    ],
    "no-loop-func": [
      2
    ],
    "no-new-func": [
      2
    ],
    "no-new-wrappers": [
      2
    ],
    "no-new": [
      2
    ],
    "no-octal-escape": [
      2
    ],
    "no-param-reassign": [
      2
    ],
    "no-proto": [
      2
    ],
    "no-return-assign": [
      2,
      "except-parens"
    ],
    "no-script-url": [
      2
    ],
    "no-self-compare": [
      2
    ],
    "no-sequences": [
      2,
      {
        "allowInParentheses": true
      }
    ],
    "no-shadow": [
      0,
      {
        "allow": [],
        "builtinGlobals": false,
        "hoist": "functions",
        "ignoreOnInitialization": false,
        "ignoreTypeValueShadow": true,
        "ignoreFunctionTypeParameterNameValueShadow": true
      }
    ],
    "no-template-curly-in-string": [
      2
    ],
    "no-throw-literal": [
      0
    ],
    "no-unmodified-loop-condition": [
      2
    ],
    "no-unused-expressions": [
      0,
      {
        "allowShortCircuit": false,
        "allowTernary": false,
        "allowTaggedTemplates": false,
        "enforceForJSX": false,
        "ignoreDirectives": false
      }
    ],
    "no-use-before-define": [
      0,
      {
        "classes": true,
        "functions": false,
        "variables": true,
        "allowNamedExports": false,
        "enums": true,
        "typedefs": true,
        "ignoreTypeReferences": true
      }
    ],
    "no-useless-call": [
      2
    ],
    "no-useless-computed-key": [
      2,
      {
        "enforceForClassMembers": true
      }
    ],
    "no-useless-concat": [
      2
    ],
    "no-useless-constructor": [
      0
    ],
    "no-useless-rename": [
      2,
      {
        "ignoreDestructuring": false,
        "ignoreImport": false,
        "ignoreExport": false
      }
    ],
    "no-useless-return": [
      2
    ],
    "no-var": [
      2
    ],
    "no-void": [
      2,
      {
        "allowAsStatement": true
      }
    ],
    "object-shorthand": [
      2,
      "always",
      {
        "avoidQuotes": true
      }
    ],
    "prefer-numeric-literals": [
      2
    ],
    "prefer-promise-reject-errors": [
      0,
      {
        "allowEmptyReject": false
      }
    ],
    "prefer-rest-params": [
      2
    ],
    "prefer-spread": [
      2
    ],
    "prefer-template": [
      2
    ],
    "radix": [
      2,
      "always"
    ],
    "require-await": [
      0
    ],
    "symbol-description": [
      2
    ],
    "vars-on-top": [
      2
    ],
    "import/no-unresolved": [
      2
    ],
    "import/named": [
      0
    ],
    "import/namespace": [
      2
    ],
    "import/default": [
      2
    ],
    "import/export": [
      2
    ],
    "import/first": [
      2
    ],
    "import/no-absolute-path": [
      2
    ],
    "import/no-amd": [
      2
    ],
    "import/no-anonymous-default-export": [
      2
    ],
    "import/no-commonjs": [
      2
    ],
    "import/no-cycle": [
      0
    ],
    "import/no-deprecated": [
      1
    ],
    "import/no-duplicates": [
      2
    ],
    "import/no-dynamic-require": [
      2
    ],
    "import/no-empty-named-blocks": [
      2
    ],
    "import/no-extraneous-dependencies": [
      2
    ],
    "import/no-import-module-exports": [
      2
    ],
    "import/no-mutable-exports": [
      2
    ],
    "import/no-named-as-default-member": [
      2
    ],
    "import/no-named-as-default": [
      2
    ],
    "import/no-relative-packages": [
      1
    ],
    "import/no-self-import": [
      2
    ],
    "import/no-useless-path-segments": [
      2
    ],
    "import/no-webpack-loader-syntax": [
      2
    ],
    "compat/compat": [
      2
    ],
    "perfectionist/sort-imports": [
      1,
      {
        "internalPattern": [
          "^@mw/.+",
          "^@masterworks/.+"
        ],
        "groups": [
          "builtin",
          "external",
          "internal",
          "parent",
          "sibling",
          "index",
          "object",
          "style",
          "side-effect",
          "side-effect-style",
          "unknown"
        ]
      }
    ],
    "no-new-symbol": [
      0
    ],
    "prefer-const": [
      2,
      {
        "destructuring": "any",
        "ignoreReadBeforeAssign": false
      }
    ],
    "@typescript-eslint/await-thenable": [
      2
    ],
    "@typescript-eslint/ban-ts-comment": [
      2,
      {
        "minimumDescriptionLength": 10
      }
    ],
    "@typescript-eslint/no-array-constructor": [
      2
    ],
    "@typescript-eslint/no-array-delete": [
      2
    ],
    "@typescript-eslint/no-base-to-string": [
      0
    ],
    "@typescript-eslint/no-duplicate-enum-values": [
      2
    ],
    "@typescript-eslint/no-duplicate-type-constituents": [
      2
    ],
    "@typescript-eslint/no-empty-object-type": [
      2
    ],
    "@typescript-eslint/no-explicit-any": [
      2
    ],
    "@typescript-eslint/no-extra-non-null-assertion": [
      2
    ],
    "@typescript-eslint/no-floating-promises": [
      2
    ],
    "@typescript-eslint/no-for-in-array": [
      2
    ],
    "@typescript-eslint/no-implied-eval": [
      2
    ],
    "@typescript-eslint/no-misused-new": [
      2
    ],
    "@typescript-eslint/no-misused-promises": [
      2
    ],
    "@typescript-eslint/no-namespace": [
      2
    ],
    "@typescript-eslint/no-non-null-asserted-optional-chain": [
      2
    ],
    "@typescript-eslint/no-redundant-type-constituents": [
      2
    ],
    "@typescript-eslint/no-require-imports": [
      2
    ],
    "@typescript-eslint/no-this-alias": [
      2
    ],
    "@typescript-eslint/no-unnecessary-type-assertion": [
      2
    ],
    "@typescript-eslint/no-unnecessary-type-constraint": [
      2
    ],
    "@typescript-eslint/no-unsafe-argument": [
      2
    ],
    "@typescript-eslint/no-unsafe-assignment": [
      2
    ],
    "@typescript-eslint/no-unsafe-call": [
      2
    ],
    "@typescript-eslint/no-unsafe-declaration-merging": [
      2
    ],
    "@typescript-eslint/no-unsafe-enum-comparison": [
      2
    ],
    "@typescript-eslint/no-unsafe-function-type": [
      2
    ],
    "@typescript-eslint/no-unsafe-member-access": [
      2
    ],
    "@typescript-eslint/no-unsafe-return": [
      2
    ],
    "@typescript-eslint/no-unsafe-unary-minus": [
      2
    ],
    "@typescript-eslint/no-unused-expressions": [
      2,
      {
        "allowShortCircuit": false,
        "allowTaggedTemplates": false,
        "allowTernary": false
      }
    ],
    "@typescript-eslint/no-unused-vars": [
      2
    ],
    "@typescript-eslint/no-wrapper-object-types": [
      2
    ],
    "@typescript-eslint/only-throw-error": [
      2
    ],
    "@typescript-eslint/prefer-as-const": [
      2
    ],
    "@typescript-eslint/prefer-namespace-keyword": [
      2
    ],
    "@typescript-eslint/prefer-promise-reject-errors": [
      2
    ],
    "@typescript-eslint/require-await": [
      2
    ],
    "@typescript-eslint/restrict-plus-operands": [
      2,
      {
        "allowAny": false,
        "allowBoolean": false,
        "allowNullish": false,
        "allowNumberAndString": false,
        "allowRegExp": false
      }
    ],
    "@typescript-eslint/restrict-template-expressions": [
      2,
      {
        "allowNumber": true
      }
    ],
    "@typescript-eslint/triple-slash-reference": [
      2
    ],
    "@typescript-eslint/unbound-method": [
      2,
      {
        "ignoreStatic": true
      }
    ],
    "@typescript-eslint/class-methods-use-this": [
      2
    ],
    "@typescript-eslint/default-param-last": [
      2
    ],
    "@typescript-eslint/no-dupe-class-members": [
      2
    ],
    "@typescript-eslint/no-use-before-define": [
      2
    ],
    "@typescript-eslint/parameter-properties": [
      2
    ],
    "@typescript-eslint/require-array-sort-compare": [
      2
    ],
    "@typescript-eslint/no-confusing-void-expression": [
      2
    ],
    "@typescript-eslint/no-deprecated": [
      1
    ],
    "@typescript-eslint/no-dynamic-delete": [
      2
    ],
    "@typescript-eslint/no-extraneous-class": [
      2
    ],
    "@typescript-eslint/no-invalid-void-type": [
      2,
      {
        "allowAsThisParameter": true,
        "allowInGenericTypeArguments": true
      }
    ],
    "@typescript-eslint/no-meaningless-void-operator": [
      2
    ],
    "@typescript-eslint/no-misused-spread": [
      2
    ],
    "@typescript-eslint/no-mixed-enums": [
      2
    ],
    "@typescript-eslint/no-non-null-asserted-nullish-coalescing": [
      2
    ],
    "@typescript-eslint/no-non-null-assertion": [
      2
    ],
    "@typescript-eslint/no-unnecessary-boolean-literal-compare": [
      2
    ],
    "@typescript-eslint/no-unnecessary-condition": [
      2
    ],
    "@typescript-eslint/no-unnecessary-template-expression": [
      2
    ],
    "@typescript-eslint/no-unnecessary-type-arguments": [
      2
    ],
    "@typescript-eslint/no-unnecessary-type-parameters": [
      0
    ],
    "@typescript-eslint/no-useless-constructor": [
      2
    ],
    "@typescript-eslint/prefer-literal-enum-member": [
      2
    ],
    "@typescript-eslint/prefer-reduce-type-parameter": [
      2
    ],
    "@typescript-eslint/prefer-return-this-type": [
      2
    ],
    "@typescript-eslint/related-getter-setter-pairs": [
      2
    ],
    "no-return-await": [
      0
    ],
    "@typescript-eslint/return-await": [
      2,
      "error-handling-correctness-only"
    ],
    "@typescript-eslint/unified-signatures": [
      2
    ],
    "@typescript-eslint/use-unknown-in-catch-callback-variable": [
      1
    ],
    "@typescript-eslint/adjacent-overload-signatures": [
      1
    ],
    "@typescript-eslint/explicit-function-return-type": [
      2,
      {
        "allowConciseArrowFunctionExpressionsStartingWithVoid": true,
        "allowExpressions": true,
        "allowIIFEs": true
      }
    ],
    "@typescript-eslint/no-shadow": [
      2
    ],
    "@typescript-eslint/prefer-enum-initializers": [
      2
    ],
    "@typescript-eslint/promise-function-async": [
      2
    ],
    "@typescript-eslint/switch-exhaustiveness-check": [
      2
    ],
    "react/display-name": [
      2
    ],
    "react/jsx-key": [
      2,
      {
        "checkFragmentShorthand": true,
        "checkKeyMustBeforeSpread": false,
        "warnOnDuplicates": false
      }
    ],
    "react/jsx-no-comment-textnodes": [
      2
    ],
    "react/jsx-no-duplicate-props": [
      2
    ],
    "react/jsx-no-target-blank": [
      2
    ],
    "react/jsx-no-undef": [
      2
    ],
    "react/jsx-uses-react": [
      0
    ],
    "react/jsx-uses-vars": [
      2
    ],
    "react/no-children-prop": [
      2
    ],
    "react/no-danger-with-children": [
      2
    ],
    "react/no-deprecated": [
      1
    ],
    "react/no-direct-mutation-state": [
      2
    ],
    "react/no-find-dom-node": [
      2
    ],
    "react/no-is-mounted": [
      2
    ],
    "react/no-render-return-value": [
      2
    ],
    "react/no-string-refs": [
      2
    ],
    "react/no-unescaped-entities": [
      2,
      {
        "forbid": [
          {
            "char": ">",
            "alternatives": [
              "&gt;"
            ]
          },
          {
            "char": "}",
            "alternatives": [
              "&#125;"
            ]
          }
        ]
      }
    ],
    "react/no-unknown-property": [
      2
    ],
    "react/no-unsafe": [
      0
    ],
    "react/prop-types": [
      0
    ],
    "react/react-in-jsx-scope": [
      0
    ],
    "react/require-render-return": [
      2
    ],
    "react-hooks/exhaustive-deps": [
      2
    ],
    "react-hooks/rules-of-hooks": [
      2
    ],
    "react/button-has-type": [
      2
    ],
    "react/jsx-no-bind": [
      2,
      {
        "allowArrowFunctions": true,
        "allowBind": false,
        "allowFunctions": false,
        "ignoreRefs": false,
        "ignoreDOMComponents": false
      }
    ],
    "react/no-access-state-in-setstate": [
      2
    ],
    "react/no-array-index-key": [
      2
    ],
    "react/no-did-mount-set-state": [
      2
    ],
    "react/no-did-update-set-state": [
      2
    ],
    "react/no-redundant-should-component-update": [
      2
    ],
    "react/no-this-in-sfc": [
      2
    ],
    "react/no-typos": [
      2
    ],
    "react/no-unused-prop-types": [
      2
    ],
    "react/no-unused-state": [
      2
    ],
    "react/no-will-update-set-state": [
      2
    ],
    "react/prefer-es6-class": [
      2
    ],
    "react/prefer-stateless-function": [
      2
    ],
    "react/self-closing-comp": [
      2
    ],
    "react/style-prop-object": [
      2,
      {
        "allow": [
          "FormattedNumber"
        ]
      }
    ],
    "react/void-dom-elements-no-children": [
      2
    ],
    "react/default-props-match-prop-types": [
      0
    ],
    "react/forbid-foreign-prop-types": [
      0
    ],
    "jsx-a11y/alt-text": [
      2
    ],
    "jsx-a11y/anchor-ambiguous-text": [
      0
    ],
    "jsx-a11y/anchor-has-content": [
      2
    ],
    "jsx-a11y/anchor-is-valid": [
      2
    ],
    "jsx-a11y/aria-activedescendant-has-tabindex": [
      2
    ],
    "jsx-a11y/aria-props": [
      2
    ],
    "jsx-a11y/aria-proptypes": [
      2
    ],
    "jsx-a11y/aria-role": [
      2
    ],
    "jsx-a11y/aria-unsupported-elements": [
      2
    ],
    "jsx-a11y/autocomplete-valid": [
      2
    ],
    "jsx-a11y/click-events-have-key-events": [
      2
    ],
    "jsx-a11y/control-has-associated-label": [
      0,
      {
        "ignoreElements": [
          "audio",
          "canvas",
          "embed",
          "input",
          "textarea",
          "tr",
          "video"
        ],
        "ignoreRoles": [
          "grid",
          "listbox",
          "menu",
          "menubar",
          "radiogroup",
          "row",
          "tablist",
          "toolbar",
          "tree",
          "treegrid"
        ],
        "includeRoles": [
          "alert",
          "dialog"
        ]
      }
    ],
    "jsx-a11y/heading-has-content": [
      2
    ],
    "jsx-a11y/html-has-lang": [
      2
    ],
    "jsx-a11y/iframe-has-title": [
      2
    ],
    "jsx-a11y/img-redundant-alt": [
      2
    ],
    "jsx-a11y/interactive-supports-focus": [
      2,
      {
        "tabbable": [
          "button",
          "checkbox",
          "link",
          "searchbox",
          "spinbutton",
          "switch",
          "textbox"
        ]
      }
    ],
    "jsx-a11y/label-has-associated-control": [
      2,
      {
        "labelComponents": [
          "Label"
        ],
        "labelAttributes": [
          "label"
        ],
        "controlComponents": [
          "Input",
          "Field"
        ],
        "depth": 3
      }
    ],
    "jsx-a11y/label-has-for": [
      0
    ],
    "jsx-a11y/media-has-caption": [
      2
    ],
    "jsx-a11y/mouse-events-have-key-events": [
      2
    ],
    "jsx-a11y/no-access-key": [
      2
    ],
    "jsx-a11y/no-autofocus": [
      2
    ],
    "jsx-a11y/no-distracting-elements": [
      2
    ],
    "jsx-a11y/no-interactive-element-to-noninteractive-role": [
      2,
      {
        "tr": [
          "none",
          "presentation"
        ],
        "canvas": [
          "img"
        ]
      }
    ],
    "jsx-a11y/no-noninteractive-element-interactions": [
      2,
      {
        "handlers": [
          "onClick",
          "onError",
          "onLoad",
          "onMouseDown",
          "onMouseUp",
          "onKeyPress",
          "onKeyDown",
          "onKeyUp"
        ],
        "alert": [
          "onKeyUp",
          "onKeyDown",
          "onKeyPress"
        ],
        "body": [
          "onError",
          "onLoad"
        ],
        "dialog": [
          "onKeyUp",
          "onKeyDown",
          "onKeyPress"
        ],
        "iframe": [
          "onError",
          "onLoad"
        ],
        "img": [
          "onError",
          "onLoad"
        ]
      }
    ],
    "jsx-a11y/no-noninteractive-element-to-interactive-role": [
      2,
      {
        "ul": [
          "listbox",
          "menu",
          "menubar",
          "radiogroup",
          "tablist",
          "tree",
          "treegrid"
        ],
        "ol": [
          "listbox",
          "menu",
          "menubar",
          "radiogroup",
          "tablist",
          "tree",
          "treegrid"
        ],
        "li": [
          "menuitem",
          "menuitemradio",
          "menuitemcheckbox",
          "option",
          "row",
          "tab",
          "treeitem"
        ],
        "table": [
          "grid"
        ],
        "td": [
          "gridcell"
        ],
        "fieldset": [
          "radiogroup",
          "presentation"
        ]
      }
    ],
    "jsx-a11y/no-noninteractive-tabindex": [
      2,
      {
        "tags": [],
        "roles": [
          "tabpanel"
        ],
        "allowExpressionValues": true
      }
    ],
    "jsx-a11y/no-redundant-roles": [
      2,
      {
        "ul": [
          "list"
        ],
        "ol": [
          "list"
        ],
        "li": [
          "listitem"
        ]
      }
    ],
    "jsx-a11y/no-static-element-interactions": [
      2,
      {
        "allowExpressionValues": true,
        "handlers": [
          "onClick",
          "onMouseDown",
          "onMouseUp",
          "onKeyPress",
          "onKeyDown",
          "onKeyUp"
        ]
      }
    ],
    "jsx-a11y/role-has-required-aria-props": [
      2
    ],
    "jsx-a11y/role-supports-aria-props": [
      2
    ],
    "jsx-a11y/scope": [
      2
    ],
    "jsx-a11y/tabindex-no-positive": [
      2
    ],
    "arrow-body-style": [
      2,
      "as-needed"
    ],
    "camelcase": [
      2,
      {
        "allow": [],
        "ignoreDestructuring": true,
        "ignoreGlobals": true,
        "ignoreImports": true,
        "properties": "never"
      }
    ],
    "capitalized-comments": [
      2,
      "always",
      {
        "ignoreConsecutiveComments": true,
        "ignoreInlineComments": true
      }
    ],
    "dot-notation": [
      2,
      {
        "allowKeywords": true,
        "allowPattern": ""
      }
    ],
    "no-else-return": [
      2,
      {
        "allowElseIf": true
      }
    ],
    "no-extra-bind": [
      2
    ],
    "no-extra-label": [
      2
    ],
    "prefer-arrow-callback": [
      2,
      {
        "allowNamedFunctions": false,
        "allowUnboundThis": true
      }
    ],
    "yoda": [
      2,
      "never",
      {
        "exceptRange": false,
        "onlyEquality": false
      }
    ],
    "import/consistent-type-specifier-style": [
      0,
      "prefer-top-level"
    ],
    "import/order": [
      0
    ],
    "@stylistic/arrow-parens": [
      2,
      "always"
    ],
    "@stylistic/arrow-spacing": [
      2
    ],
    "@stylistic/block-spacing": [
      2
    ],
    "@stylistic/dot-location": [
      2,
      "property"
    ],
    "@stylistic/no-floating-decimal": [
      2
    ],
    "@stylistic/no-multi-spaces": [
      2
    ],
    "@stylistic/rest-spread-spacing": [
      2
    ],
    "@stylistic/template-curly-spacing": [
      2
    ],
    "@stylistic/wrap-iife": [
      2,
      "any"
    ],
    "@stylistic/yield-star-spacing": [
      2,
      "after"
    ],
    "@typescript-eslint/consistent-generic-constructors": [
      2
    ],
    "@typescript-eslint/consistent-indexed-object-style": [
      2,
      "record"
    ],
    "@typescript-eslint/consistent-type-assertions": [
      2
    ],
    "@typescript-eslint/consistent-type-exports": [
      2
    ],
    "@typescript-eslint/consistent-type-imports": [
      2,
      {
        "prefer": "type-imports",
        "fixStyle": "separate-type-imports"
      }
    ],
    "@typescript-eslint/member-ordering": [
      1
    ],
    "@typescript-eslint/no-inferrable-types": [
      1
    ],
    "@typescript-eslint/prefer-for-of": [
      2
    ],
    "@typescript-eslint/prefer-includes": [
      2
    ],
    "@typescript-eslint/prefer-nullish-coalescing": [
      2,
      {
        "ignoreConditionalTests": true,
        "ignorePrimitives": true,
        "ignoreTernaryTests": true
      }
    ],
    "@typescript-eslint/prefer-optional-chain": [
      2
    ],
    "@typescript-eslint/prefer-readonly": [
      2
    ],
    "@typescript-eslint/prefer-string-starts-ends-with": [
      2
    ],
    "@typescript-eslint/sort-type-constituents": [
      2
    ],
    "react/jsx-boolean-value": [
      2,
      "never"
    ],
    "react/jsx-closing-bracket-location": [
      2
    ],
    "react/jsx-closing-tag-location": [
      2
    ],
    "react/jsx-curly-spacing": [
      2
    ],
    "react/jsx-equals-spacing": [
      2,
      "never"
    ],
    "react/jsx-fragments": [
      2,
      "syntax"
    ],
    "react/jsx-no-useless-fragment": [
      2
    ],
    "react/jsx-props-no-multi-spaces": [
      2
    ],
    "react/jsx-tag-spacing": [
      2
    ],
    "react/jsx-wrap-multilines": [
      2,
      {
        "arrow": "parens-new-line",
        "assignment": "parens-new-line",
        "condition": "parens-new-line",
        "declaration": "parens-new-line",
        "logical": "parens-new-line",
        "prop": "parens-new-line",
        "return": "parens-new-line"
      }
    ]
  },
  "plugins": [
    "@",
    "import:eslint-plugin-import-x@4.15.0",
    "compat:eslint-plugin-compat@6.0.2",
    "perfectionist:eslint-plugin-perfectionist@4.13.0",
    "@typescript-eslint:@typescript-eslint/eslint-plugin@8.33.1",
    "react",
    "react-hooks:eslint-plugin-react-hooks",
    "jsx-a11y:eslint-plugin-jsx-a11y@6.10.2",
    "@stylistic"
  ],
  "language": "@/js",
  "languageOptions": {
    "sourceType": "module",
    "ecmaVersion": 2025,
    "parser": "typescript-eslint/parser@8.33.1",
    "parserOptions": {
      "projectService": true,
      "tsconfigRootDir": "/Users/sam/Code/masterworks/frontend/",
      "ecmaFeatures": {
        "jsx": true
      },
      "jsxPragma": null
    },
    "globals": {
      "AbortController": false,
      "AbortSignal": false,
      "AbsoluteOrientationSensor": false,
      "AbstractRange": false,
      "Accelerometer": false,
      "addEventListener": false,
      "ai": false,
      "AI": false,
      "AICreateMonitor": false,
      "AITextSession": false,
      "alert": false,
      "AnalyserNode": false,
      "Animation": false,
      "AnimationEffect": false,
      "AnimationEvent": false,
      "AnimationPlaybackEvent": false,
      "AnimationTimeline": false,
      "AsyncDisposableStack": false,
      "atob": false,
      "Attr": false,
      "Audio": false,
      "AudioBuffer": false,
      "AudioBufferSourceNode": false,
      "AudioContext": false,
      "AudioData": false,
      "AudioDecoder": false,
      "AudioDestinationNode": false,
      "AudioEncoder": false,
      "AudioListener": false,
      "AudioNode": false,
      "AudioParam": false,
      "AudioParamMap": false,
      "AudioProcessingEvent": false,
      "AudioScheduledSourceNode": false,
      "AudioSinkInfo": false,
      "AudioWorklet": false,
      "AudioWorkletGlobalScope": false,
      "AudioWorkletNode": false,
      "AudioWorkletProcessor": false,
      "AuthenticatorAssertionResponse": false,
      "AuthenticatorAttestationResponse": false,
      "AuthenticatorResponse": false,
      "BackgroundFetchManager": false,
      "BackgroundFetchRecord": false,
      "BackgroundFetchRegistration": false,
      "BarcodeDetector": false,
      "BarProp": false,
      "BaseAudioContext": false,
      "BatteryManager": false,
      "BeforeUnloadEvent": false,
      "BiquadFilterNode": false,
      "Blob": false,
      "BlobEvent": false,
      "Bluetooth": false,
      "BluetoothCharacteristicProperties": false,
      "BluetoothDevice": false,
      "BluetoothRemoteGATTCharacteristic": false,
      "BluetoothRemoteGATTDescriptor": false,
      "BluetoothRemoteGATTServer": false,
      "BluetoothRemoteGATTService": false,
      "BluetoothUUID": false,
      "blur": false,
      "BroadcastChannel": false,
      "BrowserCaptureMediaStreamTrack": false,
      "btoa": false,
      "ByteLengthQueuingStrategy": false,
      "Cache": false,
      "caches": false,
      "CacheStorage": false,
      "cancelAnimationFrame": false,
      "cancelIdleCallback": false,
      "CanvasCaptureMediaStream": false,
      "CanvasCaptureMediaStreamTrack": false,
      "CanvasGradient": false,
      "CanvasPattern": false,
      "CanvasRenderingContext2D": false,
      "CaptureController": false,
      "CaretPosition": false,
      "CDATASection": false,
      "ChannelMergerNode": false,
      "ChannelSplitterNode": false,
      "ChapterInformation": false,
      "CharacterBoundsUpdateEvent": false,
      "CharacterData": false,
      "clearInterval": false,
      "clearTimeout": false,
      "clientInformation": false,
      "Clipboard": false,
      "ClipboardEvent": false,
      "ClipboardItem": false,
      "close": false,
      "closed": false,
      "CloseEvent": false,
      "CloseWatcher": false,
      "CommandEvent": false,
      "Comment": false,
      "CompositionEvent": false,
      "CompressionStream": false,
      "confirm": false,
      "console": false,
      "ConstantSourceNode": false,
      "ContentVisibilityAutoStateChangeEvent": false,
      "ConvolverNode": false,
      "CookieChangeEvent": false,
      "CookieDeprecationLabel": false,
      "cookieStore": false,
      "CookieStore": false,
      "CookieStoreManager": false,
      "CountQueuingStrategy": false,
      "createImageBitmap": false,
      "Credential": false,
      "credentialless": false,
      "CredentialsContainer": false,
      "CropTarget": false,
      "crossOriginIsolated": false,
      "crypto": false,
      "Crypto": false,
      "CryptoKey": false,
      "CSPViolationReportBody": false,
      "CSS": false,
      "CSSAnimation": false,
      "CSSConditionRule": false,
      "CSSContainerRule": false,
      "CSSCounterStyleRule": false,
      "CSSFontFaceRule": false,
      "CSSFontFeatureValuesRule": false,
      "CSSFontPaletteValuesRule": false,
      "CSSGroupingRule": false,
      "CSSImageValue": false,
      "CSSImportRule": false,
      "CSSKeyframeRule": false,
      "CSSKeyframesRule": false,
      "CSSKeywordValue": false,
      "CSSLayerBlockRule": false,
      "CSSLayerStatementRule": false,
      "CSSMarginRule": false,
      "CSSMathClamp": false,
      "CSSMathInvert": false,
      "CSSMathMax": false,
      "CSSMathMin": false,
      "CSSMathNegate": false,
      "CSSMathProduct": false,
      "CSSMathSum": false,
      "CSSMathValue": false,
      "CSSMatrixComponent": false,
      "CSSMediaRule": false,
      "CSSNamespaceRule": false,
      "CSSNestedDeclarations": false,
      "CSSNumericArray": false,
      "CSSNumericValue": false,
      "CSSPageDescriptors": false,
      "CSSPageRule": false,
      "CSSPerspective": false,
      "CSSPositionTryDescriptors": false,
      "CSSPositionTryRule": false,
      "CSSPositionValue": false,
      "CSSPropertyRule": false,
      "CSSRotate": false,
      "CSSRule": false,
      "CSSRuleList": false,
      "CSSScale": false,
      "CSSScopeRule": false,
      "CSSSkew": false,
      "CSSSkewX": false,
      "CSSSkewY": false,
      "CSSStartingStyleRule": false,
      "CSSStyleDeclaration": false,
      "CSSStyleRule": false,
      "CSSStyleSheet": false,
      "CSSStyleValue": false,
      "CSSSupportsRule": false,
      "CSSTransformComponent": false,
      "CSSTransformValue": false,
      "CSSTransition": false,
      "CSSTranslate": false,
      "CSSUnitValue": false,
      "CSSUnparsedValue": false,
      "CSSVariableReferenceValue": false,
      "CSSViewTransitionRule": false,
      "currentFrame": false,
      "currentTime": false,
      "CustomElementRegistry": false,
      "customElements": false,
      "CustomEvent": false,
      "CustomStateSet": false,
      "DataTransfer": false,
      "DataTransferItem": false,
      "DataTransferItemList": false,
      "DecompressionStream": false,
      "DelayNode": false,
      "DelegatedInkTrailPresenter": false,
      "DeviceMotionEvent": false,
      "DeviceMotionEventAcceleration": false,
      "DeviceMotionEventRotationRate": false,
      "DeviceOrientationEvent": false,
      "devicePixelRatio": false,
      "DevicePosture": false,
      "dispatchEvent": false,
      "DisposableStack": false,
      "document": false,
      "Document": false,
      "DocumentFragment": false,
      "documentPictureInPicture": false,
      "DocumentPictureInPicture": false,
      "DocumentPictureInPictureEvent": false,
      "DocumentTimeline": false,
      "DocumentType": false,
      "DOMError": false,
      "DOMException": false,
      "DOMImplementation": false,
      "DOMMatrix": false,
      "DOMMatrixReadOnly": false,
      "DOMParser": false,
      "DOMPoint": false,
      "DOMPointReadOnly": false,
      "DOMQuad": false,
      "DOMRect": false,
      "DOMRectList": false,
      "DOMRectReadOnly": false,
      "DOMStringList": false,
      "DOMStringMap": false,
      "DOMTokenList": false,
      "DragEvent": false,
      "DynamicsCompressorNode": false,
      "EditContext": false,
      "Element": false,
      "ElementInternals": false,
      "EncodedAudioChunk": false,
      "EncodedVideoChunk": false,
      "ErrorEvent": false,
      "event": false,
      "Event": false,
      "EventCounts": false,
      "EventSource": false,
      "EventTarget": false,
      "external": false,
      "External": false,
      "EyeDropper": false,
      "FeaturePolicy": false,
      "FederatedCredential": false,
      "fence": false,
      "Fence": false,
      "FencedFrameConfig": false,
      "fetch": false,
      "fetchLater": false,
      "FetchLaterResult": false,
      "File": false,
      "FileList": false,
      "FileReader": false,
      "FileSystem": false,
      "FileSystemDirectoryEntry": false,
      "FileSystemDirectoryHandle": false,
      "FileSystemDirectoryReader": false,
      "FileSystemEntry": false,
      "FileSystemFileEntry": false,
      "FileSystemFileHandle": false,
      "FileSystemHandle": false,
      "FileSystemObserver": false,
      "FileSystemWritableFileStream": false,
      "find": false,
      "focus": false,
      "FocusEvent": false,
      "FontData": false,
      "FontFace": false,
      "FontFaceSet": false,
      "FontFaceSetLoadEvent": false,
      "FormData": false,
      "FormDataEvent": false,
      "FragmentDirective": false,
      "frameElement": false,
      "frames": false,
      "GainNode": false,
      "Gamepad": false,
      "GamepadAxisMoveEvent": false,
      "GamepadButton": false,
      "GamepadButtonEvent": false,
      "GamepadEvent": false,
      "GamepadHapticActuator": false,
      "GamepadPose": false,
      "Geolocation": false,
      "GeolocationCoordinates": false,
      "GeolocationPosition": false,
      "GeolocationPositionError": false,
      "getComputedStyle": false,
      "getScreenDetails": false,
      "getSelection": false,
      "GPU": false,
      "GPUAdapter": false,
      "GPUAdapterInfo": false,
      "GPUBindGroup": false,
      "GPUBindGroupLayout": false,
      "GPUBuffer": false,
      "GPUBufferUsage": false,
      "GPUCanvasContext": false,
      "GPUColorWrite": false,
      "GPUCommandBuffer": false,
      "GPUCommandEncoder": false,
      "GPUCompilationInfo": false,
      "GPUCompilationMessage": false,
      "GPUComputePassEncoder": false,
      "GPUComputePipeline": false,
      "GPUDevice": false,
      "GPUDeviceLostInfo": false,
      "GPUError": false,
      "GPUExternalTexture": false,
      "GPUInternalError": false,
      "GPUMapMode": false,
      "GPUOutOfMemoryError": false,
      "GPUPipelineError": false,
      "GPUPipelineLayout": false,
      "GPUQuerySet": false,
      "GPUQueue": false,
      "GPURenderBundle": false,
      "GPURenderBundleEncoder": false,
      "GPURenderPassEncoder": false,
      "GPURenderPipeline": false,
      "GPUSampler": false,
      "GPUShaderModule": false,
      "GPUShaderStage": false,
      "GPUSupportedFeatures": false,
      "GPUSupportedLimits": false,
      "GPUTexture": false,
      "GPUTextureUsage": false,
      "GPUTextureView": false,
      "GPUUncapturedErrorEvent": false,
      "GPUValidationError": false,
      "GravitySensor": false,
      "Gyroscope": false,
      "HashChangeEvent": false,
      "Headers": false,
      "HID": false,
      "HIDConnectionEvent": false,
      "HIDDevice": false,
      "HIDInputReportEvent": false,
      "Highlight": false,
      "HighlightRegistry": false,
      "history": false,
      "History": false,
      "HTMLAllCollection": false,
      "HTMLAnchorElement": false,
      "HTMLAreaElement": false,
      "HTMLAudioElement": false,
      "HTMLBaseElement": false,
      "HTMLBodyElement": false,
      "HTMLBRElement": false,
      "HTMLButtonElement": false,
      "HTMLCanvasElement": false,
      "HTMLCollection": false,
      "HTMLDataElement": false,
      "HTMLDataListElement": false,
      "HTMLDetailsElement": false,
      "HTMLDialogElement": false,
      "HTMLDirectoryElement": false,
      "HTMLDivElement": false,
      "HTMLDListElement": false,
      "HTMLDocument": false,
      "HTMLElement": false,
      "HTMLEmbedElement": false,
      "HTMLFencedFrameElement": false,
      "HTMLFieldSetElement": false,
      "HTMLFontElement": false,
      "HTMLFormControlsCollection": false,
      "HTMLFormElement": false,
      "HTMLFrameElement": false,
      "HTMLFrameSetElement": false,
      "HTMLHeadElement": false,
      "HTMLHeadingElement": false,
      "HTMLHRElement": false,
      "HTMLHtmlElement": false,
      "HTMLIFrameElement": false,
      "HTMLImageElement": false,
      "HTMLInputElement": false,
      "HTMLLabelElement": false,
      "HTMLLegendElement": false,
      "HTMLLIElement": false,
      "HTMLLinkElement": false,
      "HTMLMapElement": false,
      "HTMLMarqueeElement": false,
      "HTMLMediaElement": false,
      "HTMLMenuElement": false,
      "HTMLMetaElement": false,
      "HTMLMeterElement": false,
      "HTMLModElement": false,
      "HTMLObjectElement": false,
      "HTMLOListElement": false,
      "HTMLOptGroupElement": false,
      "HTMLOptionElement": false,
      "HTMLOptionsCollection": false,
      "HTMLOutputElement": false,
      "HTMLParagraphElement": false,
      "HTMLParamElement": false,
      "HTMLPictureElement": false,
      "HTMLPreElement": false,
      "HTMLProgressElement": false,
      "HTMLQuoteElement": false,
      "HTMLScriptElement": false,
      "HTMLSelectedContentElement": false,
      "HTMLSelectElement": false,
      "HTMLSlotElement": false,
      "HTMLSourceElement": false,
      "HTMLSpanElement": false,
      "HTMLStyleElement": false,
      "HTMLTableCaptionElement": false,
      "HTMLTableCellElement": false,
      "HTMLTableColElement": false,
      "HTMLTableElement": false,
      "HTMLTableRowElement": false,
      "HTMLTableSectionElement": false,
      "HTMLTemplateElement": false,
      "HTMLTextAreaElement": false,
      "HTMLTimeElement": false,
      "HTMLTitleElement": false,
      "HTMLTrackElement": false,
      "HTMLUListElement": false,
      "HTMLUnknownElement": false,
      "HTMLVideoElement": false,
      "IDBCursor": false,
      "IDBCursorWithValue": false,
      "IDBDatabase": false,
      "IDBFactory": false,
      "IDBIndex": false,
      "IDBKeyRange": false,
      "IDBObjectStore": false,
      "IDBOpenDBRequest": false,
      "IDBRequest": false,
      "IDBTransaction": false,
      "IDBVersionChangeEvent": false,
      "IdentityCredential": false,
      "IdentityCredentialError": false,
      "IdentityProvider": false,
      "IdleDeadline": false,
      "IdleDetector": false,
      "IIRFilterNode": false,
      "Image": false,
      "ImageBitmap": false,
      "ImageBitmapRenderingContext": false,
      "ImageCapture": false,
      "ImageData": false,
      "ImageDecoder": false,
      "ImageTrack": false,
      "ImageTrackList": false,
      "indexedDB": false,
      "Ink": false,
      "innerHeight": false,
      "innerWidth": false,
      "InputDeviceCapabilities": false,
      "InputDeviceInfo": false,
      "InputEvent": false,
      "IntersectionObserver": false,
      "IntersectionObserverEntry": false,
      "isSecureContext": false,
      "Keyboard": false,
      "KeyboardEvent": false,
      "KeyboardLayoutMap": false,
      "KeyframeEffect": false,
      "LanguageDetector": false,
      "LargestContentfulPaint": false,
      "LaunchParams": false,
      "launchQueue": false,
      "LaunchQueue": false,
      "LayoutShift": false,
      "LayoutShiftAttribution": false,
      "length": false,
      "LinearAccelerationSensor": false,
      "localStorage": false,
      "location": true,
      "Location": false,
      "locationbar": false,
      "Lock": false,
      "LockManager": false,
      "matchMedia": false,
      "MathMLElement": false,
      "MediaCapabilities": false,
      "MediaCapabilitiesInfo": false,
      "MediaDeviceInfo": false,
      "MediaDevices": false,
      "MediaElementAudioSourceNode": false,
      "MediaEncryptedEvent": false,
      "MediaError": false,
      "MediaKeyError": false,
      "MediaKeyMessageEvent": false,
      "MediaKeys": false,
      "MediaKeySession": false,
      "MediaKeyStatusMap": false,
      "MediaKeySystemAccess": false,
      "MediaList": false,
      "MediaMetadata": false,
      "MediaQueryList": false,
      "MediaQueryListEvent": false,
      "MediaRecorder": false,
      "MediaRecorderErrorEvent": false,
      "MediaSession": false,
      "MediaSource": false,
      "MediaSourceHandle": false,
      "MediaStream": false,
      "MediaStreamAudioDestinationNode": false,
      "MediaStreamAudioSourceNode": false,
      "MediaStreamEvent": false,
      "MediaStreamTrack": false,
      "MediaStreamTrackAudioSourceNode": false,
      "MediaStreamTrackAudioStats": false,
      "MediaStreamTrackEvent": false,
      "MediaStreamTrackGenerator": false,
      "MediaStreamTrackProcessor": false,
      "MediaStreamTrackVideoStats": false,
      "menubar": false,
      "MessageChannel": false,
      "MessageEvent": false,
      "MessagePort": false,
      "MIDIAccess": false,
      "MIDIConnectionEvent": false,
      "MIDIInput": false,
      "MIDIInputMap": false,
      "MIDIMessageEvent": false,
      "MIDIOutput": false,
      "MIDIOutputMap": false,
      "MIDIPort": false,
      "MimeType": false,
      "MimeTypeArray": false,
      "model": false,
      "ModelGenericSession": false,
      "ModelManager": false,
      "MouseEvent": false,
      "moveBy": false,
      "moveTo": false,
      "MutationEvent": false,
      "MutationObserver": false,
      "MutationRecord": false,
      "name": false,
      "NamedNodeMap": false,
      "NavigateEvent": false,
      "navigation": false,
      "Navigation": false,
      "NavigationActivation": false,
      "NavigationCurrentEntryChangeEvent": false,
      "NavigationDestination": false,
      "NavigationHistoryEntry": false,
      "NavigationPreloadManager": false,
      "NavigationTransition": false,
      "navigator": false,
      "Navigator": false,
      "NavigatorLogin": false,
      "NavigatorManagedData": false,
      "NavigatorUAData": false,
      "NetworkInformation": false,
      "Node": false,
      "NodeFilter": false,
      "NodeIterator": false,
      "NodeList": false,
      "Notification": false,
      "NotifyPaintEvent": false,
      "NotRestoredReasonDetails": false,
      "NotRestoredReasons": false,
      "Observable": false,
      "OfflineAudioCompletionEvent": false,
      "OfflineAudioContext": false,
      "offscreenBuffering": false,
      "OffscreenCanvas": false,
      "OffscreenCanvasRenderingContext2D": false,
      "onabort": true,
      "onafterprint": true,
      "onanimationcancel": true,
      "onanimationend": true,
      "onanimationiteration": true,
      "onanimationstart": true,
      "onappinstalled": true,
      "onauxclick": true,
      "onbeforeinput": true,
      "onbeforeinstallprompt": true,
      "onbeforematch": true,
      "onbeforeprint": true,
      "onbeforetoggle": true,
      "onbeforeunload": true,
      "onbeforexrselect": true,
      "onblur": true,
      "oncancel": true,
      "oncanplay": true,
      "oncanplaythrough": true,
      "onchange": true,
      "onclick": true,
      "onclose": true,
      "oncommand": true,
      "oncontentvisibilityautostatechange": true,
      "oncontextlost": true,
      "oncontextmenu": true,
      "oncontextrestored": true,
      "oncopy": true,
      "oncuechange": true,
      "oncut": true,
      "ondblclick": true,
      "ondevicemotion": true,
      "ondeviceorientation": true,
      "ondeviceorientationabsolute": true,
      "ondrag": true,
      "ondragend": true,
      "ondragenter": true,
      "ondragleave": true,
      "ondragover": true,
      "ondragstart": true,
      "ondrop": true,
      "ondurationchange": true,
      "onemptied": true,
      "onended": true,
      "onerror": true,
      "onfocus": true,
      "onformdata": true,
      "ongamepadconnected": true,
      "ongamepaddisconnected": true,
      "ongotpointercapture": true,
      "onhashchange": true,
      "oninput": true,
      "oninvalid": true,
      "onkeydown": true,
      "onkeypress": true,
      "onkeyup": true,
      "onlanguagechange": true,
      "onload": true,
      "onloadeddata": true,
      "onloadedmetadata": true,
      "onloadstart": true,
      "onlostpointercapture": true,
      "onmessage": true,
      "onmessageerror": true,
      "onmousedown": true,
      "onmouseenter": true,
      "onmouseleave": true,
      "onmousemove": true,
      "onmouseout": true,
      "onmouseover": true,
      "onmouseup": true,
      "onmousewheel": true,
      "onoffline": true,
      "ononline": true,
      "onpagehide": true,
      "onpagereveal": true,
      "onpageshow": true,
      "onpageswap": true,
      "onpaste": true,
      "onpause": true,
      "onplay": true,
      "onplaying": true,
      "onpointercancel": true,
      "onpointerdown": true,
      "onpointerenter": true,
      "onpointerleave": true,
      "onpointermove": true,
      "onpointerout": true,
      "onpointerover": true,
      "onpointerrawupdate": true,
      "onpointerup": true,
      "onpopstate": true,
      "onprogress": true,
      "onratechange": true,
      "onrejectionhandled": true,
      "onreset": true,
      "onresize": true,
      "onscroll": true,
      "onscrollend": true,
      "onscrollsnapchange": true,
      "onscrollsnapchanging": true,
      "onsearch": true,
      "onsecuritypolicyviolation": true,
      "onseeked": true,
      "onseeking": true,
      "onselect": true,
      "onselectionchange": true,
      "onselectstart": true,
      "onslotchange": true,
      "onstalled": true,
      "onstorage": true,
      "onsubmit": true,
      "onsuspend": true,
      "ontimeupdate": true,
      "ontoggle": true,
      "ontransitioncancel": true,
      "ontransitionend": true,
      "ontransitionrun": true,
      "ontransitionstart": true,
      "onunhandledrejection": true,
      "onunload": true,
      "onvolumechange": true,
      "onwaiting": true,
      "onwheel": true,
      "open": false,
      "opener": false,
      "Option": false,
      "OrientationSensor": false,
      "origin": false,
      "originAgentCluster": false,
      "OscillatorNode": false,
      "OTPCredential": false,
      "outerHeight": false,
      "outerWidth": false,
      "OverconstrainedError": false,
      "PageRevealEvent": false,
      "PageSwapEvent": false,
      "PageTransitionEvent": false,
      "pageXOffset": false,
      "pageYOffset": false,
      "PannerNode": false,
      "parent": false,
      "PasswordCredential": false,
      "Path2D": false,
      "PaymentAddress": false,
      "PaymentManager": false,
      "PaymentMethodChangeEvent": false,
      "PaymentRequest": false,
      "PaymentRequestUpdateEvent": false,
      "PaymentResponse": false,
      "performance": false,
      "Performance": false,
      "PerformanceElementTiming": false,
      "PerformanceEntry": false,
      "PerformanceEventTiming": false,
      "PerformanceLongAnimationFrameTiming": false,
      "PerformanceLongTaskTiming": false,
      "PerformanceMark": false,
      "PerformanceMeasure": false,
      "PerformanceNavigation": false,
      "PerformanceNavigationTiming": false,
      "PerformanceObserver": false,
      "PerformanceObserverEntryList": false,
      "PerformancePaintTiming": false,
      "PerformanceResourceTiming": false,
      "PerformanceScriptTiming": false,
      "PerformanceServerTiming": false,
      "PerformanceTiming": false,
      "PeriodicSyncManager": false,
      "PeriodicWave": false,
      "Permissions": false,
      "PermissionStatus": false,
      "PERSISTENT": false,
      "personalbar": false,
      "PictureInPictureEvent": false,
      "PictureInPictureWindow": false,
      "Plugin": false,
      "PluginArray": false,
      "PointerEvent": false,
      "PopStateEvent": false,
      "postMessage": false,
      "Presentation": false,
      "PresentationAvailability": false,
      "PresentationConnection": false,
      "PresentationConnectionAvailableEvent": false,
      "PresentationConnectionCloseEvent": false,
      "PresentationConnectionList": false,
      "PresentationReceiver": false,
      "PresentationRequest": false,
      "PressureObserver": false,
      "PressureRecord": false,
      "print": false,
      "ProcessingInstruction": false,
      "Profiler": false,
      "ProgressEvent": false,
      "PromiseRejectionEvent": false,
      "prompt": false,
      "ProtectedAudience": false,
      "PublicKeyCredential": false,
      "PushManager": false,
      "PushSubscription": false,
      "PushSubscriptionOptions": false,
      "queryLocalFonts": false,
      "queueMicrotask": false,
      "RadioNodeList": false,
      "Range": false,
      "ReadableByteStreamController": false,
      "ReadableStream": false,
      "ReadableStreamBYOBReader": false,
      "ReadableStreamBYOBRequest": false,
      "ReadableStreamDefaultController": false,
      "ReadableStreamDefaultReader": false,
      "registerProcessor": false,
      "RelativeOrientationSensor": false,
      "RemotePlayback": false,
      "removeEventListener": false,
      "ReportBody": false,
      "reportError": false,
      "ReportingObserver": false,
      "Request": false,
      "requestAnimationFrame": false,
      "requestIdleCallback": false,
      "resizeBy": false,
      "ResizeObserver": false,
      "ResizeObserverEntry": false,
      "ResizeObserverSize": false,
      "resizeTo": false,
      "Response": false,
      "RestrictionTarget": false,
      "RTCCertificate": false,
      "RTCDataChannel": false,
      "RTCDataChannelEvent": false,
      "RTCDtlsTransport": false,
      "RTCDTMFSender": false,
      "RTCDTMFToneChangeEvent": false,
      "RTCEncodedAudioFrame": false,
      "RTCEncodedVideoFrame": false,
      "RTCError": false,
      "RTCErrorEvent": false,
      "RTCIceCandidate": false,
      "RTCIceTransport": false,
      "RTCPeerConnection": false,
      "RTCPeerConnectionIceErrorEvent": false,
      "RTCPeerConnectionIceEvent": false,
      "RTCRtpReceiver": false,
      "RTCRtpScriptTransform": false,
      "RTCRtpSender": false,
      "RTCRtpTransceiver": false,
      "RTCSctpTransport": false,
      "RTCSessionDescription": false,
      "RTCStatsReport": false,
      "RTCTrackEvent": false,
      "sampleRate": false,
      "scheduler": false,
      "Scheduler": false,
      "Scheduling": false,
      "screen": false,
      "Screen": false,
      "ScreenDetailed": false,
      "ScreenDetails": false,
      "screenLeft": false,
      "ScreenOrientation": false,
      "screenTop": false,
      "screenX": false,
      "screenY": false,
      "ScriptProcessorNode": false,
      "scroll": false,
      "scrollbars": false,
      "scrollBy": false,
      "ScrollTimeline": false,
      "scrollTo": false,
      "scrollX": false,
      "scrollY": false,
      "SecurityPolicyViolationEvent": false,
      "Selection": false,
      "self": false,
      "Sensor": false,
      "SensorErrorEvent": false,
      "Serial": false,
      "SerialPort": false,
      "ServiceWorker": false,
      "ServiceWorkerContainer": false,
      "ServiceWorkerRegistration": false,
      "sessionStorage": false,
      "setInterval": false,
      "setTimeout": false,
      "ShadowRoot": false,
      "sharedStorage": false,
      "SharedStorage": false,
      "SharedStorageAppendMethod": false,
      "SharedStorageClearMethod": false,
      "SharedStorageDeleteMethod": false,
      "SharedStorageModifierMethod": false,
      "SharedStorageSetMethod": false,
      "SharedStorageWorklet": false,
      "SharedWorker": false,
      "showDirectoryPicker": false,
      "showOpenFilePicker": false,
      "showSaveFilePicker": false,
      "SnapEvent": false,
      "SourceBuffer": false,
      "SourceBufferList": false,
      "speechSynthesis": false,
      "SpeechSynthesis": false,
      "SpeechSynthesisErrorEvent": false,
      "SpeechSynthesisEvent": false,
      "SpeechSynthesisUtterance": false,
      "SpeechSynthesisVoice": false,
      "StaticRange": false,
      "status": false,
      "statusbar": false,
      "StereoPannerNode": false,
      "stop": false,
      "Storage": false,
      "StorageBucket": false,
      "StorageBucketManager": false,
      "StorageEvent": false,
      "StorageManager": false,
      "structuredClone": false,
      "styleMedia": false,
      "StylePropertyMap": false,
      "StylePropertyMapReadOnly": false,
      "StyleSheet": false,
      "StyleSheetList": false,
      "SubmitEvent": false,
      "Subscriber": false,
      "SubtleCrypto": false,
      "SuppressedError": false,
      "SVGAElement": false,
      "SVGAngle": false,
      "SVGAnimatedAngle": false,
      "SVGAnimatedBoolean": false,
      "SVGAnimatedEnumeration": false,
      "SVGAnimatedInteger": false,
      "SVGAnimatedLength": false,
      "SVGAnimatedLengthList": false,
      "SVGAnimatedNumber": false,
      "SVGAnimatedNumberList": false,
      "SVGAnimatedPreserveAspectRatio": false,
      "SVGAnimatedRect": false,
      "SVGAnimatedString": false,
      "SVGAnimatedTransformList": false,
      "SVGAnimateElement": false,
      "SVGAnimateMotionElement": false,
      "SVGAnimateTransformElement": false,
      "SVGAnimationElement": false,
      "SVGCircleElement": false,
      "SVGClipPathElement": false,
      "SVGComponentTransferFunctionElement": false,
      "SVGDefsElement": false,
      "SVGDescElement": false,
      "SVGElement": false,
      "SVGEllipseElement": false,
      "SVGFEBlendElement": false,
      "SVGFEColorMatrixElement": false,
      "SVGFEComponentTransferElement": false,
      "SVGFECompositeElement": false,
      "SVGFEConvolveMatrixElement": false,
      "SVGFEDiffuseLightingElement": false,
      "SVGFEDisplacementMapElement": false,
      "SVGFEDistantLightElement": false,
      "SVGFEDropShadowElement": false,
      "SVGFEFloodElement": false,
      "SVGFEFuncAElement": false,
      "SVGFEFuncBElement": false,
      "SVGFEFuncGElement": false,
      "SVGFEFuncRElement": false,
      "SVGFEGaussianBlurElement": false,
      "SVGFEImageElement": false,
      "SVGFEMergeElement": false,
      "SVGFEMergeNodeElement": false,
      "SVGFEMorphologyElement": false,
      "SVGFEOffsetElement": false,
      "SVGFEPointLightElement": false,
      "SVGFESpecularLightingElement": false,
      "SVGFESpotLightElement": false,
      "SVGFETileElement": false,
      "SVGFETurbulenceElement": false,
      "SVGFilterElement": false,
      "SVGForeignObjectElement": false,
      "SVGGElement": false,
      "SVGGeometryElement": false,
      "SVGGradientElement": false,
      "SVGGraphicsElement": false,
      "SVGImageElement": false,
      "SVGLength": false,
      "SVGLengthList": false,
      "SVGLinearGradientElement": false,
      "SVGLineElement": false,
      "SVGMarkerElement": false,
      "SVGMaskElement": false,
      "SVGMatrix": false,
      "SVGMetadataElement": false,
      "SVGMPathElement": false,
      "SVGNumber": false,
      "SVGNumberList": false,
      "SVGPathElement": false,
      "SVGPatternElement": false,
      "SVGPoint": false,
      "SVGPointList": false,
      "SVGPolygonElement": false,
      "SVGPolylineElement": false,
      "SVGPreserveAspectRatio": false,
      "SVGRadialGradientElement": false,
      "SVGRect": false,
      "SVGRectElement": false,
      "SVGScriptElement": false,
      "SVGSetElement": false,
      "SVGStopElement": false,
      "SVGStringList": false,
      "SVGStyleElement": false,
      "SVGSVGElement": false,
      "SVGSwitchElement": false,
      "SVGSymbolElement": false,
      "SVGTextContentElement": false,
      "SVGTextElement": false,
      "SVGTextPathElement": false,
      "SVGTextPositioningElement": false,
      "SVGTitleElement": false,
      "SVGTransform": false,
      "SVGTransformList": false,
      "SVGTSpanElement": false,
      "SVGUnitTypes": false,
      "SVGUseElement": false,
      "SVGViewElement": false,
      "SyncManager": false,
      "TaskAttributionTiming": false,
      "TaskController": false,
      "TaskPriorityChangeEvent": false,
      "TaskSignal": false,
      "TEMPORARY": false,
      "Text": false,
      "TextDecoder": false,
      "TextDecoderStream": false,
      "TextEncoder": false,
      "TextEncoderStream": false,
      "TextEvent": false,
      "TextFormat": false,
      "TextFormatUpdateEvent": false,
      "TextMetrics": false,
      "TextTrack": false,
      "TextTrackCue": false,
      "TextTrackCueList": false,
      "TextTrackList": false,
      "TextUpdateEvent": false,
      "TimeEvent": false,
      "TimeRanges": false,
      "ToggleEvent": false,
      "toolbar": false,
      "top": false,
      "Touch": false,
      "TouchEvent": false,
      "TouchList": false,
      "TrackEvent": false,
      "TransformStream": false,
      "TransformStreamDefaultController": false,
      "TransitionEvent": false,
      "TreeWalker": false,
      "TrustedHTML": false,
      "TrustedScript": false,
      "TrustedScriptURL": false,
      "TrustedTypePolicy": false,
      "TrustedTypePolicyFactory": false,
      "trustedTypes": false,
      "UIEvent": false,
      "URL": false,
      "URLPattern": false,
      "URLSearchParams": false,
      "USB": false,
      "USBAlternateInterface": false,
      "USBConfiguration": false,
      "USBConnectionEvent": false,
      "USBDevice": false,
      "USBEndpoint": false,
      "USBInterface": false,
      "USBInTransferResult": false,
      "USBIsochronousInTransferPacket": false,
      "USBIsochronousInTransferResult": false,
      "USBIsochronousOutTransferPacket": false,
      "USBIsochronousOutTransferResult": false,
      "USBOutTransferResult": false,
      "UserActivation": false,
      "ValidityState": false,
      "VideoColorSpace": false,
      "VideoDecoder": false,
      "VideoEncoder": false,
      "VideoFrame": false,
      "VideoPlaybackQuality": false,
      "ViewTimeline": false,
      "ViewTransition": false,
      "ViewTransitionTypeSet": false,
      "VirtualKeyboard": false,
      "VirtualKeyboardGeometryChangeEvent": false,
      "VisibilityStateEntry": false,
      "visualViewport": false,
      "VisualViewport": false,
      "VTTCue": false,
      "VTTRegion": false,
      "WakeLock": false,
      "WakeLockSentinel": false,
      "WaveShaperNode": false,
      "WebAssembly": false,
      "WebGL2RenderingContext": false,
      "WebGLActiveInfo": false,
      "WebGLBuffer": false,
      "WebGLContextEvent": false,
      "WebGLFramebuffer": false,
      "WebGLObject": false,
      "WebGLProgram": false,
      "WebGLQuery": false,
      "WebGLRenderbuffer": false,
      "WebGLRenderingContext": false,
      "WebGLSampler": false,
      "WebGLShader": false,
      "WebGLShaderPrecisionFormat": false,
      "WebGLSync": false,
      "WebGLTexture": false,
      "WebGLTransformFeedback": false,
      "WebGLUniformLocation": false,
      "WebGLVertexArrayObject": false,
      "WebSocket": false,
      "WebSocketError": false,
      "WebSocketStream": false,
      "WebTransport": false,
      "WebTransportBidirectionalStream": false,
      "WebTransportDatagramDuplexStream": false,
      "WebTransportError": false,
      "WebTransportReceiveStream": false,
      "WebTransportSendStream": false,
      "WGSLLanguageFeatures": false,
      "WheelEvent": false,
      "when": false,
      "window": false,
      "Window": false,
      "WindowControlsOverlay": false,
      "WindowControlsOverlayGeometryChangeEvent": false,
      "Worker": false,
      "Worklet": false,
      "WorkletGlobalScope": false,
      "WritableStream": false,
      "WritableStreamDefaultController": false,
      "WritableStreamDefaultWriter": false,
      "XMLDocument": false,
      "XMLHttpRequest": false,
      "XMLHttpRequestEventTarget": false,
      "XMLHttpRequestUpload": false,
      "XMLSerializer": false,
      "XPathEvaluator": false,
      "XPathExpression": false,
      "XPathResult": false,
      "XRAnchor": false,
      "XRAnchorSet": false,
      "XRBoundedReferenceSpace": false,
      "XRCamera": false,
      "XRCPUDepthInformation": false,
      "XRDepthInformation": false,
      "XRDOMOverlayState": false,
      "XRFrame": false,
      "XRHand": false,
      "XRHitTestResult": false,
      "XRHitTestSource": false,
      "XRInputSource": false,
      "XRInputSourceArray": false,
      "XRInputSourceEvent": false,
      "XRInputSourcesChangeEvent": false,
      "XRJointPose": false,
      "XRJointSpace": false,
      "XRLayer": false,
      "XRLightEstimate": false,
      "XRLightProbe": false,
      "XRPose": false,
      "XRRay": false,
      "XRReferenceSpace": false,
      "XRReferenceSpaceEvent": false,
      "XRRenderState": false,
      "XRRigidTransform": false,
      "XRSession": false,
      "XRSessionEvent": false,
      "XRSpace": false,
      "XRSystem": false,
      "XRTransientInputHitTestResult": false,
      "XRTransientInputHitTestSource": false,
      "XRView": false,
      "XRViewerPose": false,
      "XRViewport": false,
      "XRWebGLBinding": false,
      "XRWebGLDepthInformation": false,
      "XRWebGLLayer": false,
      "XSLTProcessor": false,
      "process": "readonly"
    }
  }
}
Top-10 rules
Rule                                        | Time (ms) | Relative
:-------------------------------------------|----------:|--------:
@typescript-eslint/no-deprecated            | 29612.516 |    40.8%
@typescript-eslint/no-unsafe-assignment     | 10413.936 |    14.4%
@typescript-eslint/no-misused-promises      |  7618.453 |    10.5%
import/namespace                            |  6374.784 |     8.8%
compat/compat                               |  2178.507 |     3.0%
@typescript-eslint/no-unused-vars           |  1299.933 |     1.8%
import/no-unresolved                        |   865.460 |     1.2%
import/no-relative-packages                 |   696.922 |     1.0%
@typescript-eslint/unbound-method           |   541.959 |     0.7%
@typescript-eslint/no-floating-promises     |   519.397 |     0.7%
Benchmark results
$pkgx hyperfine --runs 3 --parameter-list threads off,2,3,4,auto './node_modules/.bin/eslint --quiet --concurrency {threads}'
Benchmark 1: ./node_modules/.bin/eslint --quiet --concurrency off
  Time (mean ± σ):     79.499 s ±  0.637 s    [User: 112.108 s, System: 6.522 s]
  Range (min … max):   79.015 s … 80.220 s    3 runs

Benchmark 2: ./node_modules/.bin/eslint --quiet --concurrency 2
  Time (mean ± σ):     57.637 s ±  0.571 s    [User: 157.154 s, System: 12.519 s]
  Range (min … max):   57.104 s … 58.239 s    3 runs

Benchmark 3: ./node_modules/.bin/eslint --quiet --concurrency 3
  Time (mean ± σ):     53.158 s ±  0.627 s    [User: 205.728 s, System: 21.050 s]
  Range (min … max):   52.579 s … 53.825 s    3 runs

Benchmark 4: ./node_modules/.bin/eslint --quiet --concurrency 4
  Time (mean ± σ):     59.152 s ±  3.709 s    [User: 276.589 s, System: 33.123 s]
  Range (min … max):   56.210 s … 63.318 s    3 runs

Benchmark 5: ./node_modules/.bin/eslint --quiet --concurrency auto
  Time (mean ± σ):     62.648 s ±  0.432 s    [User: 299.816 s, System: 33.271 s]
  Range (min … max):   62.158 s … 62.975 s    3 runs

Summary
  ./node_modules/.bin/eslint --quiet --concurrency 3 ran
    1.08 ± 0.02 times faster than ./node_modules/.bin/eslint --quiet --concurrency 2
    1.11 ± 0.07 times faster than ./node_modules/.bin/eslint --quiet --concurrency 4
    1.18 ± 0.02 times faster than ./node_modules/.bin/eslint --quiet --concurrency auto
    1.50 ± 0.02 times faster than ./node_modules/.bin/eslint --quiet --concurrency off

Caveat: my M2 Macbook Air temp-throttled during the benchmark.

Qualitatively, auto and 3 actually perform the same, bringing down the time from about 95s to 58s, ~39% reduction. In CI, it goes from ~4m30s to ~1m50s, so about 60% reduction.

Amazing! 😍

@fasttime
Copy link
Member Author

Thanks a lot for the benchmarks @DreierF, @mauriciolauffer and @stefanmaric! It would be interesting to see how those results compare with what you get when you run ESLint with --concurrency=off (or no --concurrency specified). Also, if the repositories you are testing are publicly hosted, don't hesitate to post a link in the comments. I'm still having a hard time trying to find projects with many files to use as benchmark references.

@DreierF
Copy link

DreierF commented Jun 12, 2025

I already had a measurement without --concurrency at the very end of the results.

@nzakas
Copy link
Member

nzakas commented Jun 12, 2025

Thanks everyone for the benchmark results! This confirms my theory that the effects of concurrency can't be felt on smaller projects whereas larger projects can see some significant gains. It will be interested to see what the cutoff is and we might want to consider outputting a warning if ESLint is run on a small number of files with concurrency.

@btmills
Copy link
Member

btmills commented Jun 12, 2025

To provide some additional data around a possible cutoff point, here are benchmark results including --concurrency=off on my work project, which uses a config based on https://github.com/btmills/eslint-config-btmills.

Environment: M4 Max, 12 performance, 4 efficiency

I'm using an M4 Max MacBook Pro with 12 performance cores and four efficiency cores.

../node_modules/.bin/eslint --env-info
Environment Info:

Node version: v23.7.0
npm version: v10.9.2
Local ESLint version: Not found
Global ESLint version: v9.28.0
Operating System: darwin 24.5.0
File counts: 1071 total
tokei .
===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
 JavaScript            241        35148        29729         1783         3636
 TSX                   529        82361        75418         1024         5919
 TypeScript            301        20962        17739         1050         2173
===============================================================================
 Total                1071       138471       122886         3857        11728
===============================================================================
Top-10 rules: all plugins

Most of these rules require an upfront information-gathering pass for types or imports.

TIMING=1 ../node_modules/.bin/eslint --quiet
Rule                                      | Time (ms) | Relative
:-----------------------------------------|----------:|--------:
@typescript-eslint/no-unsafe-assignment   |  2965.320 |    24.2%
import/namespace                          |  1905.590 |    15.6%
import/no-named-as-default                |  1901.914 |    15.5%
import/no-duplicates                      |  1115.600 |     9.1%
@typescript-eslint/no-unsafe-argument     |   403.872 |     3.3%
@typescript-eslint/no-unused-vars         |   269.625 |     2.2%
react/jsx-no-constructed-context-values   |   173.008 |     1.4%
@typescript-eslint/promise-function-async |   170.713 |     1.4%
@typescript-eslint/no-floating-promises   |   136.621 |     1.1%
import/no-extraneous-dependencies         |   134.582 |     1.1%
Benchmark results: 4-6 are fastest, 1.33x faster than off
hyperfine --runs 3 --parameter-list threads off,auto,1,2,4,5,6,8,10 '../node_modules/.bin/eslint --quiet --concurrency={threads}'
Benchmark 1: ../node_modules/.bin/eslint --quiet --concurrency=off
  Time (mean ± σ):     18.860 s ±  0.122 s    [User: 27.767 s, System: 2.797 s]
  Range (min … max):   18.737 s … 18.981 s    3 runs

Benchmark 2: ../node_modules/.bin/eslint --quiet --concurrency=auto
  Time (mean ± σ):     15.702 s ±  0.327 s    [User: 104.249 s, System: 19.448 s]
  Range (min … max):   15.473 s … 16.077 s    3 runs

Benchmark 3: ../node_modules/.bin/eslint --quiet --concurrency=1
  Time (mean ± σ):     22.350 s ±  0.372 s    [User: 32.224 s, System: 3.333 s]
  Range (min … max):   21.934 s … 22.652 s    3 runs

Benchmark 4: ../node_modules/.bin/eslint --quiet --concurrency=2
  Time (mean ± σ):     15.997 s ±  0.245 s    [User: 43.860 s, System: 5.310 s]
  Range (min … max):   15.735 s … 16.221 s    3 runs

Benchmark 5: ../node_modules/.bin/eslint --quiet --concurrency=4
  Time (mean ± σ):     13.985 s ±  0.419 s    [User: 63.594 s, System: 10.143 s]
  Range (min … max):   13.501 s … 14.237 s    3 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Benchmark 6: ../node_modules/.bin/eslint --quiet --concurrency=5
  Time (mean ± σ):     14.046 s ±  0.530 s    [User: 74.804 s, System: 12.085 s]
  Range (min … max):   13.626 s … 14.642 s    3 runs

Benchmark 7: ../node_modules/.bin/eslint --quiet --concurrency=6
  Time (mean ± σ):     14.284 s ±  0.075 s    [User: 86.378 s, System: 14.080 s]
  Range (min … max):   14.225 s … 14.369 s    3 runs

Benchmark 8: ../node_modules/.bin/eslint --quiet --concurrency=8
  Time (mean ± σ):     15.544 s ±  0.119 s    [User: 103.929 s, System: 19.400 s]
  Range (min … max):   15.431 s … 15.668 s    3 runs

Benchmark 9: ../node_modules/.bin/eslint --quiet --concurrency=10
  Time (mean ± σ):     18.155 s ±  0.534 s    [User: 132.599 s, System: 25.952 s]
  Range (min … max):   17.553 s … 18.575 s    3 runs

Summary
  ../node_modules/.bin/eslint --quiet --concurrency=4 ran
    1.00 ± 0.05 times faster than ../node_modules/.bin/eslint --quiet --concurrency=5
    1.02 ± 0.03 times faster than ../node_modules/.bin/eslint --quiet --concurrency=6
    1.11 ± 0.03 times faster than ../node_modules/.bin/eslint --quiet --concurrency=8
    1.12 ± 0.04 times faster than ../node_modules/.bin/eslint --quiet --concurrency=auto
    1.14 ± 0.04 times faster than ../node_modules/.bin/eslint --quiet --concurrency=2
    1.30 ± 0.05 times faster than ../node_modules/.bin/eslint --quiet --concurrency=10
    1.35 ± 0.04 times faster than ../node_modules/.bin/eslint --quiet --concurrency=off
    1.60 ± 0.05 times faster than ../node_modules/.bin/eslint --quiet --concurrency=1

Copy link
Member Author

@fasttime fasttime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still looking for feedback on #19794 (comment).

fasttime and others added 2 commits August 6, 2025 08:12
Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
@mdjermanovic mdjermanovic added the accepted There is consensus among the team that this change meets the criteria for inclusion label Aug 7, 2025
@TkDodo
Copy link

TkDodo commented Aug 13, 2025

I made some benchmarks on the getsentry/sentry codebase, which has over 1M lines of TypeScript to lint. Our eslint config is here.

Machine stats:

Apple M3 Pro
11 cores (5 performance and 6 efficiency)
36GB RAM

node v22.16.0
pnpm v10.10.0

before, with eslint 9.32.0:

hyperfine --runs=3 'pnpm run lint:js'

Benchmark 1: pnpm run lint:js
  Time (mean ± σ):     202.509 s ±  4.070 s    [User: 348.777 s, System: 23.251 s]
  Range (min … max):   199.176 s … 207.045 s    3 runs

with this PR:

hyperfine --runs=3 --parameter-list threads off,2,3,4,auto 'pnpm run lint:js --concurrency={threads}'

Benchmark 1: pnpm run lint:js --concurrency=off
  Time (mean ± σ):     189.424 s ±  1.642 s    [User: 330.061 s, System: 20.740 s]
  Range (min … max):   188.384 s … 191.317 s    3 runs
 
Benchmark 2: pnpm run lint:js --concurrency=2
  Time (mean ± σ):     127.073 s ±  2.633 s    [User: 327.366 s, System: 32.953 s]
  Range (min … max):   125.368 s … 130.105 s    3 runs
 
Benchmark 3: pnpm run lint:js --concurrency=3
  Time (mean ± σ):     118.972 s ±  4.122 s    [User: 403.752 s, System: 67.801 s]
  Range (min … max):   116.233 s … 123.714 s    3 runs
 
Benchmark 4: pnpm run lint:js --concurrency=4
  Time (mean ± σ):     131.240 s ± 16.753 s    [User: 477.990 s, System: 118.154 s]
  Range (min … max):   120.795 s … 150.564 s    3 runs
 
Benchmark 5: pnpm run lint:js --concurrency=auto
  Time (mean ± σ):     161.948 s ± 31.493 s    [User: 555.238 s, System: 190.366 s]
  Range (min … max):   140.644 s … 198.123 s    3 runs

concurrency 3 performed best, and the savings are substantial - about 35% 🎉

It’s also interesting that from the concurrency setting, auto was performing the worst

@kachkaev

This comment was marked as off-topic.

@nzakas
Copy link
Member

nzakas commented Aug 13, 2025

@kachkaev that's off-topic for this PR. If you'd like to discuss sharding further, please open a separate issue.

Copy link
Member

@nzakas nzakas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Accepting we'll probably get some bug reports once I released, I think this is a good starting point. Nice work!

Holding off on merging for @mdjermanovic to verify.

@nzakas nzakas moved this from Implementing to Second Review Needed in Triage Aug 13, 2025
@fasttime
Copy link
Member Author

fasttime commented Aug 13, 2025

Thanks for the results @TkDodo. For large projects, concurrency auto sets the thread count to half the available CPU cores. That's 5 threads in your case. I assume that this strategy isn't ideal for machines with multiple types of cores, like performance/efficiency.

@wagenet
Copy link

wagenet commented Aug 13, 2025

FWIW, I've been using this branch on a large codebase and it makes a huge difference and I've had no issues with it so far.

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, great work!

@mdjermanovic mdjermanovic merged commit 0bb777a into main Aug 15, 2025
31 checks passed
@mdjermanovic mdjermanovic deleted the rfc129 branch August 15, 2025 11:37
@github-project-automation github-project-automation bot moved this from Second Review Needed to Complete in Triage Aug 15, 2025
robbevp pushed a commit to robbevp/website-robbevanpetegem that referenced this pull request Aug 24, 2025
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [eslint](https://eslint.org) ([source](https://github.com/eslint/eslint)) | devDependencies | minor | [`9.33.0` -> `9.34.0`](https://renovatebot.com/diffs/npm/eslint/9.33.0/9.34.0) |

---

### Release Notes

<details>
<summary>eslint/eslint (eslint)</summary>

### [`v9.34.0`](https://github.com/eslint/eslint/releases/tag/v9.34.0)

[Compare Source](eslint/eslint@v9.33.0...v9.34.0)

#### Features

- [`0bb777a`](eslint/eslint@0bb777a) feat: multithread linting ([#&#8203;19794](eslint/eslint#19794)) (Francesco Trotta)
- [`43a5f9e`](eslint/eslint@43a5f9e) feat: add eslint-plugin-regexp to eslint-config-eslint base config ([#&#8203;19951](eslint/eslint#19951)) (Pixel998)

#### Bug Fixes

- [`9b89903`](eslint/eslint@9b89903) fix: default value of accessor-pairs option in rule.d.ts file ([#&#8203;20024](eslint/eslint#20024)) (Tanuj Kanti)
- [`6c07420`](eslint/eslint@6c07420) fix: fix spurious failure in neostandard integration test ([#&#8203;20023](eslint/eslint#20023)) (Kirk Waiblinger)
- [`676f4ac`](eslint/eslint@676f4ac) fix: allow scientific notation with trailing zeros matching exponent ([#&#8203;20002](eslint/eslint#20002)) (Sweta Tanwar)

#### Documentation

- [`0b4a590`](eslint/eslint@0b4a590) docs: make rulesdir deprecation clearer ([#&#8203;20018](eslint/eslint#20018)) (Domenico Gemoli)
- [`327c672`](eslint/eslint@327c672) docs: Update README (GitHub Actions Bot)
- [`bf26229`](eslint/eslint@bf26229) docs: Fix typo in core-concepts/index.md ([#&#8203;20009](eslint/eslint#20009)) (Tobias Hernstig)
- [`2309327`](eslint/eslint@2309327) docs: fix typo in the "Configuring Rules" section ([#&#8203;20001](eslint/eslint#20001)) (ghazi-git)
- [`2b87e21`](eslint/eslint@2b87e21) docs: \[no-else-return] clarify sample code. ([#&#8203;19991](eslint/eslint#19991)) (Yuki Takada (Yukinosuke Takada))
- [`c36570c`](eslint/eslint@c36570c) docs: Update README (GitHub Actions Bot)

#### Chores

- [`f19ad94`](eslint/eslint@f19ad94) chore: upgrade to `@eslint/js@9.34.0` ([#&#8203;20030](eslint/eslint#20030)) (Francesco Trotta)
- [`b48fa20`](eslint/eslint@b48fa20) chore: package.json update for [@&#8203;eslint/js](https://github.com/eslint/js) release (Jenkins)
- [`4bce8a2`](eslint/eslint@4bce8a2) chore: package.json update for eslint-config-eslint release (Jenkins)
- [`0c9999c`](eslint/eslint@0c9999c) refactor: prefer default options in `grouped-accessor-pairs` ([#&#8203;20028](eslint/eslint#20028)) (루밀LuMir)
- [`d503f19`](eslint/eslint@d503f19) ci: fix `stale.yml` ([#&#8203;20010](eslint/eslint#20010)) (루밀LuMir)
- [`e2dc67d`](eslint/eslint@e2dc67d) ci: centralize `stale.yml` ([#&#8203;19994](eslint/eslint#19994)) (루밀LuMir)
- [`7093cb8`](eslint/eslint@7093cb8) ci: bump actions/checkout from 4 to 5 ([#&#8203;20005](eslint/eslint#20005)) (dependabot\[bot])

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS42MS4wIiwidXBkYXRlZEluVmVyIjoiNDEuNjEuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: https://git.robbevp.be/robbevp/website-robbevanpetegem/pulls/495
Co-authored-by: Renovate Bot <renovate@robbevp.be>
Co-committed-by: Renovate Bot <renovate@robbevp.be>
@jeremycolin
Copy link

jeremycolin commented Aug 26, 2025

Sorry if this is creating noise, at storyblok - we managed to shave ~ 40% time on our CI with eslint by setting concurrency auto on our main frontend monorepo. Just wanted to say thank you for the hard work. (We have a strict setup with vue-eslint-parser which leads to relatively slow runs)

Benchmark 1: yarn eslint src --concurrency off
Time (mean ± σ): 788.603 s ± 2.202 s [User: 936.620 s, System: 11.756 s]
Range (min … max): 787.046 s … 790.160 s 2 runs

Benchmark 2: yarn eslint src --concurrency auto
Time (mean ± σ): 423.348 s ± 2.333 s [User: 981.695 s, System: 17.323 s]
Range (min … max): 421.698 s … 424.998 s 2 runs

@nzakas
Copy link
Member

nzakas commented Aug 26, 2025

@jeremycolin would you mind sharing that on this discussion? #20040

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion cli Relates to ESLint's command-line interface core Relates to ESLint's core APIs and features feature This change adds a new feature to ESLint
Projects
Status: Complete
Development

Successfully merging this pull request may close these issues.

Lint multiple files in parallel [$500]