Linter

On this page 7

A linter is a static analysis tool that examines source code without executing it and reports likely bugs, suspicious constructs, security risks and violations of agreed coding conventions. Among development tools, the linter is the fastest automated check a programmer gets: it runs in the editor while code is being typed, in pre-commit hooks and in continuous integration, long before tests or code review.

The name comes from lint, a Unix program written at Bell Labs in the late 1970s that flagged questionable C code the compiler accepted. Today almost every language has one or more linters, and each project typically configures a rule set that reflects its own standards.

How a linter analyzes code

A linter works by parsing source code into a syntax tree and then running a set of rules over that tree. Most rules are pattern checks: a rule visits specific node types, such as comparisons or function declarations, and reports a problem when the node matches a known risky pattern, for example comparing with == where strict equality is expected.

More advanced linters also build scope and data-flow information. With it they can detect an unused variable, a variable used before assignment, unreachable code, or a value that may be null when dereferenced. Linters with access to type information, such as typed rules for TypeScript, can catch misuse of promises or incorrect API calls that purely syntactic rules miss.

Each finding carries a rule identifier, a location and a severity. Many rules also provide an automatic fix, so the linter can rewrite simple problems itself, such as removing unused imports or adding a missing semicolon.

Rule categoryExample findingWhy it matters
Possible errorsVariable used before assignmentRuntime failure or wrong result
Suspicious patternsAssignment inside a conditionUsually a typo for comparison
Unused codeUnused variable or importDead code hides real intent
SecurityUse of eval or string-built SQLInjection vulnerabilities
ComplexityFunction with very high cyclomatic complexityHard to test and maintain
Style and conventionsNaming that breaks project rulesInconsistent, harder-to-read code
Kinds of problems a linter detects

Linters for common languages

Linters are usually language-specific because they depend on the language’s grammar and semantics. Many ecosystems have one dominant tool, and several now offer extremely fast implementations written in compiled languages.

LanguageCommon lintersNotable characteristics
JavaScript and TypeScriptESLint, BiomePlugin-based rules, type-aware checks
PythonRuff, Pylint, Flake8Ruff combines many rule sets with high speed
Gogo vet, staticcheck, golangci-lintAggregators run many analyzers together
RustClippyHundreds of lints integrated with the compiler
JavaCheckstyle, PMD, Error ProneStyle, bug patterns and compile-time checks
Shell scriptsShellCheckQuoting, portability and word-splitting errors
CSSStylelintInvalid properties and convention checks
Widely used linters by language

Rule sets are stored in a configuration file in the repository, so every developer and every CI run applies the same rules. Projects typically start from a shared preset and then enable, disable or adjust individual rules.

Linter compared with formatter, compiler and type checker

A linter overlaps with several other development tools, and modern pipelines use them together. The compiler rejects code that is invalid in the language, while a linter flags code that is valid but probably wrong or unwise. A formatter such as Prettier or Black only rewrites layout, whereas a linter reasons about meaning.

ToolMain question answeredChanges codeExample
LinterIs this code risky or against conventionsOptional automatic fixesESLint, Ruff
FormatterIs the layout consistentYes, alwaysPrettier, Black, gofmt
Type checkerAre types used correctlyNoTypeScript compiler, mypy
CompilerIs this valid codeNo, produces outputjavac, rustc
Deep static analyzerAre there complex defects across filesNoTaint and data-flow scanners
A linter among related code-quality tools

A practical rule is to let the formatter own layout and the linter own correctness. Running both with overlapping style rules produces conflicting fixes, which is why many linters now ship configurations that disable purely stylistic rules.

Integrating a linter into the workflow

A linter is most effective when it runs at several points with the same configuration. Editor integration, such as extensions in Visual Studio Code or other IDEs, shows findings as the code is typed. A pre-commit hook blocks commits that introduce new violations, and the CI pipeline fails a pull request when the rules are broken.

  • Treat errors and warnings differently: errors fail the build, warnings mark areas to improve.
  • Lint only changed files in pre-commit hooks to keep feedback within a few seconds.
  • Pin the linter version so an upgrade with new rules does not unexpectedly break builds.
  • Require a reason for inline suppressions so disabled rules can be reviewed later.

Automating style and simple bug checks also improves code review, because reviewers no longer spend comments on spacing or unused variables and can focus on design and logic.

Limitations and common pitfalls

A linter can only check what its rules describe, and it never proves that code is correct. False positives are the main practical problem: when a linter reports many findings that developers consider irrelevant, they start ignoring all of them, including the important ones.

PitfallEffectRemedy
Enabling every rule at onceThousands of findings, team ignores outputStart with a recommended preset
Linting legacy code without a baselineOld violations block all workRecord a baseline, enforce on new code
Blanket suppressionsReal problems hiddenSuppress per line with a reason
Conflicting formatter rulesFixes undo each otherDisable stylistic lint rules
Local and CI versions differPasses locally, fails in CIPin versions in the lockfile
Linter pitfalls and remedies

Stripped to the essentials

A linter is like a spelling and grammar checker for computer code. It reads what a programmer has written and underlines anything that looks like a mistake. It also flags anything that breaks the team’s agreed writing rules. It does this without running the program.

Some warnings point to real bugs, like a value that is never used or a typo that changes the meaning. Others are about keeping the code tidy and consistent. Catching these problems early is cheaper than finding them after the software is in use.

Where it is applied

A team of eight developers maintains a TypeScript web application of about 150,000 lines. Production incidents keep tracing back to the same causes: promises that are created but never awaited, and leftover debugging code that ships to users. Code reviews take long because comments focus on formatting.

The team adopts ESLint with type-aware rules that flag floating promises, adds Prettier for layout, and records a baseline of existing violations so only new code must comply. The linter runs in the editor, in a pre-commit hook on changed files and in the CI pipeline. Within a few months, incidents caused by unawaited promises stop recurring, and review discussions shift from spacing to design decisions.