Настройка линтеров linter-config для современных проектов

05 января 2026
Автор

Олег Марков

Введение

Линтеры давно перестали быть «дополнительным инструментом» и стали обязательной частью современного процесса разработки. Они помогают выровнять стиль кода, находить потенциальные ошибки еще до запуска тестов и существенно сокращать время на ревью. Но сами по себе линтеры не работают: вам нужно настроить их поведение с помощью конфигурации — linter-config.

Под linter-config мы будем понимать набор конфигурационных файлов и правил для таких инструментов, как ESLint, Stylelint, Prettier, golangci-lint, Flake8 и другие. Смотрите, я покажу вам, как шаг за шагом выстроить понятную и переносимую конфигурацию, которую легко поддерживать в команде.

Мы пройдемся по общим принципам, разберем структуру типичных конфигов, посмотрим на примеры для разных языков и обсудим интеграцию линтеров в IDE и CI. В итоге у вас будет ясная картина, как проектировать и развивать собственный linter-config.

Зачем вообще нужен linter-config

Прежде чем настраивать файл конфигурации, важно понять, какие задачи он решает.

Единый стиль кода

Конфигурация линтера фиксирует:

  • форматирование и стиль (отступы, кавычки, точка с запятой);
  • соглашения об именовании;
  • структуру модулей и импортов.

Это позволяет:

  • сделать код предсказуемым для всех участников команды;
  • уменьшить количество субъективных замечаний в ревью;
  • ускорить чтение и сопровождение кода.

Ранний поиск ошибок

Линтеры обнаруживают:

  • потенциальные баги (неиспользуемые переменные, сравнение с присваиванием, обращение к несуществующим полям);
  • проблемные конструкции (глобальные переменные, использование any в TypeScript, небезопасный SQL);
  • нарушения архитектурных ограничений (запрет определенных импортов, зависимостей между модулями).

Настройка linter-config задает, какие именно проблемы считать критичными и как на них реагировать.

Автоматизация и воспроизводимость

Хорошо настроенный linter-config:

  • одинаково работает на всех машинах разработчиков;
  • выполняется автоматически в CI;
  • не зависит от личных настроек IDE.

Благодаря этому любые новые участники команды получают единые правила «из коробки».

Общие принципы настройки линтеров

Явная фиксация версий и зависимостей

Первый базовый принцип — конфигурация должна быть воспроизводимой. Для этого:

  • фиксируйте версии линтеров и плагинов в package.json, go.mod, requirements.txt и т.п.;
  • старайтесь не использовать «плавающие» версии вроде ^8.0.0, если у вас критичен стабильный результат;
  • храните linter-config в репозитории проекта.

Так вы избежите ситуации, когда у разных разработчиков линтер ведет себя по-разному.

Минимальный, но строгий набор правил

Давайте разберемся, почему перебор с правилами вреден:

  • избыточное количество правил приводит к шуму — важные предупреждения тонут в массе несущественных;
  • команда перестает доверять линтеру и просто отключает его;
  • растет количество исключений и специальных комментариев.

Подход, который хорошо работает на практике:

  1. Включить рекомендованный базовый набор правил.
  2. Добавлять правила по мере реальной необходимости.
  3. Для каждого включенного правила зафиксировать мотивацию в документации или в комментарии в конфиге.

Разделение ответственности между линтерами

Важно понимать, какую задачу решает каждый инструмент:

  • ESLint, Flake8, golangci-lint — анализ логики и структуры кода.
  • Prettier, gofmt, black — форматирование.
  • Stylelint — стили.
  • Дополнительные специализированные линтеры (например, eslint-plugin-security).

Обратите внимание: не стоит заставлять один инструмент делать все подряд. Обычно выгодно:

  • использовать специальный форматтер (Prettier, gofmt);
  • отключить конфликтующие правила форматирования в общем линтере;
  • оставить линтеру проверку логики и архитектуры.

Конфигурация как код

