Tags: web-dev concept

Linting

Date: 2026-08-17


Static analysis that flags patterns you’ve decided to avoid. Its real value isn’t catching bugs — it’s encoding team decisions so they stop being argued in review, which means every rule should trace back to a decision somebody actually made.


A linter parses source into an abstract syntax tree and applies rules to it, reporting violations. ESLint is the standard for JavaScript and TypeScript.

What it can and can’t see

CAN                          CAN'T
unused variables             is the logic correct?
undefined variables          is this the right approach?
`==` instead of `===`        will this be slow?
missing await                is the naming good?
accessibility issues in JSX  does it meet the requirement?
unreachable code
banned imports

Linting is pattern matching, not understanding. It catches classes of mistake reliably and says nothing about whether the code does the right thing — which is what review is for — Code Review.

Rules are team decisions

The framing that makes a config coherent:

"no-console": "error"
  ← we decided not to ship console
    logging

"no-restricted-imports": [{
  "paths": [{
    "name": "lodash",
    "message": "Use lodash-es for
                tree-shaking"
  }]
}]
  ← we decided this, and the linter
    now explains it at the point of
    the mistake

A rule with a custom message is documentation delivered at exactly the right moment — better than a wiki page nobody reads, because it appears in the editor while the person is making the decision.

The corollary: every enabled rule should trace to a decision. A config copied from a template contains rules nobody agreed to, which is how linting becomes something people disable.

Linting versus formatting

The distinction that removes most of the friction:

LintingFormatting
QuestionIs this a problem?How should this look?
ExamplesUnused vars, missing awaitIndentation, quotes, semicolons
DebatableSometimesNever — automate it

Turn off every stylistic rule in the linter and let a formatter own them. Historically ESLint did both, and the overlap produced conflicting rules and slow lint runs — Formatting.

The rules worth having

Beyond a recommended baseline:

  • eslint-plugin-jsx-a11y — catches a genuine subset of accessibility issues at authoring time — WCAG
  • no-floating-promises (TypeScript) — an unawaited promise that swallows its own failure — Async Models
  • no-restricted-imports — architectural boundaries, enforced
  • exhaustive-deps for React hooks — catches a real and common bug class
  • Custom rules for your own conventions. Writing one is more approachable than it sounds, and it’s the difference between a convention that holds and one that erodes

Where it goes wrong

  • Too many rules. Hundreds of warnings nobody reads. Warnings that are never fixed should be errors or deleted — a persistent warning count trains people to ignore output
  • Rules nobody agreed to, inherited from a config
  • Linting formatting, producing conflicts and noise
  • // eslint-disable without a reason. Require a comment explaining why, or the disables accumulate silently
  • Slow enough to skip. If linting the repository takes two minutes locally, people stop running it
  • Only in CI. Feedback arrives after the push, which is the wrong moment — Pre-Commit Hooks

Where it should run

EDITOR       instantly, as you type
             ← by far the most valuable

PRE-COMMIT   on changed files only, fast
             — Pre-Commit Hooks

CI           the whole repository,
             authoritative
             — Continuous Integration

See: Pre-Commit Hooks · Continuous Integration

All three, with the same config. A rule that fails in CI but not in the editor is a rule people discover at the worst moment.

Adopting it on an existing codebase

Turning on a strict config over a large repository produces thousands of errors, and the usual outcome is that nobody fixes them.

# capture existing violations as accepted
eslint --format json > .eslint-baseline.json

Ratchet rather than boil the ocean: fail only on new violations, fix the backlog opportunistically, and tighten as the count falls. A rule enforced on new code is worth far more than a rule everyone has learned to ignore.