8000 Docs: Clarify how the `config()` helper handles rule merging in flat config · Issue #11220 · typescript-eslint/typescript-eslint · GitHub
[go: up one dir, main page]

Skip to content
Docs: Clarify how the config() helper handles rule merging in flat config #11220
Closed as not planned
@wujekbogdan

Description

@wujekbogdan

Before You File a Documentation Request Please Confirm You Have Done The Following...

Suggested Changes

Here's my real-life config:

import jsEslint from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import globals from "globals";
import tsEslint from "typescript-eslint";

export default tsEslint.config(
  jsEslint.configs.recommended,
  tsEslint.configs.recommendedTypeChecked,
  tsEslint.configs.stylisticTypeChecked,
  reactHooks.configs["recommended-latest"],
  reactRefresh.configs.recommended,
  reactRefresh.configs.vite,
  {
    ignores: ["dist"],
  },
  {
    languageOptions: {
      parserOptions: {
        project: ["./tsconfig.node.json", "./tsconfig.app.json"],
        tsconfigRootDir: import.meta.dirname,
      },
      ecmaVersion: 2020,
      globals: globals.browser,
    },
  },
  {
    files: ["**/*.js", "**/*.mjs"],
    extends: [tsEslint.configs.disableTypeChecked],
  },
  eslintConfigPrettier,
);

After setting it up, it turned out there's just one rule I'd like to override, so I turned my config into:

export default tsEslint.config(
  // ... configs
  {
    ignores: ["dist"],
  },
  {
    languageOptions: {
      parserOptions: {
        project: ["./tsconfig.node.json", "./tsconfig.app.json"],
        tsconfigRootDir: import.meta.dirname,
      },
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    rules: {
     // The rule I added:
      "@typescript-eslint/consistent-type-definitions": "off",
    },
  },
  {
    files: ["**/*.js", "**/*.mjs"],
    extends: [tsEslint.configs.disableTypeChecked],
  },
  eslintConfigPrettier,
);

I expected this to simply merge with the existing rules, but it turned out it overwrote all the previously set rules, including react-hooks/rules-of-hooks and react-refresh/only-export-components.

My first instinct was to manually re-add all the previously defined rules:

export default tsEslint.config(
  // ... configs
  {
    ignores: ["dist"],
  },
  {
    languageOptions: {
      parserOptions: {
        project: ["./tsconfig.node.json", "./tsconfig.app.json"],
        tsconfigRootDir: import.meta.dirname,
      },
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    rules: {
      // The rules I brought back:
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
      "react-refresh/only-export-components": "error",
      "@typescript-eslint/consistent-type-definitions": "off",
    },
  },
  {
    files: ["**/*.js"<
78B8
/span>, "**/*.mjs"],
    extends: [tsEslint.configs.disableTypeChecked],
  },
  eslintConfigPrettier,
);

It worked, but the manual process didn't feel right or elegant, so I started fiddling around and figured out that I have to append the rules into the next array item, instead of adding it to the entry that contains languageOptions.

I ended up with:

import jsEslint from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import globals from "globals";
import tsEslint from "typescript-eslint";

export default tsEslint.config(
  // ... configs
  {
    ignores: ["dist"],
  },
  {
    languageOptions: {
      parserOptions: {
        project: ["./tsconfig.node.json", "./tsconfig.app.json"],
        tsconfigRootDir: import.meta.dirname,
      },
      ecmaVersion: 2020,
      globals: globals.browser,
    },
  },
  {
    rules: {
      "@typescript-eslint/consistent-type-definitions": "off",
    },
  },
  {
    files: ["**/*.js", "**/*.mjs"],
    extends: [tsEslint.configs.disableTypeChecked],
  },
  eslintConfigPrettier,
);

It was just a trial-and-error process that led me to that solution, but after figuring it out I still don't really understand why it worked. Why does having rules together with languageOptions prevent merging, whereas a separate entry containing just the rules object is correctly merged?


What I'm proposing is adding a very explicit explanation on how to add custom rule overrides in a safe way - so that preceding rules aren't overridden OR, and maybe it's a better option, clarify how the tsEslint.config() performs merging: what's being merged and what is not.

Affected URL(s)

https://typescript-eslint.io/packages/typescript-eslint#config

Additional Info

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationDocumentation ("docs") that needs adding/updatinglocked due to agePlease open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing.triageWaiting for team members to take a look

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0