Храните linter-config рядом с кодом:

  • в корне проекта;
  • с явным указанием версий;
  • с комментариями и пояснениями.

Хороший принцип: к linter-config относиться как к обычному коду — ревьюить, документировать изменения, писать миграционные заметки при крупных обновлениях.

Структура типичного linter-config

Давайте посмотрим на общий подход к конфигурации линтеров на примере ESLint. Далее мы перенесем те же идеи на другие инструменты.

Базовый ESLint конфиг — пример

Здесь я размещаю пример простого конфига ESLint для проекта на JavaScript:

// .eslintrc.cjs

/** @type {import('eslint').Linter.Config} */
// Здесь мы объявляем объект конфигурации ESLint
module.exports = {
  // Задаем окружения - это влияет на набор глобальных переменных
  env: {
    browser: true,   // Доступны window, document и другие браузерные глобалы
    node: true,      // Доступны require, module, __dirname
    es2021: true,    // Включаются глобалы и синтаксис ES2021
  },

  // Определяем базу - от каких конфигов наследуемся
  extends: [
    "eslint:recommended",   // Базовые рекомендованные правила ESLint
  ],

  // Задаем парсер и его настройки
  parserOptions: {
    ecmaVersion: 12,        // Поддерживаемый стандарт ECMAScript
    sourceType: "module",   // Используем ES-модули (import/export)
  },

  // Подключаем плагины - дополнительные наборы правил
  plugins: [
    "import",               // Плагин для контроля импортов
  ],

  // В этом разделе вы настраиваете конкретные правила
  rules: {
    "no-unused-vars": "warn",        // Неиспользуемые переменные - предупреждение
    "no-console": "off",             // Разрешаем console.log
    "eqeqeq": ["error", "always"],   // Обязательное строгое сравнение ===
  },
};

Как видите, конфиг состоит из нескольких ключевых блоков:

  • env — окружения и набор глобальных переменных.
  • extends — от каких готовых конфигураций мы наследуемся.
  • parserOptions — опции синтаксического анализа.
  • plugins — дополнительные наборы правил.
  • rules — конкретные включенные или переопределенные правила.

На практике структура у разных линтеров похожа: есть базовые пресеты, плагины и точечные настройки.

Приоритет конфигураций

Большинство линтеров поддерживает наследование и мерджинг конфигов. Важно понимать, в каком порядке они применяются. Для ESLint последовательность обычно такая:

  1. Базовые (встроенные) правила.
  2. Пресеты из extends.
  3. Правила из локального файла конфигурации.
  4. Локальные переопределения для конкретных директорий или файлов (например, через overrides).

Ближе к коду — сильнее приоритет. Это удобно для исключений, тестов, миграций.

Настройка linters для JavaScript и TypeScript

Теперь давайте подробно разберем самый популярный сценарий — настройку ESLint и Prettier для JavaScript/TypeScript проекта.

Базовая связка ESLint + Prettier

Распространенная ошибка — пытаться использовать ESLint и Prettier как независимые инструменты, при этом оставляя в ESLint правила форматирования, которые конфликтуют с Prettier.

Хороший рабочий вариант:

  1. Делегировать форматирование кода Prettier.
  2. Отключить в ESLint те правила, которые дублирует Prettier.
  3. Настроить единый workflow: сначала форматирование, затем линтинг.

Вот пример настройки:

# Устанавливаем ESLint и Prettier для JS-проекта
npm install --save-dev eslint prettier eslint-config-prettier eslint-plugin-prettier

Теперь создадим конфиг ESLint:

// .eslintrc.cjs

module.exports = {
  env: {
    browser: true,
    node: true,
    es2021: true,
  },
  extends: [
    "eslint:recommended",        // Базовые правила
    "plugin:prettier/recommended" // Включаем интеграцию с Prettier
  ],
  plugins: ["prettier"],

  rules: {
    // Это правило заставляет ESLint ругаться на любые несоответствия формату Prettier
    "prettier/prettier": "error",

    // Пример дополнительных правил логики
    "no-var": "error",                // Запрещаем var
    "prefer-const": "warn",          // Рекомендуем const, если переменная не изменяется
  },
};

Отдельно создадим файл настроек Prettier:

// .prettierrc.json
{
  "//": "Здесь мы настраиваем только форматирование, без логики",
  "singleQuote": true,        // Используем одинарные кавычки
  "semi": true,               // Ставим точку с запятой
  "trailingComma": "all",     // Запятая в конце списков где это допустимо
  "printWidth": 100,          // Максимальная длина строки
  "tabWidth": 2               // Длина табуляции в пробелах
}

Комментарии в JSON-файлах официально не поддерживаются, так что вместо // вы можете использовать ключ вроде "//": "комментарий" как в примере.

Настройка ESLint для TypeScript

С TypeScript конфигурация чуть сложнее: нужен отдельный парсер и плагин. Давайте разберемся на примере.

# Устанавливаем ESLint и зависимости для TypeScript
npm install --save-dev \
  typescript \
  eslint \
  @typescript-eslint/parser \
  @typescript-eslint/eslint-plugin \
  prettier \
  eslint-config-prettier \
  eslint-plugin-prettier

Теперь создадим конфиг:

// .eslintrc.cjs

module.exports = {
  root: true, // Говорим ESLint что это корневой конфиг проекта

  env: {
    browser: true,
    node: true,
    es2021: true,
  },

  parser: "@typescript-eslint/parser", // Используем TS-парсер
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
    project: "./tsconfig.json", // Подключаем TS-проект для типовой информации
  },

  plugins: [
    "@typescript-eslint", // Правила для TS
    "prettier",
  ],

  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",        // Базовые TS-правила
    "plugin:prettier/recommended"                  // Интеграция с Prettier
  ],

  rules: {
    // Включаем проверку от Prettier
    "prettier/prettier": "error",

    // Переключаем стандартные JS-правила на TS-аналоги
    "no-unused-vars": "off",                       // Отключаем стандартное правило
    "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],

    // Пример более строгого правила для any
    "@typescript-eslint/no-explicit-any": "warn",

    // Можно смягчить некоторые правила по необходимости
    "@typescript-eslint/explicit-module-boundary-types": "off"
  },

  overrides: [
    {
      // Отдельная настройка для тестов
      files: ["**/*.test.ts", "**/*.spec.ts"],
      env: {
        jest: true,      // Глобалы Jest
      },
      rules: {
        "@typescript-eslint/no-non-null-assertion": "off", // Часто используется в тестах
      },
    },
  ],
};

Обратите внимание на ключи:

  • parser и parserOptions — включают поддержку TS синтаксиса и типов.
  • extends — тянут рекомендованный набор правил.
  • overrides — позволяют настраивать правила для отдельных паттернов файлов.

Теперь вы увидите, как это выглядит в коде: любой .ts файл будет проверен ESLint с учетом TS-правил, а форматирование будет контролироваться Prettier.

Общий конфиг для монорепозитория

Если у вас монорепозиторий с несколькими пакетами, удобно вынести базовый linter-config в корень и переиспользовать его.

Пример:

  • в корне репозитория:
// .eslintrc.base.cjs
module.exports = {
  env: { browser: true, node: true, es2021: true },
  parserOptions: { ecmaVersion: "latest", sourceType: "module" },
  rules: {
    "no-var": "error",
    "prefer-const": "warn",
  },
};
  • в отдельных пакетах:
// packages/app/.eslintrc.cjs

const baseConfig = require("../../.eslintrc.base.cjs");

module.exports = {
  ...baseConfig,   // Расширяем базовый конфиг
  rules: {
    ...baseConfig.rules,
    // Дополнительные правила для frontend-приложения
    "no-alert": "error",
  },
};

Так вы избегаете дублирования и развиваете единый linter-config для всего репозитория.

Настройка Stylelint и линтинга CSS

Для стилизации часто используют Stylelint. Логика работы похожа на ESLint: есть базовые пресеты, плагины и собственные правила.

