eslintprettiertypescript-eslinteslint-config-airbnbprettier-eslint

Flat config file with configs from legacy eslintrc compat error


I am trying to get prettier, typescript, eslint with airbnb typescript syntax working with linting and auto formatting.

The minimal node package.json file.

{
  "name": "typescript-eslint-airbnb-compat",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint .",
    "lint:fix": "npm run lint -- --fix",
    "format": "prettier . --check",
    "format:fix": "npm run format -- --write"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3.0.2",
    "@eslint/js": "^8.57.0",
    "@typescript-eslint/eslint-plugin": "^7.4.0",
    "@typescript-eslint/parser": "^7.4.0",
    "eslint": "^8.57.0",
    "eslint-config-airbnb-base": "^15.0.0",
    "eslint-config-airbnb-typescript": "^18.0.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-import": "^2.29.1",
    "typescript": "^5.4.3",
    "typescript-eslint": "^7.4.0",
    "vite": "^5.2.0"
  }
}

Here is the flat config file for eslint eslint.config.mjs.

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintConfigPrettier from "eslint-config-prettier";

import { FlatCompat } from "@eslint/eslintrc";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname
});

export default tseslint.config(
  eslint.configs.recommended,
  ...compat.extends("airbnb-base"),
  ...compat.extends("airbnb-typescript/base"),
  ...tseslint.configs.recommendedTypeChecked,
  eslintConfigPrettier,
  {
    languageOptions: {
      parserOptions: {
        project: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  {
      files: ['**/*.{js,jsx,cjs,mjs}'],
      extends: [tseslint.configs.disableTypeChecked],
  }
);

Airbnb and eslint airbnb ts config do not support the new flat config at the time of writing. Therefore, I am using the compat feature to transform the legacy config into the new syntax.

This is the error I am receiving and I haven't been able to solve it.

> typescript-eslint-airbnb-compat@0.0.0 lint
> eslint .


Oops! Something went wrong! :(

ESLint: 8.57.0

Error: Key "plugins": Cannot redefine plugin "@typescript-eslint".
    at .../typescript-eslint-airbnb-compat/node_modules/@humanwhocodes/object-schema/src/object-schema.js:250:27
    at Array.reduce (<anonymous>)
    at ObjectSchema.merge (.../typescript-eslint-airbnb-compat/node_modules/@humanwhocodes/object-schema/src/object-schema.js:237:24)
    at .../typescript-eslint-airbnb-compat/node_modules/@humanwhocodes/config-array/api.js:935:42
    at Array.reduce (<anonymous>)
    at FlatConfigArray.getConfig (.../typescript-eslint-airbnb-compat/node_modules/@humanwhocodes/config-array/api.js:934:39)
    at FlatConfigArray.isFileIgnored (.../typescript-eslint-airbnb-compat/node_modules/@humanwhocodes/config-array/api.js:962:15)
    at .../typescript-eslint-airbnb-compat/node_modules/eslint/lib/eslint/eslint-helpers.js:312:49
    at Array.reduce (<anonymous>)
    at entryFilter (.../typescript-eslint-airbnb-compat/node_modules/eslint/lib/eslint/eslint-helpers.js:299:28)

Is this a bug or am I taking the wrong step somewhere? I am open to any solution and trying different tools and configs if required.


Solution

  • This has been fixed in typescript-eslint v7.5.0

    If you're stuck on an older version you can manually fix the issue by updating the code:

      ...compat.extends("airbnb-typescript/base").map(c => {
        if (c.plugins) {
          // @ts-expect-error
          c.plugins['@typescript-eslint'] = tseslint.plugin;
        }
        return c
      }),
    

    Background:

    The error is unculear but this is working as expected. TL;DR is that our config defines

    {
      plugins: {
        '@typescript-eslint': require('typescript-eslint').plugin
      },
    }
    

    But eslint-config-airbnb-typescript defines

    {
      plugins: {
        '@typescript-eslint': require('@typescript-eslint/eslint-plugin')
      },
    }
    

    ESLint enforces that whilst you may have multiple definitions of a plugin namespace - each definition must have exactly the same value.

    The issue here is that

    require('typescript-eslint').plugin !== require('@typescript-eslint/eslint-plugin')
    

    tseslint.configs.recommendedTypeChecked uses the former and compat.extends("airbnb-typescript/base") uses the latter.

    Hence the error.