eslint: Help us know when we have conflicting rules: 10 passes should throw an error

ESLint version

v8.5.0

What problem do you want to solve?

Between eslint, prettier, import, and @typescript/eslint, it’s really easy to accidentally include rules with fixes that overlap or conflict with other rules we have enabled.

This can cause eslint --fix significantly longer to run as keeps re-fixing the same files up to 10 times.

It might also means users are not necessarily getting the output they desired if the wrong rule is the last to run in that loop of 10 attempts.

What do you think is the correct solution?

If eslint --fix needs to do 10 passes, then throw error and say which plugins are possibly conflicting.

  1. Baseline: Throw an error and say which plugins are possibly conflicting.
  2. Even nicer: Show the rule names and their options.
  3. Even more helpful: Show the HTML after each pass so we can visually see what’s going on.
  4. Super helpful but not necessary: Show the differences as a diff.
  5. Might be nice: In the fix loops, cache the previous versions, so we can detect as soon as we see a repeating pattern.

Participation

  • I am willing to submit a pull request for this change.

Additional comments

This is the code where I am seeing the 10 passes not be treated as an error: https://github.com/eslint/eslint/blob/main/lib/linter/linter.js#L2079-L2105

Also, I’m not sure if this is considered a breaking change, so it might need to wait for v9. Maybe a compromise is to warn in v8 but throw an error in v9?

I’ve made contributions to open source projects, but not eslint. I’m not sure if this is a good first issue, but I’m happy to try.

About this issue

  • Original URL
  • State: open
  • Created 9 months ago
  • Comments: 21 (18 by maintainers)

Most upvoted comments

@DMartens I’m sorry, I was actually addressing @mdjermanovic with my comment. I think your comment posted just before I hit the button. 😃

I don’t think a solution that requires constant updating of metadata is a good idea. We need something that works automatically.

Hi @nzakas - Here’s a working reproduction that you can test in your browser.

https://stackblitz.com/edit/node-ugx5i6?file=module.ts,.eslintrc.js,index.tsx

Run yarn lint or npm run lint in the terminal to see the results.

Results from terminal
~/projects/node-ugx5i6
❯ npm install

added 213 packages in 2s

89 packages are looking for funding
  run `npm fund` for details

~/projects/node-ugx5i6 2s
❯ npm run lint

> nodejs@1.0.0 lint
> DEBUG=eslint:linter eslint . --fix

  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 1) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +2ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +748ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 1) +108ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 2) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +13ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 2) +39ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 3) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +9ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 3) +32ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 4) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +8ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +1ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 4) +32ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 5) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +11ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 5) +40ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 6) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +10ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 6) +31ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 7) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +9ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 7) +28ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 8) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +7ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 8) +24ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 9) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +8ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +1ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 9) +24ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/index.tsx (pass 10) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +7ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/index.tsx (pass 10) +23ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/index.tsx +6ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/index.tsx +0ms
  eslint:linter Linting code for /home/projects/node-ugx5i6/module.ts (pass 1) +22ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: /home/projects/node-ugx5i6/module.ts +0ms
  eslint:linter Parsing: /home/projects/node-ugx5i6/module.ts +0ms
  eslint:linter Parsing successful: /home/projects/node-ugx5i6/module.ts +3ms
  eslint:linter Scope analysis: /home/projects/node-ugx5i6/module.ts +0ms
  eslint:linter Scope analysis successful: /home/projects/node-ugx5i6/module.ts +0ms
  eslint:linter Generating fixed text for /home/projects/node-ugx5i6/module.ts (pass 1) +4ms

/home/projects/node-ugx5i6/index.tsx
  10:1  error  Import "Example" is only used as types  @typescript-eslint/consistent-type-imports

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.


Hi @nzakas, thanks for the quick response and helpful questions!

Culprit

The culprit when I created the issue was two conflicting rules both from the import plugin, caused by a a bug with prefer-inline.

The repo that I noticed the slow linting had this happen in about about 7000 files, and it was happening in nearly all of them (our React patterns monorepo).

Debugging the issue

I was using DEBUG="eslint:linter" but the output was verbose for me, especially with that many files, so it was hard to notice the pattern of repeating passes. I stopped using DEBUG and just added my own console logs that included the filename, which plugin caused the fix, and the changed code. The DEBUG made it easy to figure out where I should look to add my own console logs.

What output would I expect?

Ideally, something like this, though the fix comparison might be impractical or need to be trimmed to make sure it’s readable.

Error: Conflicting rules are causing a potentially infinite loop while fixing the code:

Original code:

    import type React from 'react';
    import { useId, useState } from 'react';

Fix from: import/no-duplicates:

    import type React, { useId , useState } from 'react';

Fix from: import/consistent-type-specifier-style:

    import type React from 'react';
    import { useId, useState } from 'react';

This was stopped after repeating 10 times in: monorepo-code/packages/ui/src/code/Code.tsx
 
To remediate this, disable one or more of the rules.

Also, maybe it doesn’t need to throw a fatal error, but as a lint error, so this can be visible in IDE’s.