ESLint Catches the Bugs Code Review Misses
From "code that just runs" to "code that runs and is elegant," the gap is bridged by ESLint.
One-Sentence Answer
ESLint is a static analysis tool for JavaScript/TypeScript code—it doesn't run your code, but reads your code, finds syntax errors, bad smells, and inconsistent styles, then tells you where to change and how.
Why Do You Need ESLint?
1. Catch Low-Level Errors Early
// What do you think is wrong with this code?
if (isAdmin = true) {
deleteUserAccount();
}
Did you write = instead of ==? No, an assignment was placed inside an if condition. This code runs without a syntax error, but it will delete the user account when you're not paying attention. ESLint's no-cond-assign rule flags it the moment you type it.
Similar issues include:
- Variables declared but never used (
no-unused-vars) - Forgotten
console.logstatements (no-console) - Unreachable code after a
return(no-unreachable) - Mixing
==and===(eqeqeq)
These bugs are often hard to track down at runtime, but static analysis can find them in 0.1 seconds.
2. Unify Team Style, Eliminate Pointless Code Review Debates
"Should curly braces go on the same line or a new line?" "Tabs or spaces for indentation?" "Semicolons or not?"
These questions don't belong in Code Review. They have no right or wrong answer, but when a team isn't unified, reading code creates cognitive load. ESLint hardens these style decisions into rules, automatically checking before code submission and reporting errors for non-compliance.
// ESLint makes the team's code look the same
const foo = () => {
if (condition) {
doSomething();
}
};
3. Enforce Best Practices
JavaScript has many patterns that "work but aren't good." ESLint includes a large set of best-practice rules:
| Rule | Prevents What |
|---|---|
no-var |
Encourages let/const over var, avoiding hoisting traps |
prefer-const |
Variables never reassigned must use const |
no-eval |
Bans eval(), preventing injection attacks |
no-implicit-globals |
Prevents accidental global variable creation |
prefer-arrow-callback |
Encourages arrow functions, reducing this binding issues |
no-duplicate-case |
Prevents duplicate case clauses in switch |
4. Deep Editor Integration, Real-Time Feedback
Editors like VS Code and WebStorm support ESLint plugins. As you code, the editor can:
- Draw red squiggly lines under problematic code
- Show specific issues and fix suggestions
- Auto-fix with one click (
--fix)
This "correct as you err" experience is far more efficient than waiting for CI to find problems.
How ESLint Works
ESLint's core process can be summarized in three steps:
Parse → Traverse → Report
- Parse: ESLint parses your source code into an AST (Abstract Syntax Tree). JavaScript uses the Espree parser; TypeScript requires
@typescript-eslint/parser. - Traverse: ESLint walks every node of the AST, checking for matching rules at each node.
- Report: When a rule matches, it outputs diagnostic information at the level configured (
off/warn/error).
Plugins provide rules, and configuration decides which rules are enabled and at what level. This is ESLint's "plugins + configuration" design philosophy.
Quick Start
Installation
npm install -D eslint
Initialize Configuration
npx eslint --init
ESLint will ask a few questions (project type, framework, TypeScript usage, etc.) and then generate .eslintrc.js:
// .eslintrc.js — Legacy config format (eslintrc)
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: 'eslint:recommended',
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module',
},
rules: {
'no-unused-vars': 'warn',
'no-console': 'warn',
'eqeqeq': 'error',
'prefer-const': 'error',
},
};
New Config Format (Flat Config)
ESLint 9+ defaults to the flat configuration eslint.config.js:
// eslint.config.js
import js from '@eslint/js';
export default [
js.configs.recommended,
{
rules: {
'no-unused-vars': 'warn',
'no-console': 'warn',
'eqeqeq': 'error',
},
},
];
Flat config replaces inheritance chains with arrays, making it more intuitive and easier to understand where each rule comes from.
Running
# Check all files
npx eslint .
# Check and auto-fix
npx eslint . --fix
# Check specific files only
npx eslint src/**/*.js
With npm scripts
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
Common Rules Quick Reference
Error Detection
rules: {
'no-unused-vars': 'error', // Declared but unused variables
'no-undef': 'error', // Using undefined variables
'no-unreachable': 'error', // Unreachable code after return
'no-cond-assign': 'error', // Assignment in conditional statements
'no-debugger': 'error', // debugger statements
}
Style Conventions
rules: {
'indent': ['error', 2], // Uniform 2-space indentation
'quotes': ['error', 'single'], // Uniform single quotes
'semi': ['error', 'always'], // Enforce semicolons
'no-trailing-spaces': 'error', // No trailing spaces
'eol-last': 'error', // Newline at end of file
}
Best Practices
rules: {
'eqeqeq': 'error', // Must use === not ==
'no-eval': 'error', // Ban eval
'no-var': 'error', // Ban var
'prefer-const': 'error', // Prefer const
'no-multiple-empty-lines': ['error', { max: 2 }],
}
Suggestion: Leave style rules to Prettier, and let ESLint focus on code quality and best practices. Each tool does its own job. More on this later.
Relationship with Prettier
Many people confuse ESLint and Prettier. Simply put:
| ESLint | Prettier | |
|---|---|---|
| Focus | Code quality + code style | Pure code formatting |
| Typical Issues | Unused variables, == vs ===, unreachable code |
Indentation, quotes, line breaks, line width |
| Can fix logic? | Can find issues, some auto-fixable | No, only changes formatting, not logic |
| Configurable rules? | Yes, hundreds | Almost none, only a few options |
Best Practice: Use both together.
- ESLint handles "is the code correct?"
- Prettier handles "does the code look good?"
- Use
eslint-config-prettierto turn off ESLint formatting rules that conflict with Prettier
npm install -D prettier eslint-config-prettier
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'prettier', // Place last, turns off conflicting format rules
],
};
How to Configure for TypeScript Projects?
TypeScript projects need an additional parser and plugin:
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
// eslint.config.js
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
},
},
);
@typescript-eslint can check type-level errors, such as:
- Explicit use of
anytype (no-explicit-any) - Abuse of non-null assertion
!(no-non-null-assertion) - Unsafe type assertions (
no-unsafe-assertion)
Automate with Git Hooks
The ideal state: code is automatically checked before committing, and substandard code never enters the repository.
Using lint-staged + husky
npm install -D husky lint-staged
npx husky init
// package.json
{
"scripts": {
"prepare": "husky"
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": "eslint --fix"
}
}
echo "npx lint-staged" > .husky/pre-commit
Now, on every git commit, ESLint automatically checks the staged files and blocks the commit if there are issues. Only new/modified files are checked, not a full scan, so it's fast.
Common Configuration Presets
Rather than configuring from scratch, stand on the shoulders of giants. The community provides many mature config presets:
| Preset | Characteristics |
|---|---|
eslint:recommended |
ESLint official recommendation, covers the most common error detection |
eslint-config-airbnb |
Airbnb's strict style guide, once the community standard |
eslint-config-standard |
Standard style, no semicolons, no redundant config |
eslint-config-next |
Next.js official config, includes React + a11y rules |
eslint-config-react-app |
CRA built-in config |
eslint-config-prettier |
Turns off rules conflicting with Prettier |
// Combined usage
export default [
js.configs.recommended,
...reactHooks.configs.recommended,
...reactRefresh.configs.vite,
prettierConfig, // Place last
];
Summary
The core problem ESLint solves: Expose errors early, keep style consistent, and make best practices a habit.
| Without ESLint | With ESLint |
|---|---|
| Bugs exposed only at runtime | Found while coding |
| Code Review debates semicolons | Code Review focuses on logic |
| Everyone's code style differs | Team code looks like one person wrote it |
| Newcomers don't know best practices | Rules themselves are documentation and teaching |
If you haven't used ESLint in your project yet, install it now:
npm install -D eslint
npx eslint --init
Your codebase will thank you.