Skip to content
Innovialabs

What Is a Linter? ESLint, Ruff and Static Analysis Basics

Basics4 min

In short

A linter reads code without running it and flags likely bugs and risky patterns, a formatter only rewrites layout, and a type checker verifies types; set up all three with minimal configs and run them in the editor, before commit and in CI.

What a linter does

A linter is a tool that analyses source code without running it and reports problems: likely bugs, suspicious patterns and violations of agreed rules. This kind of analysis is called static analysis.

Typical things a linter catches:

  • Unused variables and imports — often a sign of a typo or unfinished refactor.
  • Undefined names — a misspelled variable that would fail only at runtime.
  • Unreachable code after return or throw.
  • Risky comparisons, such as == instead of === in JavaScript or == None instead of is None in Python.
  • Bug-prone patterns — mutable default arguments, a missing await, a shadowed variable.
  • Project conventions — import order, naming, banned APIs.

Linters find problems in seconds, before a reviewer or a user does.

Linter vs formatter vs type checker

ToolWhat it answersExamples
FormatterHow should the code look? Indents, quotes, line breaksPrettier, Black, ruff format, gofmt
LinterIs this code likely wrong or against our rules?ESLint, Ruff, Pylint, golangci-lint
Type checkerDo the types fit together?TypeScript (tsc), mypy, Pyright

They complement each other. A formatter ends arguments about style and changes only whitespace and layout. A linter looks at meaning. A type checker proves that, for example, you never pass a string where a number is expected. Let the formatter own style and turn off stylistic rules in the linter so the two do not fight.

Minimal ESLint config

Modern ESLint uses a "flat" config file, eslint.config.js:

import js from "@eslint/js";

export default [
  js.configs.recommended,
  {
    rules: {
      "no-unused-vars": "warn",
      eqeqeq: "error",
    },
  },
];

Run it with npx eslint ., and add --fix to apply safe automatic fixes. For TypeScript, add the typescript-eslint package and its recommended config; frameworks such as React and Next.js offer their own presets.

Minimal Ruff config

Ruff is a fast Python linter and formatter that covers rules from many older tools (Flake8, isort, pyupgrade and others). Config lives in pyproject.toml:

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "I", "B"]

Here E is pycodestyle errors, F is Pyflakes (undefined names, unused imports), I is import sorting and B is flake8-bugbear (bug-prone patterns). Commands:

  • ruff check . — lint.
  • ruff check . --fix — lint and apply safe fixes.
  • ruff format . — format, as a Black-compatible formatter.

Start with a small rule set and expand once the team is comfortable.

Where to run them

1. In the editor. Install the ESLint or Ruff extension for your editor. Problems appear as you type, and format-on-save keeps the layout consistent. This is where linting gives the fastest feedback.

2. Before commit. Tools like pre-commit (Python) or husky with lint-staged (JavaScript) run checks only on staged files, so bad code rarely reaches the repository.

3. In CI. The final gate that cannot be skipped locally. Run checks without auto-fix and fail the build on errors:

# GitHub Actions step
- run: npx eslint . --max-warnings=0
- run: ruff check . && ruff format --check .

ruff format --check and prettier --check fail if files are not formatted, without changing them.

Common mistakes

  • Enabling every rule at once. The team drowns in warnings and starts ignoring them. Begin with recommended presets.
  • Warnings that never fail anything. They pile up forever. Either make a rule an error or turn it off.
  • Mass disabling with comments. An occasional // eslint-disable-next-line or # noqa with a reason is fine; dozens of them mean the rule does not fit.
  • Linter and formatter conflicting. Disable stylistic lint rules when a formatter is in place.
  • Introducing a linter to a large codebase in one pull request. Fix the most important rules first, or apply new rules to changed files only, then expand.

FAQ

Yes. The type checker verifies types, while the linter catches things types do not cover: unused code, risky patterns, missing await, project conventions.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.