$ sudo teach IT

Модуль 12.1

ESLint и Prettier с TypeScript

Профессиональный коддинг: автоматизация качества кода

Введение

Добро пожаловать в урок по инструментам автоматизации качества кода! Сегодня мы разберём два essential инструмента, которые обязан использовать каждый TypeScript-разработчик — ESLint и Prettier.

Представьте, что вы работаете в команде из 10 человек. Каждый пишет код по-своему: кто-то использует табуляцию, кто-то пробелы; кто-то ставит точку с запятой, кто-то нет; кто-то именует переменные через camelCase, а кто-то через snake_case. Без единого стандарта кодовая база превращается в хаос.

ESLint и Prettier решают эту проблему автоматически. ESLint находит ошибки и потенциальные проблемы в коде, а Prettier форматирует его по единым правилам. Вместе они обеспечивают консистентность, читаемость и надёжность вашего кода.

1. Что такое ESLint

1.1 История и назначение

ESLint появился в 2013 году как ответ на limitations существовавших тогда линтеров — JSHint и JSLint. Его создатель Николас К. Закас (Nicholas C. Zakas) хотел создать инструмент, который был бы:

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

1.2 Архитектура ESLint

Внутренняя архитектура ESLint состоит из нескольких ключевых компонентов:

Компоненты архитектуры:

  • Parser — преобразует исходный код в AST (Abstract Syntax Tree)
  • AST Traverser — обходит дерево синтаксического анализа
  • Rule System — набор правил для проверки AST узлов
  • Reporter — форматирует и выводит результаты
  • Config System — управление конфигурациями и наследованием
// AST (Abstract Syntax Tree) — дерево синтаксического анализа
// Исходный код: const x: number = 42;

// AST узлы:
{
  type: "VariableDeclaration",
  kind: "const",
  declarations: [{
    type: "VariableDeclarator",
    id: { type: "Identifier", name: "x", typeAnnotation: { type: "TSTypeAnnotation", typeAnnotation: { type: "TSNumberKeyword" } } },
    init: { type: "NumericLiteral", value: 42 }
  }]
}

// ESLint обходит каждый узел и проверяет правила

1.3 Типы правил ESLint

ESLint предоставляет три категории правил:

⚠️ Предупреждения

Потенциальные проблемы

no-unused-vars, no-console

🚫 Ошибки

Критические проблемы

no-undef, semi

✅ Информация

Стилистические замечания

no-trailing-spaces

2. Установка и настройка ESLint

2.1 Установка в TypeScript проекте

# Установка ESLint и необходимых пакетов
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

# Или с использованием yarn
yarn add --dev eslint @typescript-eslint/parser @typescript-eslint/eslintplugin

# Для ESLint 9.x (flat config) — рекомендуемый способ
npm install --save-dev eslint typescript-eslint @eslint/js

# Автоматическая настройка через CLI
npx eslint --init

2.2 Flat Config (ESLint 9.x)

Начиная с ESLint 9.x, рекомендуется использовать формат конфигурации flat config (файл eslint.config.js). Это более простой и гибкий формат по сравнению с legacy .eslintrc:

// eslint.config.js — Flat Config формат (ESLint 9.x)
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettierConfig from 'eslint-config-prettier';

export default tseslint.config(
  // Глобальные рекомендации
  eslint.configs.recommended,

  // Рекомендации для TypeScript
  ...tseslint.configs.recommended,

  // Отключение правил, конфликтующих с Prettier
  prettierConfig,

  // Кастомные правила проекта
  {
    rules: {
      '@typescript-eslint/no-unused-vars': ['error', {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
      }],
      '@typescript-eslint/explicit-function-return-type': 'warn',
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/consistent-type-imports': 'error',
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'prefer-const': 'error',
      'no-var': 'error',
    },
    languageOptions: {
      parserOptions: {
        project: './tsconfig.json',
        ecmaVersion: 'latest',
        sourceType: 'module',
      },
    },
  },

  // Игнорируемые файлы
  {
    ignores: [
      'dist/**',
      'node_modules/**',
      'coverage/**',
      '*.config.js',
      '*.config.mjs',
    ],
  }
);

2.3 Legacy Config (.eslintrc)

Для проектов, использующих ESLint 8.x и ниже, конфигурация выглядит так:

// .eslintrc.json — Legacy формат (ESLint 8.x)
{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": "./tsconfig.json",
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "prettier"
  ],
  "rules": {
    "@typescript-eslint/no-unused-vars": ["error", {
      "argsIgnorePattern": "^_",
      "varsIgnorePattern": "^_"
    }],
    "@typescript-eslint/explicit-function-return-type": "warn",
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/consistent-type-imports": "error",
    "no-console": ["warn", { "allow": ["warn", "error"] }],
    "prefer-const": "error",
    "no-var": "error",
    "eqeqeq": ["error", "always"],
    "no-duplicate-imports": "error"
  },
  "ignorePatterns": ["dist/**", "node_modules/**", "coverage/**"],
  "env": {
    "browser": true,
    "node": true,
    "es2022": true
  }
}

2.4 Важные правила для TypeScript

Правило Описание Рекомендуемое значение
@typescript-eslint/no-explicit-any Запрещает использование any "warn" или "error"
@typescript-eslint/no-unused-vars Запрещает неиспользуемые переменные "error" с паттернами
@typescript-eslint/explicit-function-return-type Требует явный возвращаемый тип "warn"
@typescript-eslint/consistent-type-imports Единый стиль импорта типов "error"
@typescript-eslint/no-non-null-assertion Запрещает ! assertion "warn"
@typescript-eslint/prefer-nullish-coalescing Предпочитает ?? вместо || "warn"
@typescript-eslint/prefer-optional-chain Предпочитает ?. вместо && "error"

3. Плагины ESLint для TypeScript

3.1 @typescript-eslint — официальная поддержка

Пакет @typescript-eslint состоит из двух основных компонентов:

// @typescript-eslint/parser — парсер TypeScript для ESLint
// Преобразует TypeScript код в AST, понятный ESLint

// @typescript-eslint/eslintplugin — правила для TypeScript
// Содержит hundreds правил, специфичных для TypeScript

// Пример использования расширенных правил:
{
  "extends": [
    "plugin:@typescript-eslint/recommended",                    // Базовые правила
    "plugin:@typescript-eslint/recommended-requiring-type-checking" // Правила, требующие информацию о типах
  ],
  "rules": {
    // Правила для типизации
    "@typescript-eslint/ban-types": "error",
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/explicit-module-boundary-types": "warn",
    "@typescript-eslint/no-inferrable-types": "error",

    // Правила для производительности
    "@typescript-eslint/prefer-includes": "error",
    "@typescript-eslint/prefer-string-starts-ends-with": "error",
    "@typescript-eslint/prefer-for-of": "warn",

    // Правила для читаемости
    "@typescript-eslint/naming-convention": ["error",
      { "selector": "default", "format": ["camelCase"] },
      { "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
      { "selector": "parameter", "format": ["camelCase"], "leadingUnderscore": "allow" },
      { "selector": "typeLike", "format": ["PascalCase"] },
      { "selector": "enumMember", "format": ["PascalCase"] }
    ]
  }
}

3.2 Кастомные правила

Вы можете создавать собственные правила ESLint для специфических потребностей вашего проекта:

// eslint-plugin-custom/rules/no-console-log.js
// Кастомное правило: запрет console.log (но разрешает console.warn/error)

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'Запрещает использование console.log',
      category: 'Best Practices',
      recommended: true,
    },
    fixable: null,
    schema: [],
    messages: {
      noConsoleLog: 'Используйте logger вместо console.log',
    },
  },

  create(context) {
    return {
      CallExpression(node) {
        const { callee } = node;

        if (
          callee.type === 'MemberExpression' &&
          callee.object.type === 'Identifier' &&
          callee.object.name === 'console' &&
          callee.property.type === 'Identifier' &&
          callee.property.name === 'log'
        ) {
          context.report({
            node,
            messageId: 'noConsoleLog',
          });
        }
      },
    };
  },
};

// Использование в конфиге:
// "custom/no-console-log": "error"

3.3 Популярные плагины

eslint-plugin-import

Проверка импортов, обнаружение дубликатов, сортировка импортов

eslint-plugin-react

Правила для React компонентов и JSX

eslint-plugin-jest

Правила для тестов Jest

eslint-plugin-jsx-a11y

Проверка доступности JSX

eslint-plugin-unicorn

Множество полезных правил для современного JS/TS

eslint-plugin-functional

Функциональный стиль программирования

4. Интеграция ESLint с IDE

4.1 Настройка VS Code