Пример базового конфига Stylelint

npm install --save-dev stylelint stylelint-config-standard stylelint-config-prettier

Теперь создадим конфиг:

// .stylelintrc.json
{
  "extends": [
    "stylelint-config-standard", // Базовый набор правил
    "stylelint-config-prettier"  // Отключаем конфликтующие с Prettier правила
  ],
  "rules": {
    "color-hex-case": "lower",          // Цвета в нижнем регистре
    "color-hex-length": "short",        // Короткая форма hex если возможно
    "indentation": 2,                   // Два пробела
    "string-quotes": "single",          // Одинарные кавычки
    "block-no-empty": true              // Запрещаем пустые блоки
  }
}

Здесь я размещаю пример, чтобы вам было проще понять: мы опять используем extends и переопределяем правила под стиль проекта.

Настройка линтеров для Go

В Go есть встроенный форматтер gofmt и популярный агрегатор линтеров golangci-lint. Настроим golangci-lint: он поддерживает множество линтеров под одним конфигом.

Пример конфига golangci-lint

# .golangci.yml

run:
  timeout: 5m          # Максимальное время работы линтера
  tests: true          # Проверять ли тестовые файлы

linters:
  enable:
    - govet            # Анализ на уровне пакета - потенциальные баги
    - staticcheck      # Глубокий статический анализ
    - gosimple         # Упрощение кода
    - errcheck         # Проверка ошибок
    - gofmt            # Проверка форматирования gofmt
  disable:
    - megacheck        # Пример выключенного устаревшего линтера

linters-settings:
  errcheck:
    exclude-functions:
      - fmt.Fprintf    # Не требовать проверку ошибок для этих функций

issues:
  exclude-use-default: false
  max-issues-per-linter: 0  # Без ограничений на количество проблем
  max-same-issues: 0        # Без ограничений на повторяющиеся проблемы

Комментарии в YAML помогают помнить, почему какое-то правило было включено или отключено.

Здесь вы видите ту же идею: включаем нужные линтеры, настраиваем их параметры и контролируем поведение через единый linter-config.

Настройка линтеров для Python

Для Python часто используют Flake8 или связку black + isort + Flake8.

Пример комбинированной конфигурации

Обычно настройки хранят в pyproject.toml или .flake8. Давайте посмотрим пример в pyproject.toml:

# pyproject.toml

[tool.black]
line-length = 100          # Максимальная длина строки
target-version = ["py310"] # Целевая версия Python

[tool.isort]
profile = "black"          # Настраиваем isort под стиль black
line_length = 100

[tool.flake8]
max-line-length = 100
extend-ignore = [
    "E203",  # Конфликтует со стилем black
    "W503"   # Аналогично - переносы строк
]
exclude = [
    ".git",
    "__pycache__",
    "build",
    "dist"
]

Так вы разделяете ответственность:

  • black отвечает за форматирование;
  • isort — за порядок импортов;
  • flake8 — за логику и стиль.

При этом через linter-config вы явно задаете разрешенные исключения.

Гранулярная настройка правил

Теперь давайте посмотрим, как на практике тонко настраивать правила, чтобы конфигурация была и строгой, и удобной.

Уровни правил: off, warn, error

Почти все линтеры поддерживают три уровня:

  • off — правило отключено.
  • warn — предупреждение (обычно не ломает CI).
  • error — ошибка (обычно приводит к провалу CI и pre-commit).

Пример:

// Фрагмент .eslintrc.cjs

rules: {
  "no-debugger": "error",        // Запрещаем debugger в продакшене
  "no-console": "warn",          // Разрешаем но предупреждаем о console.log
  "complexity": ["warn", 10],    // Предупреждаем если функция слишком сложная
}

Практический подход:

  • багоподобные вещи (no-undef, no-unreachable, eqeqeq) — error;
  • спорные или стилистические (max-len, complexity) — warn;
  • правила, которые команда не готова соблюдать — off.

