Command Palette

Search for a command to run...

Hectal
PHASE 12Intermediate ~24 min· topic 5 of 6

Topic 12.5

Linting and Formatting

In one line

A linter such as ESLint reads your code without running it and reports likely bugs and banned patterns; a formatter such as Prettier rewrites the layout (spaces, quotes, line breaks) into one consistent style; your editor and a pre-commit check run both automatically.

Think of it like this

A teacher and a typist. Before you hand in an essay, a teacher reads it and circles real problems: "this sentence contradicts the last one", "you used a word that doesn't exist". That's the linter. A typist then retypes the whole essay neatly with the same margins, font and spacing as everyone else's, without changing a single word's meaning. That's the formatter. Teacher for mistakes, typist for looks.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Static analysis
Checking code by reading it, without running it.
Linter
A tool that reads code and reports likely bugs and banned patterns, using a list of rules.
Rule
One check a linter applies, such as "no unused variables", with a severity of off, warn or error.
Formatter
A tool that rewrites the layout of code (spaces, quotes, line breaks) into one consistent style.
Auto-fix
When the tool rewrites the code itself to solve a problem it found.
Flat config
ESLint's current settings format: one eslint.config.js file exporting an array of settings objects.
Globals
Names that exist without being declared or imported, like document in a browser or process in Node.js.
Git hook
A script Git runs automatically at a moment such as just before a commit.

Step by step

01Install ESLint and Prettier

Both are dev tools, so they go in devDependencies (Topic 12.3). globals is a small package listing browser and Node global names. npm init @eslint/config@latest can also ask you questions and write the config for you.

terminal
$ npm install -D eslint @eslint/js globals prettier eslint-config-prettier
── expected output ──
added 98 packages, and audited 99 packages in 6s
 
24 packages are looking for funding
run `npm fund` for details
 
found 0 vulnerabilities

02Write the ESLint config

Start from the recommended rules, declare browser globals, add a couple of team rules, and put eslint-config-prettier last so it can switch off anything that overlaps with formatting.

eslint.config.jswhole filejavascript
import js from "@eslint/js";
import globals from "globals";
import prettier from "eslint-config-prettier";

export default [
  { ignores: ["dist/"] },
  js.configs.recommended,
  {
    languageOptions: { globals: globals.browser },
    rules: {
      eqeqeq: "error",
      "no-var": "error",
      "no-console": "warn",
    },
  },
  prettier,
];

03Lint some code

Here is a file with four problems. Run ESLint on the project.

src/cart.jswhole filejavascript
export function total(items) {
  var sum = 0;
  for (const item of items) {
    if (item.qty == "0") continue;
    sum += item.price * item.qty;
    debugger;
  }
  const discount = 0.1;
  return sum;
}

04Read the report

Each line gives line:column, the severity, the message and the rule name. Look a rule up on eslint.org to learn why it exists. One problem is auto-fixable.

terminal
$ npx eslint .
── expected output ──
/home/asha/my-shop/src/cart.js
2:3 error Unexpected var, use let or const instead no-var
4:18 error Expected '===' and instead saw '==' eqeqeq
6:5 error Unexpected 'debugger' statement no-debugger
8:9 error 'discount' is assigned a value but never used no-unused-vars
 
✖ 4 problems (4 errors, 0 warnings)
1 error and 0 warnings potentially fixable with the `--fix` option.

05Format with Prettier

--write rewrites files in place; --check only reports, which is what CI uses. A .prettierrc file holds the few options you change, and .prettierignore lists folders to skip (it already skips node_modules).

.prettierrcwhole filejson
{
  "printWidth": 100,
  "singleQuote": false,
  "trailingComma": "all"
}
terminal
$ npx prettier . --write
── expected output ──
eslint.config.js 21ms (unchanged)
index.html 35ms
src/cart.js 9ms
src/style.css 6ms (unchanged)

06Automate it: editor, commit, CI

Add scripts so everyone runs the same commands, editor settings so it happens on save, and a CI step so nothing slips through.

.vscode/settings.jsonwhole filejson
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  }
}
Automate it: editor, commit, CIdiagram
Rendering diagram…

