Skip to content

Beuterei/eslint-config

Repository files navigation

Contributors Forks Stargazers Issues


Logo

eslint-config

Shared eslint config

· Report Bug · Request Feature ·

About The Project

Shared eslint config for my projects. Utilizing ESLint.

Installation

npm i -D eslint@9 @beuluis/eslint-config typescript-eslint

typescript-eslint is not required but recommended. See Usage without typescript-eslint

Supported primitives configs

What are primitives?

Primitives are the basis for most language support. They match the file patterns and apply the rules for the respective language. As example the typescript primitive will apply rules only to .ts files and not to .js files.

Supported configs

Usage

For most projects the configuration can be automatched using the auto config. This can be archived with matching the file patterns of the primitives (See What are primitives?). This includes the following configs:

  • base
  • typescript
  • regexp
  • react
  • jsdoc
  • json
  • yaml
  • prettier

Create a eslint.config.mjs (or .ts see TypeScript Configuration Files) file with the following content:

// @ts-check

import { config } from "typescript-eslint";

import { auto } from "@beuluis/eslint-config";

export default config(auto, {
    // For global ignores don't define other keys here. Adapt as needed.
    ignores: ["**/dist/**", "**/node_modules/**"],
});

Adapt the ignore patterns as needed based on eslint ignore.

Cache

Utilizing the cache is highly recommended.

See eslint cache

Configs

Loading configs only for matching configs is important since it improves performance and ensures the correct rules are applied.

See config(...)

⚠️ Since for other tooling the structure of your project is not assumed. It is necessary to configure the glob patterns according to your project structure. The provided examples are only for reference.

Browser

// @ts-check

import { config } from "typescript-eslint";

import { auto, browser } from "@beuluis/eslint-config";

export default config(
    auto,
    {
        files: ["*.{js,mjs,cjs,jsx,tsx,ts}"],
        extends: browser,
    },
    {
        // For global ignores don't define other keys here. Adapt as needed.
        ignores: ["**/dist/**", "**/node_modules/**"],
    },
);

Cypress

// @ts-check

import { config } from "typescript-eslint";

import { auto, cypress } from "@beuluis/eslint-config";

export default config(
    auto,
    {
        files: ["**/cypress/**/*.{js,ts,jsx,tsx}", "**/cypress.config.{js,ts}"],
        extends: cypress,
    },
    {
        // For global ignores don't define other keys here. Adapt as needed.
        ignores: ["**/dist/**", "**/node_modules/**"],
    },
);

Jest with TypeScript

Since some rules differ between jest and jest with typescript I have a dedicated config for it.

// @ts-check

import { config } from "typescript-eslint";

import { auto, jestTs } from "@beuluis/eslint-config";

export default config(
    auto,
    {
        files: [
            "**/*.?(component-){spec,test}.{ts,tsx}",
            "**/{__mocks__,__tests__}/**/*.{ts,tsx}",
            "**/jest.setup.{ts}",
        ],
        extends: jestTs,
    },
    {
        // For global ignores don't define other keys here. Adapt as needed.
        ignores: ["**/dist/**", "**/node_modules/**", "**/coverage/**"],
    },
);

Jest

// @ts-check

import { config } from "typescript-eslint";

import { auto, jest } from "@beuluis/eslint-config";

export default config(
    auto,
    {
        files: [
            "**/*.?(component-){spec,test}.{js,mjs,cjs,jsx}",
            "**/{__mocks__,__tests__}/**/*.{js,mjs,cjs,jsx}",
            "**/jest.setup.{js,mjs,cjs}",
        ],
        extends: jest,
    },
    {
        // For global ignores don't define other keys here. Adapt as needed.
        ignores: ["**/dist/**", "**/node_modules/**", "**/coverage/**"],
    },
);

Modules

// @ts-check

import { config } from "typescript-eslint";

import { auto, module } from "@beuluis/eslint-config";

