Code Quality: Linters & Formatters
Advanced · 17 min read · ▶ live playground · ✦ checkpoint
Code that runs is not always code you want to keep. Code quality tools are the small machines that catch suspicious patterns, rewrite boring spacing, and keep teams from spending review time on arguments a computer can settle.
Linters catch likely bugs
A linter is a tool that reads your code and reports patterns that are probably mistakes. ESLint is the JavaScript linter you will see most often. It does not run your app like a user. It scans the code and applies rules, meaning named checks with clear pass/fail behavior.
This file runs, but it has two problems a linter can catch before runtime:
const discountCode = "SUMMER10";
export function canFinishCheckout(totalCents) {
if (totalCents == "0") {
return false;
}
return true;
}The first problem is discountCode: the code creates a name and never uses it. The second is ==: this course has used === since the equality lesson because strict equality avoids surprise coercion.
In a project, you usually run ESLint through an npm script:
{
"scripts": {
"lint": "eslint ."
}
}Then the terminal gives you actionable output:
npm run lint
# src/checkout.js
# 1:7 error 'discountCode' is assigned a value but never used no-unused-vars
# 4:18 error Expected '===' and instead saw '==' eqeqeqThe rule names matter. no-unused-vars points at code that may be dead, misspelled, or half-refactored. eqeqeq enforces strict equality, so the == coercion story from Section 4 does not quietly return in a bigger app.
ESLint is not magic. A linter cannot prove your app is correct. It catches classes of mistakes cheaply and consistently, which is exactly why teams use it.
Formatters end spacing debates
A formatter is a tool that parses your code and prints it back in one consistent style. Prettier is the common formatter in many JavaScript projects.
Before formatting, code can be legal but noisy:
const receipt={store:"Book Loft",items:["Notebook","Pen"],totalCents:3299}
console.log( receipt.store,receipt.items.length,receipt.totalCents );After formatting, the same code is easier to scan:
const receipt = {
store: "Book Loft",
items: ["Notebook", "Pen"],
totalCents: 3299,
};
console.log(receipt.store, receipt.items.length, receipt.totalCents);The important shift is social as much as technical. A team does not need to debate spaces, wrapping, trailing commas, or quote choices in every review. The formatter owns those decisions, and people spend their attention on behavior.
The scripts usually look like this:
{
"scripts": {
"format": "prettier . --write",
"format:check": "prettier . --check"
}
}format changes files. format:check only reports whether files are already formatted, which makes it useful in automation.
Biome is the combined-tool option
ESLint plus Prettier is the incumbent setup: one tool for code-quality rules, one for formatting. Biome is a newer toolchain that can format and lint with one command family.
A Biome-shaped package script can look like this:
{
"scripts": {
"check": "biome check .",
"check:write": "biome check --write ."
}
}That does not make ESLint plus Prettier wrong. It gives you two normal choices:
- Use ESLint plus Prettier when a project already has them, or when you need the wider ESLint plugin ecosystem.
- Use Biome when you want one fast tool for common linting and formatting jobs.
Do not stack tools blindly. Two formatters fighting over the same file is how a save becomes noisy. Pick the project standard, put it in scripts, and let the editor and terminal run the same commands.
Format on save
Format-on-save is an editor setting that runs your formatter each time you save a file. It keeps code tidy before you even think about it.
In VS Code, a project may commit workspace settings like these:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}Your exact editor settings can vary. The idea stays the same: fast local feedback first. You save, the formatter handles layout, and the linter can fix safe issues when you ask it to.
This pairs well with the npm scripts. The editor gives quick feedback while you work. The terminal scripts give a shared source of truth for everyone.
Pre-commit hooks
A Git hook is a script Git, the version-control tool that records project history, runs at a specific Git moment. A pre-commit hook runs before Git creates a commit. Teams use it to stop obviously messy commits before they leave your machine.
One common JavaScript setup uses a helper package to install hook scripts, then runs project checks:
# .husky/pre-commit
npm run lint
npm run format:checkThe hook is not a replacement for thinking. It is a last quick gate. If formatting or linting fails, the commit stops, you fix the file, and you commit again.
Keep hooks fast. Running a formatter check and a linter is reasonable. Running a ten-minute full deployment test before every commit teaches everyone to bypass the hook.
The project contract
By this point, your professional JavaScript project has a useful rhythm:
npm run dev # work locally
npm run lint # catch likely mistakes
npm run format # make code style boring
npm run build # produce deployable outputThose scripts are not decoration. They are the contract for how the project is worked on. A new teammate can install dependencies, run the same commands, and get the same style rules you do.
Tooling does not make you a better programmer by itself. It removes low-value friction so you can spend more attention on design, names, data flow, and edge cases. The next professional safety layer goes deeper than linting: TypeScript adds types so the editor can reason about the values moving through your code before you run it.
Checkpoint
Answer all three to mark this lesson complete