Подбор правил по этапам жизни проекта

На разных этапах проекта имеет смысл включать разные наборы правил:

  • В начале:
    • минимум строгих правил;
    • линтер больше выступает как советник.
  • При стабилизации:
    • усиливается жесткость (строгие правила для публичных API, типов);
    • появляются архитектурные ограничения (запрет циклических импортов).
  • При сопровождении:
    • часть правил можно переводить в error для защиты от регрессий.

Хороший прием: хранить в конфиге комментарии «почему» для каждого спорного правила.

rules: {
  // Ограничиваем сложность функций
  // Это упрощает ревью и снижает риск скрытых багов
  "complexity": ["warn", 15],
}

Исключения на уровне кода

Иногда нужно осознанно нарушить правило в конкретном месте. Для этого есть специальные комментарии.

Пример для ESLint:

// Здесь мы сознательно используем any - например в generic-обертке
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function wrapAny(value: any) {
  // ...
}

Важно не злоупотреблять исключениями и по возможности указывать причину.

Интеграция linter-config в рабочий процесс

Сам по себе конфиг мало что меняет, пока линтеры не встроены в ежедневный цикл разработки.

Скрипты в package.json

Для JS/TS проектов удобнее всего запускать линтеры через npm-скрипты:

// package.json (фрагмент)
{
  "scripts": {
    "lint": "eslint \"src/**/*.{js,ts,tsx}\"",        // Запуск ESLint
    "lint:fix": "eslint \"src/**/*.{js,ts,tsx}\" --fix", // Автоматическое исправление
    "format": "prettier --write \"src/**/*.{js,ts,tsx,json,md,css,scss}\"" // Форматирование
  }
}

Здесь кавычки вокруг шаблонов файлов важны для корректной работы в разных оболочках.

Pre-commit хуки

Чтобы линтеры запускались перед коммитом, часто используют инструмент lint-staged или Husky.

Пример:

npm install --save-dev husky lint-staged

Добавим в package.json:

// package.json (фрагмент)
{
  "lint-staged": {
    "src/**/*.{js,ts,tsx}": [
      "eslint --fix",      // Линтим и по возможности исправляем
      "prettier --write"   // Форматируем
    ],
    "src/**/*.{css,scss,md,json}": [
      "prettier --write"
    ]
  }
}

Инициализируем husky:

npx husky install
npx husky add .husky/pre-commit "npx lint-staged"

Теперь перед каждым коммитом измененные файлы будут автоматически проверяться и форматироваться.

Интеграция в CI

В CI важно запускать линтеры как отдельный шаг, который явно может упасть. Пример для GitHub Actions (JS/TS проект):

# .github/workflows/lint.yml

name: Lint

on:
  pull_request:
  push:
    branches: [ main ]

jobs:
  eslint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Use Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

  prettier:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Use Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Check formatting
        run: npx prettier --check "src/**/*.{js,ts,tsx,json,md,css,scss}"

Так вы отделяете проверку стиля и форматирования и явно видите, из-за чего падает пайплайн.

Интеграция с IDE

Практически все IDE умеют:

  • подхватывать linter-config из файла проекта;
  • автоматически запускать линтер при сохранении;
  • подсвечивать ошибки прямо в редакторе.

Рекомендация:

  • включите «Run ESLint on save» или аналог;
  • отключите встроенный форматтер в пользу Prettier или gofmt;
  • убедитесь, что IDE использует локально установленную версию линтера из проекта, а не глобальную.

Это гарантирует, что поведение линтера в IDE совпадает с тем, что происходит в CI.

Организация и версияция linter-config

Когда проект растет, linter-config также усложняется. Важно управлять им как нормальным кодом.

Вынесение конфигурации в отдельный пакет

Для монорепозитория или нескольких проектов в компании удобно создать отдельный npm-пакет с конфигурацией, например @company/eslint-config.

Структура:

packages/
  eslint-config/
    index.js           // Базовый конфиг ESLint
    package.json
  project-a/
    .eslintrc.cjs      // Подключает @company/eslint-config
  project-b/
    .eslintrc.cjs

