Стандарты кода
Elftia использует строгую конфигурацию ESLint (typescript-eslint/strict) с прогрессивной стратегией применения.
Инструментальная цепочка
| Инструмент | Назначение | Файл конфигурации |
|---|---|---|
| ESLint | Проверка качества кода (строгий режим) | eslint.config.js |
| Prettier | Форматирование кода | .prettierrc |
| Husky | Управление Git Hooks | .husky/pre-commit |
| lint-staged | Проверка staged-файлов | package.json |
Часто используемые команды
# Запустить проверку ESLint
npm run lint:eslint
# Проверить форматирование Prettier
npm run lint:prettier
# Автоматически отформатировать код
npm run format
# Полная lint-проверка (ESLint + TypeScript)
npm run lint
# Проверить один файл
npx eslint path/to/file.ts
# Автоматически исправить один файл
npx eslint path/to/file.ts --fix
Автоматические проверки перед коммитом
При коммите Husky автоматически запускает lint-staged для staged-файлов .ts/.tsx с командой eslint --fix:
- уровень error: Блокирует коммит, необходимо исправить
- уровень warn: Выводит предупреждение, коммит разрешён
- auto-fix: Автоматически исправляет устранимые проблемы, например сортировку импортов
Краткий справочник по правилам ESLint
Уровни серьёзности
| Уровень | Значение | Поведение при коммите |
|---|---|---|
error | Обязательно исправить | Блокирует коммит |
warn | Рекомендуется исправить | Коммит разрешён |
off | Отключено | - |
Правила уровня error (блокируют коммит)
// simple-import-sort/imports — исправляется автоматически
// Импорты должны быть упорядочены по правилам; eslint --fix исправит автоматически
// prefer-const
let value = 1; // Никогда не переназначается — следует использовать const
// no-var
var x = 1; // Использование var запрещено
// eqeqeq
if (a == b) { } // Использовать == вместо === (кроме проверок на null)
// react-hooks/rules-of-hooks
if (condition) { useState(); } // Хуки можно вызывать только на верхнем уровне
Правила уровня warn (коммит разрешён)
Правила TypeScript
| Правило | Описание | Исправление |
|---|---|---|
@typescript-eslint/no-unused-vars | Неиспользуемые переменные | Удалить или добавить префикс _ |
@typescript-eslint/no-explicit-any | Использование типа any | Заменить конкретным типом или unknown |
@typescript-eslint/consistent-type-imports | Импорты без type-только | Заменить на import { type Foo } |
@typescript-eslint/no-non-null-assertion | Ненулевое утверждение ! | Использовать опциональную цепочку ?. или защиту типа |
Правила React
| Правило | Описание | Исправление |
|---|---|---|
react-hooks/exhaustive-deps | Неполный массив зависимостей хука | Дополнить массив зависимостей |
react/jsx-no-leaked-render | Утечка рендера (count && <X />) | Заменить на count > 0 && <X /> |
react/self-closing-comp | Пустой тег без самозакрытия | <Comp></Comp> → <Comp /> |
react/jsx-curly-brace-presence | Лишние фигурные скобки | prop={"val"} → prop="val" |
react/jsx-boolean-value | Избыточное булево значение | disabled={true} → disabled |
react/hook-use-state | Имя состояния не в camelCase | [Value, setV] → [value, setV] |
Прочие правила
| Правило | Описание | Исправление |
|---|---|---|
no-console | Использование console.log в процессе рендеринга | Заменить на console.warn/error/info/debug |
jsx-a11y/* | Проблемы доступности | См. Design System |
Правила сортировки импортов
Импорты должны быть упорядочены следующим образом (eslint --fix исправит автоматически):
// 1. Встроенные модули Node.js
import path from 'node:path';
import fs from 'node:fs';
// 2. Внешние пакеты
import React, { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
// 3. Внутренний псевдоним @/
import { Button } from '@/components/ui/button';
import { useAppState } from '@/shared/hooks/useAppState';
// 4. Псевдоним @shared/
import type { Message } from '@shared/contracts';
// 5. Псевдоним @main/
import { AppPaths } from '@main/services/infra/paths/paths';
// 6. Импорты из родительских директорий
import { utils } from '../utils';
// 7. Импорты из текущей директории
import { helper } from './helper';
// 8. Импорты стилей
import './styles.css';
Конфигурация Prettier
| Опция | Значение | Описание |
|---|---|---|
semi | true | Использовать точки с запятой |
singleQuote | true | Использовать одинарные кавычки |
tabWidth | 2 | Ширина отступа |
trailingComma | 'es5' | Завершающие запятые |
printWidth | 100 | Максимальная длина строки |
Ограничения на размер файлов
Золотое правило: один файл не должен превышать 600 строк кода.
| Тип файла | Рекомендуемый предел | Предупреждение | Жёсткий предел |
|---|---|---|---|
| React-компонент | 400 строк | 600 строк | 800 строк |
| Пользовательский хук | 300 строк | 400 строк | 600 строк |
| Вспомогательные функции | 150 строк | 200 строк | 300 строк |
| Определения типов | 100 строк | 150 строк | 200 строк |
| Сервис/API | 300 строк | 400 строк | 600 строк |
Последствия нарушений:
- Превышение предупредительной линии → обязательно разбить файл в рамках текущей задачи, не откладывать
- Превышение жёсткого предела → немедленно остановиться, сначала провести рефакторинг
Соглашения об именовании
Именование файлов
| Тип | Правило | Пример |
|---|---|---|
| React-компонент | PascalCase.tsx | UserMessage.tsx |
| Хуки | use + PascalCase.ts | useChatActions.ts |
| Вспомогательные функции | camelCase.ts | messageHelpers.ts |
| Определения типов | camelCase.ts или types.ts | chatTypes.ts |
| Константы | UPPER_CASE.ts | API_CONSTANTS.ts |
| Сервисы | PascalCase + Service.ts | ChatService.ts |
| Директории | kebab-case | chat-messages/ |
Именование компонентов
// Хорошо: понятное, описательное
export function UserMessage() { }
export function EditTool() { }
export function ChatComposer() { }
// Плохо: слишком обобщённое
export function Message() { }
export function Tool() { }
export function Input() { }
Именование хуков
// Хорошо: use + описание функции
export function useChatActions() { }
export function useMessageEffects() { }
// Плохо: не соответствует соглашению об именовании React Hook
export function chatActions() { }
export function messageHook() { }
Стандарты структуры директорий
Директория компонентов
components/
├── ComponentName/
│ ├── index.ts # Единая точка экспорта
│ ├── ComponentName.tsx # Основной компонент
│ ├── types.ts # Определения типов
│ ├── utils.ts # Вспомогательные функции
│ ├── SubComponent.tsx # Дочерний компонент
│ └── __tests__/
│ └── ComponentName.test.tsx
Директория хуков
hooks/
├── domain/
│ ├── index.ts # Единая точка экспорта
│ ├── useDomainState.ts # Состояние
│ ├── useDomainActions.ts # Действия
│ └── useDomainEffects.ts # Побочные эффекты
Директория сервисов
services/
├── ServiceName/
│ ├── index.ts # Единая точка экспорта
│ ├── ServiceName.ts # Основной сервис
│ ├── types.ts # Определения типов
│ └── subdomain/ # Поддомен
Лучшие практики TypeScript
Импорты типов
// Использовать только type-импорты
import { type SomeType } from './types';
import type { AnotherType } from '@shared/contracts';
Избегайте any
// Плохо
function handle(data: any) { }
// Хорошо: использовать конкретные типы
function handle(data: Message) { }
// Хорошо: использовать обобщения
function handle<T extends BaseMessage>(data: T) { }
// Хорошо: использовать unknown + защита типа
function handle(data: unknown) {
if (isMessage(data)) { /* data: Message */ }
}
Barrel Export
// components/chat/messages/index.ts
export { MessageItem } from './MessageItem';
export { MessageList } from './MessageList';
export type { MessageItemProps } from './MessageItem';
Порядок организации кода
Структура React-компонента
// 1. Импорты
// 2. Определения типов
// 3. Константы
// 4. Вспомогательные функции
// 5. Основной компонент
// 5.1 Хуки
// 5.2 Производное состояние (useMemo)
// 5.3 Обработчики событий (useCallback)
// 5.4 Эффекты (useEffect)
// 5.5 Рендер (return)
// 6. Дочерние компоненты (если простые)
Принцип единственной ответственности
Каждый файл должен иметь только одну причину для изменения. Компонент следует разбить, если:
- Он превышает 300 строк кода
- Содержит 3 и более независимых логических блока
- Имеет части, пригодные для повторного использования
- Разные части изменяются с разной частотой
- Тестирование становится затруднительным
Стратегии разбиения:
| Стратегия | Когда применять | Пример |
|---|---|---|
| По функции | UI-блоки независимы | Тулбар / список сообщений / поле ввода → отдельные компоненты |
| По потоку данных | Сложная логика состояния | Состояние / Действия / Побочные эффекты → отдельные хуки |
| По домену | Несколько однотипных элементов | EditTool / WriteTool / BashTool → отдельные файлы |
Git-процесс
Соглашение о коммитах
Используйте формат Conventional Commits:
feat: описание новой функции
fix: описание исправления
refactor: описание рефакторинга
docs: обновление документации
style: изменения форматирования (без изменений логики)
test: изменения, связанные с тестами
chore: изменения инструментов сборки или вспомогательных инструментов
Контрольный список перед коммитом
- Запустить
npm run lint— убедиться в отсутствии ошибок - Исправить все проблемы уровня error
- По возможности исправить проблемы уровня warn
- Запустить
npm run format— отформатировать код - Новые файлы должны иметь 0 предупреждений
- При изменении существующих файлов — заодно исправить предупреждения в них