Installation

$ pnpm create @eslint/config@latest
$ pnpm add -D @stylistic/eslint-plugin

Check that the following packages have been correctly added:

  "devDependencies": {
    "@eslint/js": "^9.7.0",
    "@stylistic/eslint-plugin": "^2.3.0",
    "eslint": "9.x",
    "globals": "^15.8.0",
    "typescript-eslint": "^7.16.1",

Config

Flat config files

The ESLint “flat config” files are powerful but counter-intuitive to setup. Keep the official documentation on hand.

import globals from 'globals'
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import stylistic from '@stylistic/eslint-plugin'
 
export default tseslint.config(
  {
    // Global ignores: https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores
    ignores: ['dist/**'],
    // DO NOT ADD ANYTHING ELSE IN THIS OBJECT
  },
  {
    files: ['**/*.{ts,js,mjs,cjs}'],
    extends: [
      eslint.configs.recommended,
      ...tseslint.configs.recommended,
      stylistic.configs.customize({
        indent: 2,
        quotes: 'single',
        semi: false,
      }),
    ],
    rules: {
		/* ... */
    },
    languageOptions: {
      globals: globals.browser,
      parserOptions: {
        project: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  {
	// Files that you don't want to typecheck
    files: ['**/*.{mjs,cjs,js}', 'vite.config.ts'],
    extends: [tseslint.configs.disableTypeChecked],
  }
)

Format code with ESLint

In VSCode settings:

{
	"eslint.format.enable": true
}

Attention

If ESLint breaks because of an invalid config, you need to manually restart it, or the formatting action won’t be available

Personal rules

rules: {
  'no-unused-vars': 'off',
  '@typescript-eslint/no-unused-vars': [
    'warn',
    {
      ignoreRestSiblings: true,
      varsIgnorePattern: '^_',
      argsIgnorePattern: '^_',
    },
  ],
  '@typescript-eslint/no-explicit-any': 'warn',
  '@typescript-eslint/no-namespace': 'off',
  '@typescript-eslint/member-ordering': 'error',
  '@typescript-eslint/no-extraneous-class': 'error',
  'sort-imports': [
    'warn',
    {
      ignoreCase: true,
      ignoreDeclarationSort: true,
      ignoreMemberSort: false,
      memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
      allowSeparatedGroups: true,
    },
  ],
 
  // Style
  '@stylistic/object-curly-spacing': ['warn', 'always'],
  '@stylistic/array-bracket-newline': ['warn', 'consistent'],
  '@stylistic/operator-linebreak': ['error', 'after'],
  '@stylistic/arrow-parens': ['error', 'as-needed'],
  '@stylistic/max-statements-per-line': ['error', { max: 2 }],
  '@stylistic/comma-dangle': [
    'warn',
    {
      arrays: 'always-multiline',
      objects: 'always-multiline',
      imports: 'always-multiline',
      exports: 'always-multiline',
      functions: 'never',
    },
  ],
  '@stylistic/array-bracket-spacing': ['error', 'never'],
},