// .vscode/settings.json — Настройки проекта
{
  // Автоматическое исправление при сохранении
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit",
    "source.organizeImports": "explicit"
  },

  // Показ ошибок ESLint
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],

  // Форматирование по умолчанию
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,

  // Отключение встроенного линтера TypeScript
  "typescript.tsserver.enableJavascript": true
}

// .vscode/extensions.json — Рекомендуемые расширения
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "bradlc.vscode-tailwindcss",
    "csstools.postcss"
  ]
}

4.2 Настройка JetBrains IDE (WebStorm, IntelliJ)

В JetBrains IDE интеграция ESLint встроена и не требует дополнительных расширений:

  1. Откройте Settings → Languages & Frameworks → JavaScript → Code Quality Tools → ESLint
  2. Выберите "Automatic ESLint configuration"
  3. Укажите путь к конфигурационному файлу
  4. Включите "Fix ESLint errors on save"
  5. Настройте "Run eslint --fix on save" для автоматического исправления

4.3 Git Hooks с Husky и lint-staged

Автоматическая проверка кода перед коммитом — essential практика:

# Установка Husky и lint-staged
npm install --save-dev husky lint-staged

# Инициализация Husky
npx husky init

# Создание pre-commit хука
echo "npx lint-staged" > .husky/pre-commit

// package.json — Конфигурация lint-staged
{
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix --max-warnings=0",
      "prettier --write",
      "bash -c 'tsc --noEmit'"
    ],
    "*.{js,jsx}": [
      "eslint --fix --max-warnings=0",
      "prettier --write"
    ],
    "*.{json,md,yml,yaml,css,scss}": [
      "prettier --write"
    ]
  },
  "scripts": {
    "lint": "eslint . --ext .ts,.tsx,.js,.jsx",
    "lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
    "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,md}\"",
    "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,md}\"",
    "typecheck": "tsc --noEmit",
    "validate": "npm run typecheck && npm run lint && npm run format:check"
  }
}

5. Что такое Prettier

5.1 Назначение и философия

Prettier — это "opinionated" код-форматтер (форматер с жёсткими мнениями). Его философия проста: все форматирующие решения уже приняты за вас. Вы не можете настроить Prettier так, чтобы он форматировал код так же, как вы пишете — вместо этого вы приспосабливаетесь к его стилю.

Преимущества такого подхода:
  • Нулевая настройка — работает "из коробки"
  • Единый стиль для всей команды
  • Больше времени на написание кода, меньше на обсуждение стиля
  • Поддерживает множество языков (JS, TS, CSS, HTML, JSON, MD и др.)

5.2 Отличия от ESLint

ESLint

  • Находит ошибки и потенциальные проблемы
  • Проверяет логику кода
  • Настраиваемые правила
  • Фокус на качестве кода
  • Может исправлять (fixable правила)

Prettier

  • Форматирует код по единым правилам
  • Не анализирует логику
  • Минимальная настройка
  • Фокус на стиле и читаемости
  • Всегда исправляет (переформатирует)

5.3 Что форматирует Prettier

// ДО форматирования Prettier:
function    calculateTotal( items:  Array<  Product >   ):  number  {
return    items.reduce((   sum,    item   )   =>   {   return  sum  +  item.price  *  item.quantity;   },    0  )
}

interface    User   {  id:    number ;  name: string;    email:string;    role: 'admin' | 'user' | 'moderator'   }

const    users   =  [  { id:1,  name:'Alice',   email:'alice@example.com',   role:'admin'  },    {    id:2,   name:'Bob',    email:'bob@example.com',    role:'user'   }  ]


// ПОСЛЕ форматирования Prettier:
function calculateTotal(items: Array): number {
  return items.reduce((sum, item) => {
    return sum + item.price * item.quantity;
  }, 0);
}

interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'moderator';
}

