Олег Марков
Настройка линтеров linter-config для современных проектов
Введение
Линтеры давно перестали быть «дополнительным инструментом» и стали обязательной частью современного процесса разработки. Они помогают выровнять стиль кода, находить потенциальные ошибки еще до запуска тестов и существенно сокращать время на ревью. Но сами по себе линтеры не работают: вам нужно настроить их поведение с помощью конфигурации — 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 в репозитории проекта.
Так вы избежите ситуации, когда у разных разработчиков линтер ведет себя по-разному.
Минимальный, но строгий набор правил
Давайте разберемся, почему перебор с правилами вреден:
- избыточное количество правил приводит к шуму — важные предупреждения тонут в массе несущественных;
- команда перестает доверять линтеру и просто отключает его;
- растет количество исключений и специальных комментариев.
Подход, который хорошо работает на практике:
- Включить рекомендованный базовый набор правил.
- Добавлять правила по мере реальной необходимости.
- Для каждого включенного правила зафиксировать мотивацию в документации или в комментарии в конфиге.
Разделение ответственности между линтерами
Важно понимать, какую задачу решает каждый инструмент:
- 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 последовательность обычно такая:
- Базовые (встроенные) правила.
- Пресеты из
extends. - Правила из локального файла конфигурации.
- Локальные переопределения для конкретных директорий или файлов (например, через
overrides).
Ближе к коду — сильнее приоритет. Это удобно для исключений, тестов, миграций.
Настройка linters для JavaScript и TypeScript
Теперь давайте подробно разберем самый популярный сценарий — настройку ESLint и Prettier для JavaScript/TypeScript проекта.
Базовая связка ESLint + Prettier
Распространенная ошибка — пытаться использовать ESLint и Prettier как независимые инструменты, при этом оставляя в ESLint правила форматирования, которые конфликтуют с Prettier.
Хороший рабочий вариант:
- Делегировать форматирование кода Prettier.
- Отключить в ESLint те правила, которые дублирует Prettier.
- Настроить единый 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-файлы не изменены после установки зависимостей.