| name | eslint |
| description | [Applies to: **/*.{js,jsx,ts,tsx,mts,cts}] This guide defines the definitive ESLint configuration and best practices for our team, ensuring consistent code quality, early error detection, and seamless integration with modern JavaScript and TypeScript workflows. |
| source | cursor_mdc |
eslint Best Practices
ESLint is the bedrock of our JavaScript and TypeScript code quality. This guide outlines our definitive, opinionated approach to configuring and using ESLint, focusing on modern best practices for December 2025.
1. Core Configuration: Flat Config is Mandatory
Always use the new flat configuration format (eslint.config.js, .mjs, .cjs, or .ts). The legacy .eslintrc format is deprecated and must not be used.
1.1. Base Configuration
Start with the recommended and strict presets for robust error prevention and stylistic consistency.
import eslint from '@eslint/js';
import { defineConfig } from 'eslint/config';
export default defineConfig([
eslint.configs.recommended,
eslint.configs.strict,
eslint.configs.stylistic,
]);
1.2. TypeScript Integration (Type-Aware Linting)
For TypeScript projects, enable type-aware linting. This provides powerful, deep analysis that catches subtle type-related issues.
import eslint from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
export default defineConfig([
eslint.configs.recommended,
tseslint.configs.recommendedTypeChecked,
tseslint.configs.strictTypeChecked,
tseslint.configs.stylisticTypeChecked,
{
languageOptions: {
parser: tseslint.parser,
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
},
]);
Performance Note: Type-aware linting adds overhead. This is a worthwhile trade-off for the increased safety and quality. Rely on IDE extensions for instant feedback and run full linting in CI.
1.3. Prettier Integration
ESLint handles code quality; Prettier handles code formatting. Use eslint-config-prettier to disable any ESLint rules that conflict with Prettier, preventing unnecessary warnings.
import eslint from '@eslint/js';
import { defineConfig } from 'eslint/config';
import prettierConfig from 'eslint-config-prettier';
export default defineConfig([
eslint.configs.recommended,
prettierConfig,
{
rules: {
'no-console': 'warn',
},
},
]);
2. Code Organization and Structure
Apply rules precisely where they're needed using files and ignores.
import { defineConfig } from 'eslint/config';
export default defineConfig([
{
files: ['src/**/*.js', 'src/**/*.jsx'],
rules: {
'react/jsx-uses-react': 'off',
'react/react-in-jsx-scope': 'off',
},
},
{
files: ['**/*.test.js', '**/*.spec.ts'],
rules: {
'no-unused-expressions': 'off',
'jest/expect-expect': 'error',
},
},
{
ignores: ['dist/', 'node_modules/', 'coverage/'],
},
]);
3. Common Patterns and Anti-patterns
Enforce these critical rules to maintain high code quality.
3.1. Immutability (prefer-const)
Always use const unless a variable's value is reassigned.
❌ BAD
let name = 'Alice';
name = 'Bob';
✅ GOOD
const name = 'Alice';
let age = 30;
age++;
3.2. Unused Variables (no-unused-vars)
Remove dead code. Unused variables, functions, or imports indicate cruft.
❌ BAD
const unusedVar = 10;
function doSomething() { }
✅ GOOD
const usedVar = 10;
console.log(usedVar);
3.3. Consistent Returns (consistent-return)
Ensure functions either always return a value or never return one explicitly. Avoid implicit undefined returns.
❌ BAD
function process(value) {
if (value > 0) {
return value * 2;
}
}
✅ GOOD
function process(value) {
if (value > 0) {
return value * 2;
}
return 0;
}
function logValue(value) {
console.log(value);
}
3.4. No Else Return (no-else-return)
Simplify conditional logic by returning early.
❌ BAD
function getValue(condition) {
if (condition) {
return 'A';
} else {
return 'B';
}
}
✅ GOOD
function getValue(condition) {
if (condition) {
return 'A';
}
return 'B';
}
3.5. Strict Equality (eqeqeq)
Always use === and !== to prevent type coercion issues.
❌ BAD
if (value == null) { }
✅ GOOD
if (value === null || value === undefined) { }
if (value === 0) { }
3.6. No Console Logs (no-console)
Prevent accidental console.log statements from reaching production.
export default defineConfig([
{
rules: {
'no-console': ['error', { allow: ['warn', 'error'] }],
},
},
]);
3.7. No Magic Numbers (no-magic-numbers)
Replace unexplained numeric literals with named constants for readability and maintainability.
❌ BAD
function calculateArea(radius) {
return 3.14159 * radius * radius;
}
✅ GOOD
const PI = 3.14159;
function calculateArea(radius) {
return PI * radius * radius;
}
4. Common Pitfalls and Gotchas
4.1. Avoid eslint-disable
Never use eslint-disable comments. They mask underlying issues, accumulate technical debt, and compromise code quality. If a rule is genuinely problematic for a specific, rare case, discuss it with the team to adjust the global configuration or create a targeted override.
❌ BAD
console.log('Debug info');
✅ GOOD
4.2. Configuration Order Matters
Ensure eslint-config-prettier is always the last configuration in your array to correctly disable conflicting rules.
import eslint from '@eslint/js';
import prettierConfig from 'eslint-config-prettier';
export default defineConfig([
eslint.configs.recommended,
prettierConfig,
{
rules: {
},
},
]);
5. Testing Approaches
Integrate ESLint into your development workflow for proactive issue detection.
5.1. Pre-commit Hooks (Husky + lint-staged)
Enforce linting on staged files before every commit. This ensures only clean code enters the repository.
{
"name": "your-project",
"devDependencies": {
"husky": "^9.0.0",
"lint-staged": "^15.0.0",
"eslint": "^9.0.0"
},
"scripts": {
"prepare": "husky",
"lint": "eslint .",
"lint:fix": "eslint --fix ."
},
"lint-staged": {
"*.{js,jsx,ts,tsx,mts,cts}": "eslint --fix"
}
}
Then, set up Husky:
npx husky init
npx husky add .husky/pre-commit "npx lint-staged"
5.2. CI/CD Integration
Run ESLint as a mandatory step in your Continuous Integration pipeline. This acts as a final gatekeeper for code quality.
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint