remix-run / remix

Build Better Websites. Create modern, resilient user experiences with web fundamentals.
https://remix.run
MIT License
29.74k stars 2.51k forks source link

ESLint v9 doesn't work with .eslintrc.cjs config #10109

Open anton-pas opened 1 week ago

anton-pas commented 1 week ago

Reproduction

I've installed ESlint v9.12.0 and would like to update the old eslint config (.eslintrc.js file). It looks like this:

module.exports = {
    extends: [
        '@remix-run/eslint-config',
        '@remix-run/eslint-config/node',
        '@remix-run/eslint-config/jest-testing-library',
        'prettier',
    ],
    ignorePatterns: [
        '**/*.test.ts',
        '**/*.test.js',
        '**/*.test.tsx',
        '**/*.test.jsx',
        '**/*.spec.ts',
        '**/*.spec.js',
        '**/*.spec.tsx',
        '**/*.spec.jsx',
    ],
    settings: {
        jest: {
            version: 27,
        },
    },
    rules: {
        'react/display-name': 'off',
    },
};

So I wonder if there any examples of configs in a new format like eslint.config.js, eslint.config.mjs or eslint.config.cjs specifically for Remix framework.

I've seen this file - templates/remix/.eslintrc.cjs. Can it be considered as default eslint config for now?

System Info

System:
    OS: macOS 13.6.7
    CPU: (10) arm64 Apple M1 Pro
    Memory: 118.58 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.18.2 - ~/.nvm/versions/node/v18.18.2/bin/node
    npm: 9.8.1 - ~/.nvm/versions/node/v18.18.2/bin/npm
  Browsers:
    Safari: 18.0
  npmPackages:
    @remix-run/dev: 2.11.2 => 2.11.2

Used Package Manager

npm

Expected Behavior

A new eslint config is present for Eslint v9 (eslint.config.js, eslint.config.mjs or eslint.config.cjs)

Actual Behavior

Existing eslint config is not apadted to the latest version of ESlint

HW13 commented 2 days ago

As of Eslint v9 the old .eslintrc.js style config is deprecated and disabled by default. If you still want to use it you need to set a ESLINT_USE_FLAT_CONFIG env var to false. Source -> https://eslint.org/docs/latest/use/configure/configuration-files-deprecated

Since @remix-run/eslint-config uses the old eslintrc format, the config can't be reused in the new flat config format. The rules and settings are re-usable, but you'd need to import them directly and you'd also need to manually add the plugins.

Like this:

// eslint.config.mjs

import remixCore from '@remix-run/eslint-config/rules/core.js'
import remixImport from '@remix-run/eslint-config/rules/import.js'
import remixJsxA11y from '@remix-run/eslint-config/rules/jsx-a11y.js'
import remixReact from '@remix-run/eslint-config/rules/react.js'
import remixReactSettings from '@remix-run/eslint-config/settings/react.js'
import pluginImport from 'eslint-plugin-import'
import pluginJsxA11y from 'eslint-plugin-jsx-a11y'
import pluginReact from 'eslint-plugin-react'
import pluginReactHooks from 'eslint-plugin-react-hooks'

const config = [{
  settings: {
    ...remixReactSettings
  },
  plugins: {
    'jsx-a11y': pluginJsxA11y,
    import: pluginImport,
    react: pluginReact,
    'react-hooks': pluginReactHooks
  },
  rules: {
    ...remixCore,
    ...remixImport,
    ...remixReact,
    ...remixJsxA11y
  }
}]

export default config

Keep in mind this example doesn't take into account the overrides, globals, or ignored files used in @remix-run/eslint-config.

If it's cool with the Remix team I would be happy to dive into this further and open a PR to add a flat config alternative to @remix-run/eslint-config

MichaelDeBoey commented 2 days ago

@HW13 The team has deprecated @remix-run/eslint-config basically right after releasing Remix v2, hence the update to the separate configs in the templates in #7597

HW13 commented 2 days ago

Thanks @MichaelDeBoey, I was not aware of that - my bad 😅

Well, If you guys are interested in migrating the templates to the new flat config format I'd be happy to contribute!

MichaelDeBoey commented 2 days ago

@HW13 I'm currently working on doing exactly that: migrating the templates to the new flat config format

Once I have a PR, I'll link to this issue

anton-pas commented 40 minutes ago

That's what I was able to configure, not sure that's 100% correct variant:

// eslint.config.mjs

/** @type {import('eslint').Linter.FlatConfig[]} */
import js from "@eslint/js";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import typescriptParser from "@typescript-eslint/parser";
import globals from "globals";
import importPlugin from "eslint-plugin-import";

import reactPlugin from "eslint-plugin-react";
import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
import reactHooksPlugin from "eslint-plugin-react-hooks";

const __dirname = new URL(".", import.meta.url).pathname;

export default [
  js.configs.recommended,
  importPlugin.flatConfigs.recommended,
  {
    ignores: [
      "build/*",
      "build/**/*",
      "**/build/**/*",
      "eslint.config.mjs",
      "coverage/*",
      "coverage/**/*",
      "node_modules/*",
      "node_modules/**/*",
      "global.d.ts",
      "**/*.test.ts",
      "**/*.test.js",
      "**/*.test.tsx",
      "**/*.test.jsx",
      "**/*.spec.ts",
      "**/*.spec.js",
      "**/*.spec.tsx",
      "**/*.spec.jsx",
    ],
  },
  {
    files: ["**/*.{js,jsx,ts,tsx}"],
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
      globals: {
        ...globals.browser,
        ...globals.commonjs,
        ...globals.es6,
        ...globals.jest,
        process: "readonly",
      },
    },
    plugins: {
      react: reactPlugin,
      "jsx-a11y": jsxA11yPlugin,
      "react-hooks": reactHooksPlugin,
    },
    rules: {
      ...reactHooksPlugin.configs.recommended.rules,
      ...reactPlugin.configs.recommended.rules,
      ...jsxA11yPlugin.configs.recommended.rules,
      "react/no-unescaped-entities": "off",
      "react/display-name": "off",
      "react/prop-types": "off",
      "no-prototype-builtins": "off",
    },
    settings: {
      react: {
        version: "detect",
      },
      jest: {
        version: 27,
      },
      formComponents: ["Form"],
      linkComponents: [
        { name: "Link", linkAttribute: "to" },
        { name: "NavLink", linkAttribute: "to" },
      ],
      "import/resolver": {
        typescript: {
          alwaysTryTypes: true,
          project: "./tsconfig.json",
        },
      },
      "import/ignore": [".(css)$"],
    },
  },
  // TypeScript configuration
  {
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      parser: typescriptParser,
      parserOptions: {
        tsconfigRootDir: __dirname,
        project: ["./tsconfig.json"],
      },
      globals: {
        ...globals.node,
        React: "readonly",
        NodeJS: "readonly",
      },
    },
    plugins: {
      "@typescript-eslint": typescriptEslint,
    },
    rules: {
      ...typescriptEslint.configs.recommended.rules,
      "@typescript-eslint/no-explicit-any": "off",
      "@typescript-eslint/no-require-imports": "off",
      "@typescript-eslint/no-empty-object-type": "off",
      "@typescript-eslint/ban-ts-comment": "off",
    },
  },
  // Node environment for eslint.config.mjs
  {
    files: ["eslint.config.mjs"],
    env: {
      node: true,
    },
  },
];