Try it yourself

  1. 1

    Find the fourth rule

    In the second example, add a rule no-console that reports any line containing console.. Add a line console.log(sum); to source and predict the new report line, column included.

  2. 2

    Fool the tiny linter

    Add the line const note = "never use var here"; to source. Predict what the tiny linter reports. Why would real ESLint stay quiet?

  3. 3

    Change the print width

    In the third example, find the smallest printWidth that keeps the object on one line. Count first: how long is the one-line version?

Code & diagrams

Bugs that run without an error (and a linter would catch) New tab

No error appears, and three of these are real bugs. That's the point of a linter: it finds problems that don't crash, by reading the code. ESLint's recommended set catches the last three; `eqeqeq` is one line to add.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

0 == '' is true but 0 === '' is false
theme: light
Everyone is admin now! role = admin
total: 5
A tiny linter: rules, positions and a report New tab

Positions are `line:column`, both counted from 1, like ESLint's report. Real ESLint doesn't search text: it parses the code into a tree first, so `"var "` inside a string or a comment never triggers `no-var`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

2:3  error  Unexpected var, use let or const instead  no-var
4:18  error  Expected '===' and instead saw '=='  eqeqeq
6:5  error  Unexpected 'debugger' statement  no-debugger
3 problems
A tiny formatter: same code, one layout New tab

Mixed quotes and random spaces disappear because the formatter prints from the structure, not the text. If the line fits within `printWidth` it stays on one line; otherwise each property gets its own line with a trailing comma, like Prettier's default output.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

const user = { name: "Asha", city: "Pune", plan: "pro", seats: 3 };
const user = {
  name: "Asha",
  city: "Pune",
  plan: "pro",
  seats: 3,
};

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget to declare browser globals

In eslint.config.js, remove the languageOptions: { globals: globals.browser } line and run npx eslint . on code that uses document.

terminal
$ npx eslint .
── what you'll see ──
/home/asha/my-shop/src/main.js
1:15 error 'document' is not defined no-undef
 
✖ 1 problem (1 error, 0 warnings)

Break #2

Commit without formatting

Turn off format on save, write code with mixed quotes and odd spacing, commit, and let CI run npx prettier . --check.

terminal
$ npx prettier . --check
── what you'll see ──
Checking formatting...
[warn] src/cart.js
[warn] Code style issues found in the above file. Run Prettier with --write to fix.

Myth vs fact

Myth

Linters and formatters do the same job.

Fact

A linter finds code-quality problems and possible bugs; a formatter only changes layout. Use both, and let each own its area (eslint-config-prettier keeps them from fighting).

Myth

If the code runs, the linter's complaints don't matter.

Fact

Many lint errors are bugs that don't crash: duplicate keys, assignments in conditions, unused variables you meant to use. They show up later as wrong results.

Myth

Prettier's style is just someone's opinion forced on everyone.

Fact

It is an opinion, and that's the benefit: one automatic style means no time spent arguing or reviewing spacing, and diffs only show real changes.

Myth

Disabling a rule with a comment is cheating.

Fact

Sometimes a rule is wrong for one line. Disable it for that line only, name the rule, and add the reason; turning rules off for whole files hides real problems.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    ESLint parses code with Espree into an ESTree-shaped AST; each rule registers visitors such as BinaryExpression(node) and reports with a location. Writing a custom rule for your codebase ("don't call fetch directly, use api()") is a short file. Type-aware linting (typescript-eslint with type information) catches things like a forgotten await on a promise (no-floating-promises), at the cost of slower runs.

  • ▸

    Prettier's core algorithm comes from Philip Wadler's "A prettier printer" paper: it builds a document of groups and tries to fit each group on one line within printWidth, breaking the outermost group first when it doesn't fit.

  • ▸

    Speed matters at scale: lint only changed files in pre-commit hooks (lint-staged), cache results (eslint --cache), and consider Rust-based tools such as Oxlint or Biome, which are many times faster and implement most popular ESLint rules.

  • ▸

    Rules are documentation of team decisions. Turning on no-restricted-imports or no-restricted-syntax can encode architecture rules ("UI code may not import from server/") so they are checked on every commit instead of remembered.

