| name | ESLint Setup |
| description | This skill should be used when the user asks to "setup ESLint", "configure ESLint", "lint TypeScript", "lint React", "ESLint rules", "code quality", or works with ESLint configuration for JavaScript/TypeScript/React projects. |
| version | 0.1.0 |
ESLint Setup
ESLint is a configurable JavaScript linter that helps you find and fix problems in your code.
Core Concepts
ESLint Flat Config
ESLint v9+ uses the new flat config format (eslint.config.js):
import js from '@eslint/js'
import ts from 'typescript-eslint'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import globals from 'globals'
export default [
{
ignores: ['dist', 'node_modules', '*.config.js'],
},
js.configs.recommended,
...ts.configs.recommendedTypeChecked,
...ts.configs.stylisticTypeChecked,
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: {
...globals.browser,
...globals.es2020,
},
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/consistent-type-imports': 'error',
'no-console': ['warn', { allow: ['error', 'warn'] }],
'prefer-const': 'error',
'no-var': 'error',
},
settings: {
react: {
version: 'detect',
},
},
},
]
Legacy Config (.eslintrc.cjs)
For ESLint < 9 or compatibility:
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-explicit-any': 'warn',
},
settings: {
react: {
version: 'detect',
},
},
}
Installation
Vite + React + TypeScript:
npm install -D eslint @eslint/js typescript typescript-eslint
npm install -D eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh
npm install -D globals
Recommended Configurations
React + TypeScript (Strict):
import js from '@eslint/js'
import ts from 'typescript-eslint'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import importPlugin from 'eslint-plugin-import'
import jsxA11y from 'eslint-plugin-jsx-a11y'
import globals from 'globals'
export default [
{ ignores: ['dist', 'node_modules', 'coverage'] },
js.configs.recommended,
...ts.configs.strictTypeChecked,
...ts.configs.stylisticTypeChecked,
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {
ecmaVersion: 2024,
sourceType: 'module',
globals: {
...globals.browser,
...globals.es2021,
},
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
import: importPlugin,
'jsx-a11y': jsxA11y,
},
rules: {
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'react/prop-types': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always',
alphabetize: { order: 'asc', caseInsensitive: true },
},
],
'import/no-duplicates': 'error',
'import/first': 'error',
...jsxA11y.configs.recommended.rules,
'no-console': ['warn', { allow: ['error', 'warn'] }],
'no-debugger': 'error',
'no-alert': 'error',
'prefer-const': 'error',
'no-var': 'error',
'object-shorthand': 'error',
'prefer-template': 'error',
},
settings: {
react: { version: 'detect' },
'import/resolver': {
typescript: {
project: './tsconfig.json',
},
},
},
},
{
files: ['**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
},
},
]
Package Scripts
{
"scripts": {
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"lint:cache": "eslint . --ext ts,tsx --cache",
"type-check": "tsc --noEmit"
}
}
Prettier Integration
Install:
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
prettier.config.js:
export default {
semi: false,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
printWidth: 100,
arrowParens: 'avoid',
}
Update ESLint config:
import prettier from 'eslint-plugin-prettier'
import prettierConfig from 'eslint-config-prettier'
export default [
{
plugins: {
prettier,
},
rules: {
...prettierConfig.rules,
'prettier/prettier': 'error',
},
},
]
VS Code Settings
.vscode/settings.json:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
"eslint.workingDirectories": [{ "mode": "auto" }],
"typescript.tsdk": "node_modules/typescript/lib"
}
Extensions to install:
- ESLint
- Prettier
- TypeScript Importer
CI Integration
GitHub Actions:
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm run type-check
Pre-commit Hook
Install husky and lint-staged:
npm install -D husky lint-staged
npx husky init
.husky/pre-commit:
npx lint-staged
package.json:
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"]
}
}
Best Practices
- Use flat config for new projects - ESLint v9+ format
- Extend recommended configs - Don't reinvent rules
- Configure parserOptions - Enable type-aware rules
- Use --fix in pre-commit - Auto-fix issues
- Set max-warnings to 0 - Don't allow warnings in CI
- Separate test config - Relax rules for test files
Additional Resources
references/eslint-rules.md - Rule explanations
examples/eslint-configs/ - Complete configurations