Пример index.js:

// packages/eslint-config/index.js

module.exports = {
  env: { browser: true, node: true, es2021: true },
  extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
    project: "./tsconfig.json",
  },
  plugins: ["@typescript-eslint"],
  rules: {
    "no-var": "error",
    "prefer-const": "warn",
  },
};

А в проекте:

// .eslintrc.cjs
module.exports = {
  extends: ["@company/eslint-config"],
  rules: {
    // Локальные дополнения или изменения
    "no-console": "off",
  },
};

Теперь изменение правил делается централизованно и распространяется на все проекты через обновление версии пакета.

Миграции и изменения конфигурации

При существенных изменениях:

  • документируйте в CHANGELOG, какие правила были добавлены/убраны;
  • по возможности предоставляйте пути миграции (например, временно переводите правило из error в warn);
  • не меняйте резко уровень важности правил без необходимости.

Хорошая практика — сначала включить новое правило как warn, дать команде время поправить код, а затем поднять до error.

Заключение

linter-config — это не просто набор случайных флажков и правил, а формализованный договор команды о том, как должен выглядеть и работать код. Через грамотную настройку линтеров вы:

  • снижаете вероятность багов;
  • ускоряете ревью;
  • упрощаете онбординг новых разработчиков;
  • поддерживаете единый стиль проектов.

Мы разобрали общие принципы построения конфигураций, структуру типичных файлов для разных языков и инструментов, практические примеры интеграции в IDE и CI, а также подходы к развитию и версияции конфигов.

Если относиться к linter-config как к полноценному коду — документировать, ревьюить, постепенно эволюционировать — он становится надежным фундаментом качества проекта, а не источником случайных ошибок и раздражения.

Частозадаваемые технические вопросы и ответы

Как настроить разные правила ESLint для фронтенда и бэкенда в одном репозитории

Используйте несколько конфигов и overrides:

  • в корне создайте общий .eslintrc.cjs с базовыми правилами;
  • для фронтенд-папки (frontend/) и бэкенд-папки (backend/) добавьте локальные .eslintrc.cjs, которые делают extends от общего конфига;
  • либо в одном конфиге используйте overrides с files: ["frontend/**/*"] и files: ["backend/**/*"].

Так можно по-разному настраивать окружения (env) и правила.

Как временно отключить правило для большого Legacy-каталога

Добавьте в конфиг ESLint раздел overrides:

overrides: [
  {
    files: ["legacy/**/*"],
    rules: {
      "no-any": "off",
    },
  },
]

Так правило будет действовать везде, кроме каталога legacy. Постепенно можно уменьшать эту зону.

Как сделать так чтобы Prettier не трогал определенные файлы

Используйте файл .prettierignore. В него добавьте пути или паттерны:

build/
dist/
public/vendor.js

Prettier не будет форматировать файлы, подпадающие под эти выражения.

Как запускать разные наборы линтеров в CI для быстрого и полного режима

Сделайте несколько npm-скриптов, например:

  • lint:fast — только ESLint без тяжелых проверок;
  • lint:full — ESLint, Stylelint, TypeScript --noEmit, security-линтеры.

В CI настройте быстрый режим на каждый пуш, а полный — только на pull-request в main или по расписанию.

Как проверить что новый участник команды использует те же версии линтеров

Рекомендуется:

  • использовать npm ci вместо npm install (для JS/TS проектов), чтобы гарантировать версии из lock-файла;
  • хранить lock-файлы (package-lock.json, yarn.lock, pnpm-lock.yaml, poetry.lock);
  • при необходимости добавить в CI шаг, который проверяет, что lock-файлы не изменены после установки зависимостей.
Стрелочка влевоГенерация слайсов в Go - паттерн slice-generator с примерами и объяснениямиИнициализация FSD проекта - initСтрелочка вправо

Все гайды по Fsd

Открыть базу знаний

Отправить комментарий