const users = [
  { id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' },
  { id: 2, name: 'Bob', email: 'bob@example.com', role: 'user' },
];

6. Настройка Prettier с TypeScript

6.1 Установка

# Установка Prettier
npm install --save-dev prettier

# Дополнительные плагины
npm install --save-dev prettier-plugin-organize-imports  # Сортировка импортов
npm install --save-dev prettier-plugin-tailwindcss       # Сортировка Tailwind классов

6.2 Конфигурация .prettierrc

// .prettierrc — Конфигурация Prettier
{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "lf",
  "quoteProps": "as-needed",
  "jsxSingleQuote": false,
  "htmlWhitespaceSensitivity": "css",
  "embeddedLanguageFormatting": "auto",
  "singleAttributePerLine": false,
  "overrides": [
    {
      "files": "*.ts",
      "options": {
        "printWidth": 120,
        "trailingComma": "all"
      }
    },
    {
      "files": ["*.json", "*.yml", "*.yaml"],
      "options": {
        "singleQuote": false
      }
    },
    {
      "files": "*.md",
      "options": {
        "printWidth": 80,
        "proseWrap": "always"
      }
    }
  ]
}

// .prettierignore — Игнорируемые файлы
node_modules
dist
build
coverage
*.min.js
*.min.css
package-lock.json
yarn.lock
pnpm-lock.yaml

6.3 CLI команды Prettier

# Форматирование всех файлов
npx prettier --write .

# Форматирование конкретных файлов
npx prettier --write "src/**/*.ts"

# Проверка без форматирования (для CI/CD)
npx prettier --check .

# Форматирование одного файла
npx prettier --write src/index.ts

# Форматирование с выводом в stdout
npx prettier --parser typescript src/index.ts

# Использование конфигурации проекта
npx prettier --config .prettierrc --write .

7. Совместная работа ESLint и Prettier

7.1 Почему возникают конфликты

Проблема в том, что и ESLint, и Prettier могут пытаться форматировать один и тот же код. Например, ESLint может требовать точку с запятой, а Prettier — убирать её. Это создаёт конфликт и " fight" между инструментами.

7.2 Решение: eslint-config-prettier

# Установка
npm install --save-dev eslint-config-prettier

// ESLint конфигурация — подключение prettier
// Flat Config (ESLint 9.x):
import prettierConfig from 'eslint-config-prettier';
export default [
  ...tseslint.configs.recommended,
  prettierConfig, // Должен быть ПОСЛЕ других extends!
  { /* ваши правила */ }
];

// Legacy Config (.eslintrc):
{
  "extends": [
    "plugin:@typescript-eslint/recommended",
    "prettier"  // Должен быть ПОСЛЕ других extends!
  ]
}

// eslint-config-prettier автоматически отключает все правила ESLint,
// которые конфликтуют с Prettier:
// - indent
// - semi
// - quotes
// - comma-dangle
// - no-unexpected-multiline
// ... и многие другие

7.3 eslint-plugin-prettier

Этот плагин запускает Prettier как правило ESLint, что позволяет видеть ошибки форматирования прямо в IDE:

# Установка
npm install --save-dev eslint-plugin-prettier

// Конфигурация с eslint-plugin-prettier
{
  "extends": [
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": ["error", {
      "singleQuote": true,
      "trailingComma": "all",
      "printWidth": 100,
      "tabWidth": 2
    }]
  }
}

// Или использование рекомендованного конфига:
{
  "extends": [
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended"  // Включает prettier и prettier/prettier
  ]
}

7.4 Рекомендуемый workflow

  1. IDE → форматирует при сохранении (Prettier через editor integration)
  2. Git hook (pre-commit) → запускает lint-staged: ESLint --fix + Prettier --write
  3. CI/CD → проверяет: ESLint (lint) + Prettier (format:check) + TypeScript (typecheck)
  4. Code Review → нет споров о стиле — всё автоматизировано

8. Автоматическое форматирование при сохранении

8.1 Настройка в VS Code

// .vscode/settings.json
{
  // Форматирование при сохранении
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",

  // Автоисправление ESLint при сохранении
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit",
    "source.fixAll.prettier": "explicit",
    "source.organizeImports": "explicit"
  },

  // Приоритет форматеров
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[markdown]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },

  // Настройки редактора для консистентности с Prettier
  "editor.tabSize": 2,
  "editor.insertSpaces": true,
  "editor.wordWrap": "on"
}

8.2 Автоформатирование в JetBrains IDE

Настройка в WebStorm/IntelliJ IDEA:

  1. Settings → Languages & Frameworks → JavaScript → Prettier
  2. Включить "On code reformat" и "On save"
  3. Settings → Tools → File Watchers → добавить Prettier
  4. Settings → Editor → Code Style → Prettier
  5. Настроить "Run on Save" для автоматического форматирования

9. Расширенные техники

9.1 Shared Configurations

Для монорепозиториев и больших команд удобно создавать shared конфигурации:

// packages/eslint-config-company/index.js
// Общая конфигурация для всех проектов компании

const prettierConfig = require('./prettier-config');

