META
Руководства
Настройка и использование Pre-commit хуков

Настройка и использование Pre-commit хуков в проекте

Этот документ описывает, как настроены и работают pre-commit хуки в проекте, которые автоматически проверяют код перед коммитом.

Обзор

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

В проекте используются следующие инструменты:

  • Husky 9 — для управления Git хуками
  • lint-staged 16 — для запуска линтеров только на staged файлах
  • ESLint — для проверки качества кода
  • Prettier — для форматирования кода

Архитектура решения

Принцип работы

  1. При попытке сделать коммит Git автоматически запускает pre-commit хук
  2. Хук проверяет, есть ли изменения во фронтенде (src/main/webapp/app/react/)
  3. Если изменений во фронтенде нет — хук пропускается
  4. Если есть изменения — запускается lint-staged, который:
    • Находит все staged файлы, соответствующие паттернам
    • Запускает ESLint с отдельным конфигом для pre-commit
    • Запускает Prettier для форматирования
    • Блокирует коммит при наличии ошибок

Структура файлов

meta/
├── .husky/
│   └── pre-commit                     # Git хук, запускаемый перед коммитом
├── eslint.config.mjs                  # Основной конфиг ESLint (warn для большинства правил)
├── eslint.pre-commit.config.mjs       # Строгий конфиг для pre-commit (error)
└── package.json                       # Конфигурация lint-staged

Husky

Установка и настройка

Husky устанавливается как dev-зависимость и автоматически инициализируется через скрипт prepare:

{
  "scripts": {
    "prepare": "husky"
  },
  "devDependencies": {
    "husky": "^9.1.7"
  }
}

После установки зависимостей (npm install) Husky автоматически настраивает Git хуки.

Pre-commit хук

Файл .husky/pre-commit содержит логику проверки изменений:

# Проверяем, есть ли изменения во фронтенде
FRONTEND_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '^src/main/webapp/app/react/' || true)
 
if [ -z "$FRONTEND_FILES" ]; then
  echo "No frontend files changed, skipping pre-commit hooks"
  exit 0
fi
 
echo "Frontend files changed, running lint-staged..."
npx lint-staged

Как это работает:

  • git diff --cached --name-only — получает список staged файлов
  • --diff-filter=ACM — фильтрует только добавленные (A), измененные (C) и модифицированные (M) файлы
  • grep -E '^src/main/webapp/app/react/' — проверяет, есть ли файлы во фронтенде
  • Если файлов нет — хук завершается успешно
  • Если есть — запускается lint-staged

Примеры:

# Коммит только backend файлов — хук пропускается
git add src/main/java/MyClass.java
git commit -m "Update backend"  # Хук не запускается
 
# Коммит фронтенд файлов — хук запускается
git add src/main/webapp/app/react/src/components/MyComponent.tsx
git commit -m "Update component"  # Хук проверяет файл

lint-staged

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

Конфигурация lint-staged находится в package.json:

{
  "lint-staged": {
    "src/main/webapp/app/react/**/*.{ts,tsx}": [
      "eslint --config eslint.pre-commit.config.mjs --fix",
      "prettier --write"
    ],
    "src/main/webapp/app/react/**/*.{js,jsx,json,css,less,scss,md}": [
      "prettier --write"
    ]
  }
}

Паттерны файлов

  • **/*.{ts,tsx} — TypeScript и TSX файлы

    • Запускается ESLint с автоматическим исправлением
    • Запускается Prettier для форматирования
  • **/*.{js,jsx,json,css,less,scss,md} — остальные файлы

    • Запускается только Prettier для форматирования

Как работает lint-staged

  1. Находит все staged файлы, соответствующие паттернам
  2. Запускает команды для каждого файла
  3. Автоматически добавляет исправленные файлы обратно в staging
  4. Если команда возвращает ошибку — блокирует коммит

Пример:

# Исходный файл с ошибками
const test: any = 0;  # Использование any
 
# После git add и попытки коммита
git add src/main/webapp/app/react/src/test/test.ts
git commit -m "Add test"
 
# lint-staged запускает:
# 1. eslint --fix (находит ошибку с any)
# 2. prettier --write (форматирует код)
# Коммит блокируется из-за ошибки ESLint

ESLint конфигурация для pre-commit

Отдельный конфиг

Для pre-commit используется отдельный конфиг eslint.pre-commit.config.mjs, который более строгий, чем основной eslint.config.mjs.

Причина разделения:

  • Основной конфиг (eslint.config.mjs) использует warn для большинства правил, чтобы не блокировать сборку проекта
  • Pre-commit конфиг (eslint.pre-commit.config.mjs) использует error для критичных правил, чтобы блокировать коммиты с проблемным кодом

Критичные правила (error)

Эти правила блокируют коммит при нарушении:

TypeScript

  • @typescript-eslint/no-explicit-any: 'error'
    Блокирует использование типа any.

    // Ошибка — коммит заблокирован
    const data: any = {};
     
    // Правильно
    const data: Record<string, unknown> = {};
  • @typescript-eslint/no-unused-vars: 'error'
    Блокирует неиспользуемые переменные.

    // Ошибка — коммит заблокирован
    const unusedVar = 42;
     
    // Правильно
    const usedVar = 42;
    console.log(usedVar);

