Запуск ESLint в pre-commit хуке

Запуск линтинга на этапе pre-commit реализует контроль качества кода до попадания изменений в репозиторий. В экосистеме JavaScript ключевую роль в этом процессе играет интеграция системы хуков Git с линтером ESLint, что позволяет автоматически анализировать только те файлы, которые участвуют в коммите, и блокировать фиксации при обнаружении ошибок или нарушений стиля.

Git предоставляет механизм hooks — скриптов, которые выполняются на различных этапах жизненного цикла репозитория. Хук pre-commit срабатывает непосредственно перед созданием коммита. В этот момент уже сформирован набор изменений, но запись в историю ещё не произведена.

Типовой поток выполнения:

  • подготовка изменений в staging area (git add)
  • запуск pre-commit
  • выполнение проверок (линтеры, тесты)
  • разрешение или блокировка коммита

Встраивание ESLint в этот этап позволяет остановить попадание в историю синтаксически некорректного или стилистически несогласованного кода.

Базовая интеграция через Git hooks

Git хранит хуки в директории .git/hooks. Файл pre-commit может быть выполнен как shell-скрипт:

#!/bin/sh

npx eslint .

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

Ограничение области анализа до staged-файлов

Оптимизированная модель работы основана на проверке только тех файлов, которые находятся в индексе Git. Для этого используется команда:

git diff --cached --name-only --diff-filter=ACM

Интеграция с ESLint:

#!/bin/sh

FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.js$')

if [ -z "$FILES" ]; then
  exit 0
fi

npx eslint $FILES

Такой подход уменьшает время выполнения и делает pre-commit проверку предсказуемой даже в больших репозиториях.

Использование Husky для управления хуками

В современных JavaScript-проектах управление Git hooks часто делегируется инструменту Husky. Он абстрагирует работу с директориями .git/hooks и позволяет описывать хуки через конфигурацию проекта.

Инициализация:

npx husky install

Добавление pre-commit хука:

npx husky add .husky/pre-commit "npx eslint ."

После этого запуск ESLint становится частью управляемого процесса, а не ручного скрипта внутри Git.

Husky обеспечивает воспроизводимость конфигурации между окружениями и разработчиками, так как hooks хранятся в репозитории.

Комбинация Husky и lint-staged

Оптимальная производительность достигается при связке Husky и lint-staged. Последний ограничивает область выполнения инструментов только staged-файлами.

Конфигурация в package.json:

{
  "lint-staged": {
    "*.js": [
      "eslint --fix",
      "git add"
    ]
  }
}

Husky pre-commit хук:

npx lint-staged

В этой модели ESLint выполняется только над изменёнными файлами, а автоматическое исправление (--fix) возвращает модифицированные файлы обратно в staging area.

Использование кеширования ESLint

При больших проектах значительное ускорение достигается за счёт встроенного кеша:

npx eslint . --cache

Файл кеша (.eslintcache) позволяет повторно не анализировать неизменённые файлы. В контексте pre-commit это снижает нагрузку при повторяющихся коммитах в одни и те же области кода.

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

Обработка ошибок и блокировка коммита

ESLint возвращает ненулевой код завершения при обнаружении ошибок. Git interprets это как сигнал к остановке процесса commit.

Пример поведения:

  • отсутствуют ошибки → commit продолжается
  • обнаружены ошибки → commit прерывается

Это создаёт механизм принудительного соблюдения код-стандарта на уровне версии истории.

Конфигурация ESLint в контексте pre-commit

Эффективность проверки зависит от правил конфигурации .eslintrc. В контексте pre-commit часто применяются следующие стратегии:

  • разделение правил на error и warn
  • блокировка коммита только при error
  • отключение тяжёлых правил, влияющих на производительность
  • использование parserOptions для поддержки современного синтаксиса

Пример:

{
  "extends": "eslint:recommended",
  "rules": {
    "no-console": "warn",
    "no-unused-vars": "error"
  }
}

Monorepo и масштабирование pre-commit проверки

В монорепозиториях запуск линтера требует дополнительной сегментации. Используются:

  • фильтрация по пакетам
  • определение области через --scope
  • использование инструментов типа Nx или Lerna

Пример с ограничением директории:

npx eslint packages/app/src

При интеграции с Husky и lint-staged каждая зона репозитория может обрабатываться независимо, что снижает время pre-commit проверки до уровня, близкого к константному.

Проблемы и ограничения подхода

Механизм pre-commit не является универсальным слоем качества кода. Основные ограничения:

  • невозможность гарантировать проверку всего проекта в одном коммите при staged-only стратегии
  • зависимость от локальной конфигурации окружения
  • различия версий Node.js и ESLint между разработчиками
  • потенциальные обходы через --no-verify

Поэтому pre-commit проверка рассматривается как локальный фильтр, дополняемый CI-проверками, где ESLint запускается уже на полном наборе файлов репозитория.

Интеграция с CI как дублирующий уровень

В дополнение к pre-commit хукам часто используется аналогичная конфигурация в CI-пайплайне:

npx eslint .

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

Связка pre-commit + CI формирует двухуровневую систему контроля качества, где первый уровень работает быстро и локально, второй — полно и строго на стороне инфраструктуры.