module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint', 'import', 'jsx-a11y'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:import/errors',
    'plugin:import/warnings',
    'plugin:import/typescript',
    'plugin:jsx-a11y/recommended',
    'prettier',
  ],
  rules: {
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/consistent-type-imports': 'error',
    'import/order': ['error', {
      groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
      'newlines-between': 'always',
    }],
    'no-console': ['warn', { allow: ['warn', 'error'] }],
  },
  settings: {
    'import/resolver': {
      typescript: {
        alwaysTryTypes: true,
      },
    },
  },
};

// Использование в проекте:
// npm install --save-dev eslint-config-company
// extends: ["company"]

9.2 Кастомные Prettier плагины

// prettier-plugin-custom-sort/index.js
// Кастомный плагин для сортировки импортов

module.exports = {
  parsers: {
    typescript: {
      ...require('prettier/parser-typescript').parsers.typescript,
      preprocess: (text, options) => {
        // Сортировка импортов перед форматированием
        const lines = text.split('\n');
        const imports = [];
        const rest = [];

        lines.forEach(line => {
          if (line.startsWith('import ')) {
            imports.push(line);
          } else {
            rest.push(line);
          }
        });

        // Сортировка импортов
        const sortedImports = imports.sort((a, b) => {
          const aIsType = a.includes('type ');
          const bIsType = b.includes('type ');
          if (aIsType && !bIsType) return 1;
          if (!aIsType && bIsType) return -1;
          return a.localeCompare(b);
        });

        return [...sortedImports, '', ...rest].join('\n');
      },
    },
  },
};

9.3 Проверка в CI/CD

# .github/workflows/ci.yml — GitHub Actions
name: CI

on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: TypeScript check
        run: npm run typecheck

      - name: ESLint
        run: npm run lint

      - name: Prettier check
        run: npm run format:check

      - name: Tests
        run: npm test -- --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info

9.4 Монорепозиторий с Turborepo

// turbo.json — Конфигурация Turborepo
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "typecheck": {
      "dependsOn": ["^typecheck"],
      "outputs": []
    },
    "lint": {
      "outputs": []
    },
    "format": {
      "outputs": []
    },
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    }
  }
}

// Корневой package.json
{
  "scripts": {
    "typecheck": "turbo run typecheck",
    "lint": "turbo run lint",
    "format": "turbo run format",
    "format:check": "turbo run format:check",
    "build": "turbo run build",
    "test": "turbo run test",
    "validate": "turbo run typecheck lint format:check test"
  }
}

10. Практические примеры

10.1 Полная конфигурация проекта

// package.json — полная конфигурация
{
  "name": "my-typescript-project",
  "version": "1.0.0",
  "scripts": {
    "dev": "ts-node-dev --respawn src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "lint": "eslint . --ext .ts,.tsx --max-warnings=0",
    "lint:fix": "eslint . --ext .ts,.tsx --fix",
    "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
    "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"",
    "typecheck": "tsc --noEmit",
    "validate": "npm run typecheck && npm run lint && npm run format:check",
    "prepare": "husky"
  },
  "lint-staged": {
    "src/**/*.{ts,tsx}": [
      "eslint --fix --max-warnings=0",
      "prettier --write"
    ],
    "src/**/*.{json,md}": [
      "prettier --write"
    ]
  },
  "devDependencies": {
    "@eslint/js": "^9.0.0",
    "@typescript-eslint/eslint-plugin": "^8.0.0",
    "@typescript-eslint/parser": "^8.0.0",
    "eslint": "^9.0.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-plugin-prettier": "^5.0.0",
    "husky": "^9.0.0",
    "lint-staged": "^15.0.0",
    "prettier": "^3.0.0",
    "typescript": "^5.0.0",
    "typescript-eslint": "^8.0.0"
  }
}

10.2 Пример кода: до и после

// ДО (плохой код — много проблем):
import   { useState, useEffect, useCallback } from 'react';
import   { User } from '../types'
import axios from 'axios'
import   { API_URL } from '../config'
import   { formatDate } from '../utils'

interface Props {
  userId: string;
  onUpdate?: (user: User) => void
}