export default config(
    auto,
    {
        files: ["**/*.{mjs,js}"],
        extends: module,
    },
    {
        // For global ignores don't define other keys here. Adapt as needed.
        ignores: ["**/dist/**", "**/node_modules/**"],
    },
);

Node

// @ts-check

import { config } from "typescript-eslint";

import { auto, node } from "@beuluis/eslint-config";

export default config(
    auto,
    {
        files: ["**/*.{js,mjs,cjs}"],
        extends: node,
    },
    {
        // For global ignores don't define other keys here. Adapt as needed.
        ignores: ["**/dist/**", "**/node_modules/**"],
    },
);

Vitest

// @ts-check

import { config } from "typescript-eslint";

import { auto, vitest } from "@beuluis/eslint-config";

export default config(
    auto,
    {
        files: [
            "**/*.?(component-){spec,test}.{js,mjs,cjs,jsx}",
            "**/{__mocks__,__tests__}/**/*.{js,mjs,cjs,jsx}",
            "**/vitest.config.{js,mjs,cjs}",
        ],
        extends: vitest,
    },
    {
        // For global ignores don't define other keys here. Adapt as needed.
        ignores: ["**/dist/**", "**/node_modules/**"],
    },
);

Zod

// @ts-check

import { config } from "typescript-eslint";

import { auto, zod } from "@beuluis/eslint-config";

export default config(
    auto,
    {
        files: ["**/*.{js,mjs,cjs,jsx,ts,tsx}"],
        extends: zod,
    },
    {
        // For global ignores don't define other keys here. Adapt as needed.
        ignores: ["**/dist/**", "**/node_modules/**"],
    },
);

VSCode

Needs the official ESLint extension.

Add the following to your .vscode/settings.json in your repository root (Alternatively you can add it to your global settings. Keep in mind this can have side effects). See VSCode settings:

{
    "eslint.useFlatConfig": true,
    // Adapt this to your needs
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        "json",
        "typescript",
        "typescriptreact",
        "yaml"
    ],
    // Only needed for TS and not really related to eslint but one of the main problems I encountered. VSCode otherwise will use its build in typescript language server which may differ from the one you use in your project. This will result in false positives and false negatives.
    "typescript.tsdk": "node_modules/typescript/lib"
}

Autofix on save

Needs the official ESLint extension.

Add this to your settings.json in vscode.

{
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    },
    // This could cause problems so try it out if you want but no guarantee.
    "editor.defaultFormatter": "dbaeumer.vscode-eslint",
    "editor.formatOnSave": true
}

EditorConfig

Here is a EditorConfig to be used alongside this eslint config.

root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false

[*.{json,json5,jsonc,yml,yaml}]
indent_size = 2

Custom configs using primitives

If you do not want to use the auto config you can import all needed configs manually from the primitives export.

Keep in mind that the base config needs to be loaded and loaded first and ending with the prettier config.

Keep in mind that this is basically the wild west of config dependencies and definitions. You can peak into the configurations folder to see how it works. Good luck cowboy!

// @ts-check

import { config } from "typescript-eslint";

import { base, yaml, prettier } from "@beuluis/eslint-config";

export default config(base, yaml, prettier);

Usage without typescript-eslint

⚠️ The name may suggest it is only for TS but it can be used for JS as well

The usage of typescript-eslint is optional. You can use the eslint config without it.

But it offers a create set of utils to make your life a bit easier for merging configs and type safety.

If you do not want to use it please see apply config array

typescript config vs. typescript-no-type-checking config

I decided to do a opt out approach in favor of type safety. The typescript config extends the typescript-no-type-checking config with some more rules that need deeper type checking. In general this is a good thing thats why it is enabled in the default typescript config but for some larger projects or projects with complex types this can be a performance issue. If you experience such performance issues you have two points to consider:

  • Refactor your complex types. Most of the time when ESlint has trouble this is a hint that your types may not be well structure or can be optimized.
  • Turn off this feature by using the typescript-no-type-checking config instead of the typescript config
  • Alternatively, if you're using the auto config, you can use autoNoTypeChecking instead:
