Linters and Formatters Explained: ESLint, Prettier, Biome, and Ruff
A formatter makes code look consistent; a linter catches likely bugs. What each does, the common tools for JavaScript and Python, how to run them automatically on save and before every commit, and why they matter even more when an AI is writing the code.
Two kinds of tool quietly improve almost every codebase: formatters and linters. They're easy to set up, run automatically, and matter even more when an AI tool is writing a lot of your code.
Formatters: how code looks
A formatter rewrites code so it looks consistent — indentation, quotes, line breaks, spacing — according to fixed rules.
Before:
const user={name:'Ana',
age:34 , plan : "pro"}
After formatting:
const user = { name: "Ana", age: 34, plan: "pro" }
Formatters don't change what the code does, only how it looks. Why bother?
- Readability. Consistent code is easier to scan.
- No arguments. Tabs vs spaces is settled by the tool, not people.
- Cleaner diffs. Without a formatter, changes get buried among whitespace differences. With one, a diff shows only real changes. (How to Read a Diff.)
Common formatters: Prettier (JavaScript, TypeScript, CSS, HTML, Markdown, JSON), Biome (JavaScript/TypeScript, fast, also lints), Ruff and Black (Python), gofmt (Go, built in).
Linters: catching likely bugs
A linter reads your code and flags patterns that are probably mistakes or bad practice:
const total = calculateTotal(cart) // ⚠ 'total' is assigned but never used
if (user.role = "admin") { … } // ⚠ assignment inside a condition — did you mean ===?
useEffect(() => {
fetchPlants(userId)
}, []) // ⚠ missing dependency: 'userId'
That last one is a React rule that catches a real class of bugs AI tools commonly produce. (What Is React?.)
Common linters: ESLint (JavaScript/TypeScript, the most widely used, with plugins for React, Next.js, and more), Biome, Ruff (Python, extremely fast, also formats).
Many linter rules can auto-fix — eslint --fix corrects the simple ones automatically.
Type checkers
Related, and worth mentioning: TypeScript's type checker (tsc) catches a different class of mistakes — passing the wrong kind of data, misspelled properties, missing null checks. Linters, formatters, and a type checker together catch a large share of simple errors before you ever run the code. (TypeScript for AI-Generated Code.)
Setting them up
Most project generators (like create-next-app) include ESLint already. To add Prettier to a JavaScript project:
npm install --save-dev prettier
Then add scripts to package.json:
{
"scripts": {
"lint": "eslint .",
"format": "prettier --write ."
}
}
Now npm run lint checks everything and npm run format formats everything. (npm and package.json Explained.)
Or ask your AI tool: "Set up ESLint and Prettier for this project with sensible defaults, add lint and format scripts, and fix any existing issues in a separate commit."
Tip: do the first big format of an existing project in its own commit, with nothing else in it. It touches every file; mixing it with real changes makes them impossible to review.
Run them automatically
Tools you have to remember to run don't get run. Automate them at three points:
1. In your editor, on save
In VS Code, install the ESLint and Prettier extensions and turn on Format On Save. Code gets formatted as you work, and lint warnings appear as squiggly underlines. (VS Code for Beginners.)
2. Before every commit
A pre-commit hook runs checks automatically when you commit, and stops the commit if they fail. Tools like Husky with lint-staged check only the files you're committing, so it stays fast.
3. In CI
Run npm run lint and the type checker on every push and pull request, so nothing slips through. (GitHub Actions CI Basics.)
Why this matters more with AI
AI tools generate a lot of code quickly, in whatever style their training suggests. Linters and formatters:
- Keep AI output consistent with the rest of the project, automatically.
- Catch its common mistakes — unused variables, missing React dependencies, unhandled promises — immediately.
- Give the AI fast feedback. Tell Claude Code to run the linter and type checker after changes, and it will fix what they flag before handing the work back.
A line in your CLAUDE.md does it: "After making changes, run npm run lint and npx tsc --noEmit and fix all errors." (How to Write a CLAUDE.md.) You can go further and run them automatically after every edit with a hook. (Claude Code Hooks.)
Don't overdo it
Start with the recommended rule sets. Hundreds of strict custom rules create noise people learn to ignore — or turn off. Add rules when a real problem keeps recurring.
EasySpawn gives Claude Code a full workspace where it can run your linter, formatter, and type checker after every change — the fastest feedback loop you can give an AI. See how it works or join the waitlist.
Related: What Is CI/CD? · Technical Debt in AI-Built Apps · Review AI-Generated Pull Requests
Keep reading
How to Write Good Commit Messages (and Why It Matters With AI)
'fix', 'update', and 'wip' tell you nothing six months later. What a commit message is for, the simple format most teams use, examples of good and bad messages, how often to commit when an AI tool is making the changes, and how to get Claude Code to write useful ones.
How to Write a README for Your Project
The README is the front page of your project — for collaborators, clients, future you, and AI tools that read it for context. What to include, a template you can copy, how it differs from a CLAUDE.md, and the mistakes that make READMEs useless.