React Hooks

  • react-hooks/rules-of-hooks: 'error'
    Блокирует нарушение правил хуков.

    // Ошибка — хук вызывается в условии
    if (condition) {
      useEffect(() => {
        // ...
      });
    }
     
    // Правильно
    useEffect(() => {
      if (condition) {
        // ...
      }
    });
  • react-hooks/exhaustive-deps: 'error'
    Блокирует отсутствие зависимостей в хуках.

    // Ошибка — отсутствует зависимость
    useEffect(() => {
      console.log(value);
    }, []); // value не указан в зависимостях
     
    // Правильно
    useEffect(() => {
      console.log(value);
    }, [value]);

Общие правила

  • no-var: 'error'
    Блокирует использование var.

    // Ошибка
    var count = 0;
     
    // Правильно
    let count = 0;
    // или
    const count = 0;
  • @typescript-eslint/ban-ts-comment: 'error'
    Блокирует использование @ts-ignore и подобных комментариев без объяснения.

    // Ошибка
    // @ts-ignore
    const result = someFunction();
     
    // Правильно (с объяснением)
    // @ts-expect-error - функция возвращает any, но мы знаем тип
    const result: MyType = someFunction();

Полный список правил

Все правила в eslint.pre-commit.config.mjs настроены как error, что означает, что любое нарушение блокирует коммит:

rules: {
  // Критичные правила для pre-commit - блокируем коммит при ошибках
  "@typescript-eslint/no-explicit-any": "error",
  "@typescript-eslint/no-unused-vars": "error",
  "react-hooks/rules-of-hooks": "error",
  "react-hooks/exhaustive-deps": "error",
  
  // Остальные правила также настроены как error
  "@typescript-eslint/no-this-alias": "error",
  "@typescript-eslint/ban-ts-comment": "error",
  "@typescript-eslint/no-require-imports": "error",
  "@typescript-eslint/no-unsafe-function-type": "error",
  "@typescript-eslint/no-unused-expressions": "error",
  "@typescript-eslint/no-empty-object-type": "error",
 
  "react/no-deprecated": "error",
  "react/prop-types": "error",
  "react/no-unknown-property": "error",
 
  "no-var": "error",
  "import/no-unresolved": "error",
  "import/named": "error",
  "prefer-spread": "error",
  "prefer-rest-params": "error"
}

Prettier

Prettier автоматически форматирует код согласно настройкам проекта (.prettierrc):

{
  "printWidth": 120,
  "trailingComma": "all",
  "singleQuote": true,
  "semi": true,
  "arrowParens": "avoid"
}

Как работает:

  1. Prettier автоматически форматирует staged файлы
  2. Отформатированные файлы автоматически добавляются обратно в staging
  3. Если форматирование изменило файл, коммит продолжается с отформатированным кодом

Пример:

// До форматирования
const component = (props:{name:string,age:number})=>{return<div>{props.name}</div>}
 
// После Prettier (автоматически)
const component = (props: { name: string; age: number }) => {
  return <div>{props.name}</div>;
};

Рабочий процесс

Типичный сценарий

  1. Разработчик вносит изменения:

    # Редактирует файл
    vim src/main/webapp/app/react/src/components/MyComponent.tsx
  2. Добавляет файлы в staging:

    git add src/main/webapp/app/react/src/components/MyComponent.tsx
  3. Пытается сделать коммит:

    git commit -m "Update component"
  4. Pre-commit хук запускается:

    Frontend files changed, running lint-staged...
  5. lint-staged проверяет файл:

    • ESLint находит ошибки (если есть)
    • Prettier форматирует код
    • Если есть ошибки ESLint — коммит блокируется
    • Если ошибок нет — коммит проходит

Обработка ошибок

Если ESLint находит ошибки:

$ git commit -m "Update component"
Frontend files changed, running lint-staged...
 
 eslint --config eslint.pre-commit.config.mjs --fix:
  src/main/webapp/app/react/src/components/MyComponent.tsx
    1:13  error  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
 
 1 problem (1 error, 0 warnings)
 
husky - pre-commit script failed (code 1)

Что делать:

  1. Исправить ошибки в коде
  2. Добавить исправленные файлы снова: git add ...
  3. Попробовать коммит снова

Если нужно временно пропустить проверку (не рекомендуется):

git commit --no-verify -m "Update component"

Важно: Используйте --no-verify только в исключительных случаях. Лучше исправить ошибки перед коммитом.

Преимущества

Автоматическая проверка

  • Код проверяется автоматически перед каждым коммитом
  • Не нужно помнить о запуске линтеров вручную
  • Проблемы обнаруживаются до попадания в репозиторий

Только измененные файлы

  • Проверяются только файлы, которые были изменены
  • Быстрая работа даже на больших проектах
  • Не тратится время на проверку всего проекта

Разделение конфигов

  • Основной конфиг ESLint не блокирует сборку
  • Pre-commit конфиг строже и блокирует проблемный код
  • Можно постепенно улучшать качество кода

Автоматическое форматирование

  • Prettier автоматически форматирует код
  • Единый стиль кода в проекте
  • Меньше споров о форматировании в code review

Игнорируемые пути

Те же пути, что и в основном конфиге ESLint, игнорируются при проверке:

ignores: [
  'docs/',
  'node_modules/',
  'out/',
  'src/test/',
  'src/main/webapp/app/_b/',
  'src/main/webapp/app/vendor/',
  'src/main/webapp/app/bower_components/',
  'src/main/webapp/app/contrib/',
  'src/main/webapp/app/react/node_modules/',
  'src/main/webapp/app/react/src/components/external/'
]