Remember this

  1. 1

    Static analysis means checking code by reading it, without running it. A linter is a static-analysis tool that applies a set of rules. ESLint is the standard linter for JavaScript. Its rules catch real bugs: no-undef (a misspelled variable such as docment), no-unused-vars (a variable you meant to use but didn't), no-dupe-keys (the same key twice in an object, where the last one silently wins), no-cond-assign (if (x = 5) instead of ===), no-unreachable (code after return). Others enforce team choices: eqeqeq (always ===), no-var (use let/const).

  2. 2

    Each rule has a severity: "off", "warn" (reported, but doesn't fail the run) or "error" (the command exits with a failure code, which stops a CI build). Many rules are auto-fixable: eslint --fix rewrites the code for you (for example var to let). Rules can be turned off for one line with a comment, // eslint-disable-next-line no-console, which you should use rarely and explain.

  3. 3

    ESLint 9 uses flat config: one eslint.config.js file that exports an array of config objects. You usually start with js.configs.recommended (a curated rule set), tell ESLint which globals exist (browser ones such as window and document, or Node ones such as process) through languageOptions.globals, and add your own rules. Plugins add rules for frameworks and languages: typescript-eslint for TypeScript, eslint-plugin-react-hooks for React. The older .eslintrc format is no longer supported in ESLint 10.

  4. 4

    A formatter only changes layout: indentation, line length, quote style, semicolons, trailing commas, spacing. Prettier is the most used one. It doesn't edit your text: it parses the code into a tree (the AST from Topic 0.1) and prints it again from scratch in its own style, so the way you typed it no longer matters. Prettier has very few options on purpose (printWidth, default 80; singleQuote; semi; tabWidth; trailingComma), which ends style debates in code review. Prettier also formats HTML, CSS, JSON and Markdown. Biome is a newer, faster tool that does both linting and formatting.

  5. 5

    Keep the two jobs separate. Since ESLint 8.53, ESLint's own formatting rules are deprecated, and if you use a shared config that still has them, add **eslint-config-prettier, which turns off every ESLint rule that could fight Prettier. Then ESLint reports problems and Prettier owns the look. Run them in three places: in the editor (format on save, lint errors underlined as you type), before each commit (a Git hook with tools such as husky and lint-staged checks only the changed files), and in CI** (eslint . and prettier --check . fail the build if anyone skipped the first two).

  6. 6

    Make the editor setup part of the project, so everyone gets it automatically. In VS Code, install the ESLint and Prettier extensions and commit a .vscode/settings.json with "editor.formatOnSave": true and "editor.defaultFormatter": "esbenp.prettier-vscode". An **.editorconfig** file sets basics (indent size, line endings, final newline) in almost every editor. The aim: nobody thinks about formatting, and lint problems are fixed while the code is still fresh in your head.

Explain it without notes

01

What is the difference between a linter and a formatter? Name the usual tool for each.

02

Give three bugs ESLint catches that don't cause an error when the code runs.

03

How do you stop ESLint and Prettier from disagreeing?

04

Where should linting and formatting run in a team, and why in more than one place?

Practice

01

Write an eslint.config.js for a browser project that uses the recommended rules, makes eqeqeq an error and no-console a warning, and ignores the dist folder.

02

Write a function findLooseEquals(lines) that returns the 1-based line numbers containing == or != but not === or !==. Test it on ["a === b", "a == b", "a != b"].

Trade-offs

  • ↔

    Strict rule sets catch more problems but can slow people down with noise; start from recommended, add rules that prevent bugs you've actually had, and prefer error over many ignored warns.

  • ↔

    Pre-commit hooks give fast feedback but can be skipped (--no-verify) and slow down commits if they lint the whole project; CI checks can't be skipped but report later.

Done when you can

  • I can explain the difference between a linter and a formatter.

  • I can install ESLint and Prettier and write a flat eslint.config.js with globals and rules.

  • I can read an ESLint report: position, severity, message, rule name.

  • I can set up format on save and keep ESLint and Prettier from conflicting.

  • I know why teams run checks in the editor, before commits and in CI.