A linter is a program that reads source code without running it and reports problems: likely bugs, non-portable or error-prone constructs, violations of a project’s style rules, and “code smells” that hint at deeper design issues. The defining trait is that a linter checks things a compiler will happily let through. The category and the verb “to lint” both trace to one tool, Bell Labs lint, whose 1978 report described a command that “examines C source programs, detecting a number of bugs and obscurities” and enforced “the type rules of C more strictly than the C compilers.”
That original split is the heart of the concept. A compiler is optimized to produce a working executable as fast as possible and will accept any program that is technically legal, even if it is almost certainly wrong. A linter is free to be slower and pickier, taking what Johnson’s report called “a more global, leisurely view of the program.” Modern linters formalize this as a set of rules run over the code’s abstract syntax tree. ESLint’s documentation describes itself as “a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code,” where each rule is an independent check that can be turned on, turned off, or set to warn or error.
Linters typically cover several overlapping concerns at once. Correctness rules catch things like unreachable code, unused variables, or comparisons that always evaluate the same way. Portability and safety rules flag constructs that work on one platform or in one mode but break elsewhere. Style rules enforce naming, spacing, and structural conventions so a codebase reads consistently. Many linters also support “smell” rules that warn about overly long functions, deep nesting, or duplicated logic, nudging code toward the qualities described in the clean code and code smell literature.
Because the rules are configurable, linting is also a way for a team to encode and automate its own standards. A shared configuration file (such as ESLint’s flat config or a .pylintrc) lets a project agree once on what counts as acceptable and then enforce it mechanically on every commit and in continuous integration. This turns subjective review comments about style and obvious mistakes into automated, repeatable checks, freeing human reviewers to focus on design and intent.
Today nearly every major language has at least one widely used linter: ESLint for JavaScript and TypeScript, Pylint and Ruff for Python, clang-tidy for C and C++, golangci-lint and go vet for Go, RuboCop for Ruby, and more. They differ in language and detail, but all inherit the same idea Johnson set out in 1978: a dedicated static checker, separate from the compiler, whose job is to catch the bugs, hazards, and untidiness that a build alone will not.