export const UserProfile = ({ userId, onUpdate } :Props) => {
  const [user, setUser] = useState  (null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    async function fetchUser() {
      try {
        const response = await axios.get(API_URL + '/users/' + userId)
        setUser(response.data)
        if (onUpdate) {
          onUpdate(response.data)
        }
      } catch (error) {
        console.log('Error:', error)
      } finally {
        setLoading(false)
      }
    }
    fetchUser()
  }, [])

  if (loading) return <div>Loading...</div>
  if (!user) return <div>Not found</div>

  return (
    <div className="profile">
      <h1>{user.name}</h1>
      <p>{formatDate(user.createdAt)}</p>
      <p>{user.email}</p>
    </div>
  )
}
// ПОСЛЕ (хороший код — исправлено ESLint + Prettier):
import { useState, useEffect, useCallback } from 'react';
import axios from 'axios';

import { API_URL } from '../config';
import type { User } from '../types';
import { formatDate } from '../utils';

interface Props {
  userId: string;
  onUpdate?: (user: User) => void;
}

export const UserProfile = ({ userId, onUpdate }: Props) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState<boolean>(true);

  const fetchUser = useCallback(async () => {
    try {
      const response = await axios.get<User>(`${API_URL}/users/${userId}`);
      setUser(response.data);
      onUpdate?.(response.data);
    } catch (error) {
      console.error('Failed to fetch user:', error);
    } finally {
      setLoading(false);
    }
  }, [userId, onUpdate]);

  useEffect(() => {
    void fetchUser();
  }, [fetchUser]);

  if (loading) {
    return <div>Loading...</div>;
  }

  if (!user) {
    return <div>Not found</div>;
  }

  return (
    <div className="profile">
      <h1>{user.name}</h1>
      <p>{formatDate(user.createdAt)}</p>
      <p>{user.email}</p>
    </div>
  );
};

11. Лучшие практики

Делайте

  • Включите strict mode в tsconfig
  • Используйте eslint-config-prettier
  • Добавьте husky + lint-staged
  • Используйте --max-warnings=0 в CI
  • Настраивайте правила под проект
  • Используйте shared configs в монорепо
  • Регулярно обновляйте зависимости

Не делайте

  • Не конфликтуйте ESLint с Prettier
  • Не игнорируйте warnings без причины
  • Не используйте "eslint-disable" без комментария
  • Не дублируйте правила в разных конфигах
  • Не забывайте обновлять .eslintignore
  • Не пропускайте typecheck в CI
  • Не коммитьте без проверки

11.1 Правила для команд

  1. Один конфиг на проект — храните конфигурацию в репозитории, не в IDE
  2. Не отключайте правила без PR — любое изменение правил должно проходить code review
  3. Используйте shared configs — для единообразия между проектами
  4. Автоматизируйте всё — hooks, CI, форматирование при сохранении
  5. Документируйте правила — если правило нестандартное, объясните почему
  6. Измеряйте прогресс — количество warnings/errors должно уменьшаться

12. Чек-лист настройки

Чек-лист: настройка ESLint + Prettier для TypeScript проекта

☐ 1. Установить: eslint, @typescript-eslint/parser, @typescript-eslint/eslintplugin
☐ 2. Установить: prettier, eslint-config-prettier, eslint-plugin-prettier
☐ 3. Установить: husky, lint-staged
☐ 4. Создать eslint.config.js (или .eslintrc.json)
☐ 5. Создать .prettierrc с настройками проекта
☐ 6. Создать .eslintignore и .prettierignore
☐ 7. Настроить VS Code settings.json
☐ 8. Настроить husky pre-commit хук
☐ 9. Настроить lint-staged в package.json
☐ 10. Добавить npm scripts: lint, lint:fix, format, format:check
☐ 11. Настроить CI/CD pipeline
☐ 12. Добавить расширения в .vscode/extensions.json
☐ 13. Проверить, что всё работает: npm run validate
☐ 14. Задокументировать правила в README.md

Итоги урока

В этом уроке мы подробно изучили два essential инструмента для TypeScript-разработки:

ESLint

  • Статический анализ кода
  • Обнаружение ошибок и проблем
  • Кастомные правила и плагины
  • Интеграция с TypeScript через @typescript-eslint

Prettier

  • Автоматическое форматирование
  • Единый стиль для команды
  • Минимальная настройка
  • Поддержка множества языков

Запомните: качество кода — это не роскошь, а необходимость. Инвестиции в настройку инструментов автоматизации окупаются многократно за счёт уменьшения времени на code review, исправление багов и адаптацию нового кода.

В следующем уроке мы перейдём к тестированию TypeScript кода с помощью Jest — ещё одного essential инструмента для профессиональной разработки.

ESLint и Prettier с TypeScript

10 вопросов

Настройка ESLint и Prettier для TypeScript

Premium