Олег Марков
Настройка линтеров linter-config от основ к рабочему проекту
Введение
Настройка линтеров через конфигурационные файлы linter-config давно стала обязательной частью современного рабочего процесса. Линтеры помогают автоматически находить ошибки, потенциальные баги и несоблюдение кодстайла. Но без продуманной конфигурации они или почти бесполезны, или начинают мешать работе потоком ложных срабатываний.
Здесь вы разберете на практике, как устроены конфигурации линтеров, из чего они состоят, как их группировать и переиспользовать. Смотрите, я покажу вам типовые примеры из мира JavaScript и TypeScript с ESLint, а также параллели для других стеков вроде Go, Python и фронтенда. Подходы одни и те же, даже если конкретный инструмент разный.
Цель статьи — чтобы вы умели:
- спроектировать конфигурацию линтера для нового проекта
- адаптировать готовую под существующий легаси
- отключать, ослаблять и усиливать правила осознанно
- разделять конфиг для разработки, production и тестов
- интегрировать линтеры в IDE и CI
Давайте шаг за шагом разберем, как это сделать.
Зачем вообще нужен linter-config
Автоматизация код-ревью
Линтер с хорошей конфигурацией берет на себя:
- проверку форматирования и стиля
- поиск очевидных ошибок (неиспользуемые переменные, лишний код)
- контроль соглашений (имена, структуры файлов, импорты)
- типичные антипаттерны (сложные условия, магические числа)
В итоге код-ревью превращается из обсуждения скобочек и переносов строк в обсуждение архитектуры и бизнес-логики. Но этого не произойдет, если конфиг линтера не настроен под ваш проект.
Консистентность в команде
Без конфигурации каждый разработчик будет опираться на свои привычки. linter-config позволяет зафиксировать единые правила и автоматически их проверять:
- один и тот же стиль именования
- единые ограничения по цикломатической сложности
- одинаковая структура импортов
- согласованные правила для тестов, UI-компонентов, серверного кода
Плавная эволюция кода
Важно понимать: линтер — это не только «строго сейчас». Он помогает плавно улучшать существующий код, не ломая процесс разработки. За это отвечает грамотная конфигурация:
- постепенное усиление правил
- разные уровни строгости для старого и нового кода
- временные исключения и TODO-правила
- явная фиксация технического долга
Общие принципы проектирования linter-config
Основные элементы конфигурации
Почти у всех современных линтеров структура похожа. Возьмем в качестве примера ESLint, но эти идеи легко переносимы на другие инструменты:
- extends — базовые наборы правил, от которых вы наследуетесь
- plugins — дополнительные пакеты правил
- rules — конкретные настройки и переопределения
- overrides — особые правила для отдельных типов файлов
- env / parserOptions / settings — контекст проекта (окружение, язык, версии)
Простой пример .eslintrc.js. Здесь я размещаю пример, чтобы вам было проще понять:
// .eslintrc.js
module.exports = {
// Наследуемся от базового конфигурационного набора
extends: [
'eslint:recommended', // Набор базовых правил ESLint
'plugin:@typescript-eslint/recommended' // Правила для TypeScript
],
// Подключаем плагины с дополнительными правилами
plugins: ['@typescript-eslint', 'import'],
// Настраиваем парсер и особенности синтаксиса проекта
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020, // Современный синтаксис JS
sourceType: 'module' // Используем ES-модули
},
// Описываем окружение (глобальные переменные и особенности)
env: {
browser: true, // Код выполняется в браузере
node: true, // и в Node.js
es6: true
},
// Переопределяем правила и уровни строгости
rules: {
// Ошибка при использовании var
'no-var': 'error',
// Предупреждение при неиспользуемых переменных, но игнорируем аргументы _
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
// Ошибка, если есть неупорядоченные импорты
'import/order': ['error', {
'alphabetize': { order: 'asc', caseInsensitive: true }
}]
}
}
Как видите, конфиг — это просто декларативное описание того, чего вы хотите от линтера. Важно, чтобы это описание было:
- коротким и читаемым
- разделенным на логические части
- понятным новичку в проекте
Уровни строгости: off, warn, error
Практически во всех линтерах уровни похожи:
off— правило отключеноwarn— предупреждениеerror— ошибка, которая обычно валит сборку или проверку в CI
Давайте разберемся на примере нескольких правил:
rules: {
// Полностью отключаем правило
'no-console': 'off',
// Показываем предупреждение, но не падаем
'eqeqeq': 'warn', // Рекомендует использовать === вместо ==
// Любое нарушение этого правила считается ошибкой
'no-debugger': 'error'
}
Рекомендуемый подход:
- в локальной разработке больше
warn, чтобы не тормозить работу - в CI или
pre-commitте же правила можно усиливать доerror - для переезда старого кода многие правила сначала запускают в режиме
warn
Смотрите, важный момент: уровни строгости — это ваш инструмент постепенного внедрения линтинга без жестких блокировок в первый же день.
Типичная структура конфигурации для проекта
Базовый конфиг на уровне репозитория
Обычно в корне репозитория лежит один «главный» конфиг линтера. Например, .eslintrc.js, .golangci.yml, .flake8, .stylelint.config.js и так далее. Именно он задает общую политику.
Пример дерева проекта:
project/
.eslintrc.js // Базовая конфигурация линтера
package.json
src/
...
tests/
...
scripts/
...
Рекомендации к содержанию базового конфига:
- общий набор
extendsиplugins - базовые соглашения по стилю
- правила, одинаковые для всего проекта
- минимум специфики, которая относится только к определенным каталогам
Локальные конфиги для разных областей
Нередко бывает полезно разделить настройки:
src/— основной код приложенияtests/— тесты, где допустимы свои практики (например,console.logили магические строки)scripts/— вспомогательные скрипты
Например, в ESLint вы можете использовать overrides в одном общем файле:
// .eslintrc.js
module.exports = {
// Общая базовая конфигурация
extends: ['eslint:recommended'],
overrides: [
{
// Особые настройки для тестов
files: ['**/*.test.ts', '**/*.spec.ts'],
env: {
jest: true // Включаем глобали jest describe it и т.п.
},
rules: {
// В тестах допускаем console.log как отладку
'no-console': 'off'
}
},
{
// Конфиг для скриптов
files: ['scripts/**/*.js'],
env: {
node: true
},
rules: {
// Для скриптов может быть другой набор требований
'no-process-exit': 'off'
}
}
]
}
Теперь вы увидите, как overrides позволяют немного менять политику для каждого типа кода, при этом сохраняя единый конфиг.
Наследование и переиспользование конфигов
Локальные переиспользуемые конфиги
В больших монорепозиториях удобно создавать свои конфиги и наследоваться от них. Например, можно сделать папку config/lint и внутри несколько профилей:
config/
lint/
base.eslintrc.js // Базовые правила для всего репо
frontend.eslintrc.js // Специфика фронтенда
backend.eslintrc.js // Специфика бэкенда
Пример config/lint/base.eslintrc.js:
// config/lint/base.eslintrc.js
module.exports = {
extends: ['eslint:recommended'],
rules: {
'no-var': 'error',
'prefer-const': 'warn'
}
}
А теперь конфиг конкретного пакета:
// packages/frontend/.eslintrc.js
module.exports = {
// Наследуемся от своего локального базового конфига
extends: [
'../../config/lint/base.eslintrc.js',
'plugin:react/recommended'
],
rules: {
// Дополнительные правила, актуальные только для фронтенда
'react/prop-types': 'off'
}
}
Такой подход удобно масштабируется, если у вас несколько команд и разные типы сервисов.
Переиспользуемые npm-пакеты с конфигом
Если у вас много репозиториев, вы можете вынести конфиг в отдельный npm-пакет @company/eslint-config и подключать его во всех проектах.
Пример использования:
// .eslintrc.js в обычном проекте
module.exports = {
extends: ['@company/eslint-config'],
rules: {
// Локальные уточнения для конкретного проекта
'no-console': 'warn'
}
}
Чтобы такой пакет работал, внутри него обычно лежит файл index.js с экспортируемым конфигом и объявленными зависимостями (peerDependencies) к нужным плагинам.
Давайте посмотрим, как может выглядеть содержимое такого пакета:
// index.js внутри @company/eslint-config
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended'
],
plugins: [
'@typescript-eslint',
'import'
],
rules: {
'no-var': 'error',
'prefer-const': ['error', { destructuring: 'all' }],
'@typescript-eslint/explicit-function-return-type': 'off'
}
}
Этот подход позволяет централизованно обновлять правила во всех проектах компании.
Практическая настройка для разных стеков
Ниже я опишу настройки на примере нескольких популярных линтеров. Логика везде общая, но удобнее понимать на конкретных конфигурациях.
JavaScript / TypeScript и ESLint
Установка и базовый конфиг
Для TypeScript-проекта обычно делают так:
# Устанавливаем ESLint и поддержку TypeScript
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
Создадим базовый .eslintrc.js:
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser', // Парсер TS
plugins: ['@typescript-eslint'], // Плагин с правилами TS
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module'
},
rules: {
// Немного адаптируем базовый набор под проект
'@typescript-eslint/no-explicit-any': 'warn', // Разрешаем any, но с предупреждением
'@typescript-eslint/ban-ts-comment': 'off' // Разрешаем явные ts-ignore при необходимости
}
}
Обратите внимание на комментарии — их стоит добавлять и в своем конфиге, чтобы через полгода было понятно, зачем то или иное правило изменено.
Дополнительные плагины и правила
Часто добавляют:
eslint-plugin-import— контроль импортовeslint-plugin-promise— правила работы с промисамиeslint-plugin-unicorn— набор полезных best practices
Пример расширенного конфига:
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended'
],
plugins: ['@typescript-eslint', 'import'],
rules: {
// Стиль импортов
'import/order': ['warn', {
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'alphabetize': { order: 'asc', caseInsensitive: true }
}],
// Запрещаем "висящие" промисы
'@typescript-eslint/no-floating-promises': 'error',
// Предупреждаем о неиспользуемых переменных
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
}
}
Теперь давайте перейдем к примеру, как этот конфиг влияет на реальный код.
// Этот код нарушает правило no-floating-promises
async function saveUser(user) {
db.save(user) // Линтер покажет ошибку - промис никуда не await и не обрабатывается
}
Исправленный вариант:
async function saveUser(user) {
// Здесь мы явно ожидаем завершения операции
await db.save(user)
}
Линтер с таким конфигом подскажет, где вы забыли await и могли получить незаметный баг.
Go и golangci-lint
В Go популярна связка golangci-lint плюс конфиг golangci.yml в корне проекта. Смотрите, как выглядит базовая конфигурация:
# golangci.yml
run:
timeout: 5m # Лимит времени на выполнение линтинга
tests: true # Линтить ли тесты
linters:
enable:
- govet # Поиск типичных багов
- staticcheck # Глубокие проверки
- gosimple # Упрощение кода
- errcheck # Проверка ошибок
disable:
- maligned # Пример отключенного линтера
issues:
# Максимальное количество одинаковых ошибок
max-same-issues: 3
# Список исключений по тексту сообщения или файлам
exclude-rules:
- path: _test\.go
linters:
- errcheck # В тестах допускаем не обрабатывать ошибки
Как видите, структура очень похожа по смыслу на ESLint:
enable/disable— включить или выключить линтерыexclude-rules— локальные исключенияrun— контекст выполнения
Давайте посмотрим, что происходит в следующем примере кода:
// Здесь мы игнорируем ошибку и линтер errcheck это заметит
result, _ := someFunc() // Линтер ругается на пустой обработчик ошибки
_ = result
С такой конфигурацией golangci-lint подскажет все места, где вы забыли обработать ошибку.
Python и flake8
Во многих Python-проектах конфигурация линтера хранится в setup.cfg или tox.ini. Обратите внимание, как задаются правила:
[flake8]
max-line-length = 100 # Максимальная длина строки
ignore = E203, W503 # Список кодов ошибок, которые игнорируем
exclude = .git,__pycache__,build # Папки, которые не проверяем
По смыслу это те же настройки:
- политика длины строк
- исключения правил
- исключения по путям
Пошаговое внедрение линтеров в существующий проект
Часто вы настраиваете линтер не для нового «чистого» проекта, а для большого уже существующего, где тысячи строк и множество нарушений. В этом случае важно не просто «включить все и чинить», а сделать по шагам.
Шаг 1. Включите минимальный набор правил
Сначала включите базовый конфиг:
eslint:recommended(JS/TS)- ограниченный набор линтеров в
golangci-lint - базовые правила
flake8
И соберите список нарушений. На этом этапе:
- не пытайтесь исправить все
- не превращайте все нарушения в ошибки
- установите большинство правил в
warn
rules: {
'no-unused-vars': 'warn',
'no-console': 'off', // Пока не трогаем
'eqeqeq': 'warn'
}
Шаг 2. Введите правило «только для нового кода»
Один из рабочих приемов — отделять старый и новый код. Например, можно:
- для новых директорий применять более строгие правила
- запрещать изменения в файлах, пока в них есть предупреждения линтера
- использовать специальные скрипты, проверяющие только измененные файлы
Пример проверки только измененных файлов (через ESLint и git):
# Псевдокоманда - идея
eslint $(git diff --name-only --cached -- '*.ts' '*.tsx')
Комментарий:
- здесь мы берем только измененные и добавленные файлы
- так вы постепенно будете чистить код по мере работы над ним
Шаг 3. Постепенно усиливайте правила
Как только команда привыкает к базовым проверкам:
- меняйте важные правила с
warnнаerror - добавляйте специализированные плагины (безопасность, производительность)
- вводите отдельный строгий профиль для CI
Например, можно сделать:
.eslintrc.jsдля разработки.eslintrc.ci.jsдля CI с более жесткими правилами
// .eslintrc.ci.js
const baseConfig = require('./.eslintrc.js')
module.exports = {
...baseConfig,
rules: {
...baseConfig.rules,
// В CI предупреждения превращаем в ошибки
'@typescript-eslint/no-unused-vars': 'error',
'eqeqeq': 'error'
}
}
В CI запускается строгий конфиг, а локально — более мягкий.
Типичные ошибки при настройке линтеров
Перевключили все правила «на максимум»
Одна из самых частых проблем — взять популярный пресет «самый строгий» и включить его в большом легаси-проекте. В итоге вы получаете:
- десятки тысяч предупреждений
- конфликты с существующей архитектурой
- раздражение команды
Лучше:
- начать с рекомендованных пресетов
- усилять только те правила, которые реально важны для вашего проекта
- вводить новые требования постепенно
Отключение правил без причины
Иногда в конфиге появляются строки вроде:
rules: {
'no-anything': 'off' // Непонятно, почему отключили
}
Через полгода никто не помнит, зачем это сделано. Обязательно добавляйте комментарий с причиной:
rules: {
// Отключаем правило no-anything, так как используем специфическую библиотеку,
// которая конфликтует с ним. Подробнее - в документации по архитектуре.
'no-anything': 'off'
}
Это сильно повышает качество и прозрачность вашего linter-config.
Смешение форматирования и логических правил
Форматирование кода (отступы, кавычки, запятые) лучше отдавать отдельным инструментам вроде Prettier или gofmt. Логические линтеры (ESLint, golangci-lint) должны сосредоточиться на:
- возможных ошибках
- подозрительных конструкциях
- сложных местах кода
В JS/TS-мире это обычно решают так:
- Prettier отвечает за форматирование
- ESLint — за логику и архитектурные правила
- Чтобы они не конфликтовали, подключают
eslint-config-prettier
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier' // Отключаем конфликтующие с prettier правила
]
Интеграция линтеров с IDE и CI
Подсказки в редакторе кода
Почти все популярные редакторы (VS Code, WebStorm, GoLand, PyCharm) умеют:
- автоматически запускать линтер при сохранении файла
- подсвечивать ошибки прямо в коде
- предлагать быстрые исправления
Важно:
- установить соответствующее расширение (например, ESLint plugin в VS Code)
- убедиться, что редактор использует конфиг из проекта, а не глобальный
В VS Code для ESLint есть настройка в settings.json:
{
"eslint.workingDirectories": [
{ "mode": "auto" }
]
}
Она помогает корректно находить конфиг в монорепозиториях.
Автоматическая проверка в CI
Интеграция в CI позволяет:
- не пропускать в main ветку код, который ломает правила
- гарантировать единое качество во всех окружениях
Пример для GitHub Actions с ESLint:
# .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.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npx eslint "src/**/*.{ts,tsx,js,jsx}"
Похожим образом подключается golangci-lint и другие инструменты: обычно это просто отдельный шаг в пайплайне.
Работа с исключениями и сложными случаями
Когда действительно нужен disable в коде
Иногда вам приходится отключить правило прямо в файле или строке. Покажу вам, как это реализовано на практике.
Пример в ESLint:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function handleMessage(message: any) {
// Здесь мы сознательно допускаем тип any
}
Комментарии:
- используйте
disable-next-lineилиdisable-line, а неdisableбез ограничений по файлу - обязательно добавляйте рядом пояснение в обычном комментарии, почему это исключение оправдано
Похожий подход в golangci-lint:
//nolint:errcheck // Здесь мы специально игнорируем ошибку, логируем ее отдельно
result, _ := someFunc()
_ = result
Локальные исключения файлов и директорий
Иногда есть каталоги, которые вы не хотите линтить вообще, например, сгенерированный код.
В ESLint это можно сделать через .eslintignore:
# .eslintignore
dist/
build/
generated/
В golangci-lint — через настройки:
run:
skip-dirs:
- generated
- vendor
Старайтесь не злоупотреблять игнором. Лучше точечно отключать конкретные правила там, где это необходимо по делу.
Заключение
Настройка линтеров через linter-config — это не про «поставить инструмент и включить все галочки». Это про аккуратное проектирование набора правил, который:
- отражает договоренности вашей команды
- помогает находить реальные проблемы, а не «шум»
- позволяет постепенно улучшать код, не блокируя поток разработки
- хорошо интегрирован в IDE и CI
Ключевые идеи, которые стоит вынести:
- используйте наследование конфигов и переиспользуемые пакеты
- разделяйте правила по областям (основной код, тесты, скрипты)
- начинайте с базового набора и постепенно усиливайте строгие правила
- документируйте любые отключения и нестандартные решения
- отделяйте форматирование от логических проверок
С таким подходом линтер перестает быть «раздражающей проверкой» и становится полезным помощником, который каждый день экономит вам время и снижает количество багов в проде.
Частозадаваемые технические вопросы по теме статьи
1. Как запускать линтер только на измененных файлах перед коммитом
Можно использовать husky и lint-staged (для JS/TS):
npm install --save-dev husky lint-staged
В package.json:
{
"lint-staged": {
"src/**/*.{ts,tsx,js,jsx}": "eslint"
},
"scripts": {
"prepare": "husky install"
}
}
И добавить hook:
npx husky add .husky/pre-commit "npx lint-staged"
Теперь при коммите линтер запустится только на измененных файлах.
2. Как иметь разные конфиги линтера для продакшена и локальной разработки
Сделайте два файла, например .eslintrc.dev.js и .eslintrc.ci.js, и используйте разные команды в package.json:
{
"scripts": {
"lint": "eslint -c .eslintrc.dev.js src",
"lint:ci": "eslint -c .eslintrc.ci.js src"
}
}
В CI запускайте npm run lint:ci, локально — npm run lint.
3. Как понять, какое именно правило сработало и где его изменить
У большинства линтеров в сообщении об ошибке есть идентификатор правила. Например, в ESLint это что-то вроде no-unused-vars. Чтобы настроить его:
- найдите эту строку в документации ESLint или плагина
- добавьте правило в раздел
rulesвашего конфига - измените уровень строгости или параметры
rules: {
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
}
4. Как временно ослабить одно правило для всего проекта
Можно понизить уровень с error до warn в конфиге, но при этом сохранить настройку:
rules: {
'eqeqeq': 'warn' // Временно сделали предупреждением
}
Если вы планируете позже вернуть его в error, добавьте комментарий с планом:
// TODO: вернуть eqeqeq на error после того, как почистим старый код
'eqeqeq': 'warn'
5. Как комбинировать несколько линтеров в одном проекте без конфликтов
Главное правило — развести зоны ответственности. Например в JS-проекте:
- Prettier отвечает за форматирование
- ESLint — за логику и архитектуру
- Stylelint — за стили
В конфиг ESLint добавьте eslint-config-prettier, а Prettier подключите через отдельный скрипт или hook. В CI запускайте их по очереди:
npm run lint # ESLint
npm run lint:styles # Stylelint
npm run format:check # Prettier --check