// @ts-check

import { config } from "typescript-eslint";

import { autoNoTypeChecking } from "@beuluis/eslint-config";

export default config(autoNoTypeChecking, {
    // For global ignores don't define other keys here
    ignores: ["**/dist/**", "**/node_modules/**"],
});

Migration to 3.0.0

This version is basically a rewrite of the whole thing. It supports eslint v9 which enforces so called flat configs.

But this also comes with way more flexibility and no worries the most heavy lifting is done in this package.

  1. Run npm i -D eslint@9 @beuluis/eslint-config@latest typescript-eslint

  2. Remove any old eslint configurations and ignore files. Those could be in package.json or in a separate config file.

  3. Create a new config file on the root of your project as described in the usage section. Don't forget to add the global ignores back from the old ignore file.

  4. Setup/Update the IDE integration.

  5. Run ESlint and check the output. You can run it with --fix to autofix most of the issues (Please verify the fixes. During migrations you will often encounter false positives just because the pure size of a codebase).

  6. Enjoy

Migration from other linter setups

  1. Remove all previous installed ESlint plugins and configs (This pack comes with all it needs). Keep only those not provides by this pack.

  2. Run npm i -D eslint@9 @beuluis/eslint-config typescript-eslint

  3. Use one of the example configs or build your own based on them

  4. Run ESlint and check the output. You can run it with --fix to autofix most of the issues (Please verify the fixes. During migrations you will often encounter false positives just because teh pure size of a codebase).

  5. Enjoy

Prettier

Prettier is a great tool to provide common formatting for multiple file types across the codebase. The problem is that prettier acts dynamic based on the .prettierrc config present in the project.

An example:

Prettier gets defined with an indent with 2 spaces in the .prettierrc file.

Now some eslint rule requires a indent of 4 spaces and now you have a conflict.

So this package ignores the .prettierrc file and uses the one defined in this package using eslint-plugin-prettier.

Development

Structure

I use the subfolder configurations to define the configurations.

src
├── ...
├── configurations    # All configurations to be used
│ ├── primitives      # Primitive configurations that serve as building blocks
│ │ ├── base.ts       # Base configuration for all ESLint testing
│ │ └── {{name}}.ts   # Other primitive configurations
│ ├── {{name}}.ts     # Other specialized configurations (e.g. different testing libs etc)
│ └── ...
├── primitives.ts     # Primitive configuration export
├── index.ts          # Other configuration export
└── ...

Inside the files I try to keep this structure:

const rules = {
    first_all_rules_that_are_turned_off: "off",
    first_all_rules_that_are_configured_with_warn: "warn",
    first_all_rules_that_are_configured_with_error: "error",
};

Rule values

Rules should use the string value instead of the number to ensure easy reading:

  • "off" instead of 0

  • "warn" instead of 1

  • "error" instead of 2

Testing philosophy

Testing a eslint config is a bit tricky.

There were two main concerns I wanted to cover with the testing strategy:

  1. The configs should be valid and be able to be loaded by eslint
  2. I manually typed some configs from dependency and they could change structure with updates.

So I decided to actually try to initialize the configs with eslint lint a known bad file and see if I get the expected results.

Verbose logging

You can run the tests with npm run test:verbose -- nest to get verbose logging. This will log the results of the linting to the console.

Tips and tricks

  • One of the most confusing part is the override mechanism of eslint.

    Some useful info can be found in config(...) and combine-configs

  • You can use lint-staged to run linting on staged files. This is faster than running it for the whole project. (Remember that if you change something that might affect the linting results, e.g. a change to the eslint configuration, you should run linting for the whole project. This catches errors before GitLab CI).

    Example:

    {
        "lint-staged": {
            "*.{ts,tsx,yaml,yml,json}": ["eslint"] // Adapt to project needs
        }
    }

    And run it as git hook. For example with husky with npx lint-staged --verbose