Shared eslint config
·
Report Bug
·
Request Feature
·
- About The Project
- Supported primitives configs
- Supported configs
- Usage
- VSCode
- Custom configs using primitives
- Usage without typescript-eslint
typescript
config vs.typescript-no-type-checking
config- Migration to 3.0.0
- Migration from other linter setups
- Prettier
- Development
Shared eslint config for my projects. Utilizing ESLint.
npm i -D eslint@9 @beuluis/eslint-config typescript-eslint
typescript-eslint is not required but recommended. See Usage without typescript-eslint
- base - The code style guide base. Needs to be loaded and loaded first.
- jsdoc - For projects using JSDoc
- json - For projects using JSON
- prettier - Applies Prettier formatting
- react - For projects using React
- regexp - For projects that use regular expressions.
- typescript - For projects using TypeScript
- typescript-no-type-checking - For projects using TypeScript and don't want additional rules that require type information (rules using type information take longer to run)
- yaml - For projects using YAML
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.
- browser - For projects that use DOM and other browser APIs.
- cypress - For projects that use Cypress
- jest - For projects using Jest
- jestTs - For projects using Jest with TypeScript
- module - For projects that use ESM modules.
- node - For projects using node.js
- vitest - For projects using Vitest
- zod - For projects using Zod
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.
Utilizing the cache is highly recommended.
See eslint cache
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.
// @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/**"],
},
);
// @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/**"],
},
);
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/**"],
},
);
// @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/**"],
},
);
// @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/**"],
},
);
// @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/**"],
},
);
// @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/**"],
},
);
// @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/**"],
},
);
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"
}
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
}
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
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);
⚠️ 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
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 thetypescript
config - Alternatively, if you're using the
auto
config, you can useautoNoTypeChecking
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/**"],
});
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.
-
Run
npm i -D eslint@9 @beuluis/eslint-config@latest typescript-eslint
-
Remove any old eslint configurations and ignore files. Those could be in
package.json
or in a separate config file. -
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.
-
Setup/Update the IDE integration.
-
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). -
Enjoy
-
Remove all previous installed ESlint plugins and configs (This pack comes with all it needs). Keep only those not provides by this pack.
-
Run
npm i -D eslint@9 @beuluis/eslint-config typescript-eslint
-
Use one of the example configs or build your own based on them
-
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). -
Enjoy
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.
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",
};
Rules should use the string value instead of the number to ensure easy reading:
-
"off"
instead of0
-
"warn"
instead of1
-
"error"
instead of2
Testing a eslint config is a bit tricky.
There were two main concerns I wanted to cover with the testing strategy:
- The configs should be valid and be able to be loaded by eslint
- 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.
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.
-
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