Использование информации о типах в правилах

Современные инструменты статического анализа, построенные вокруг ESLint, давно вышли за пределы синтаксических проверок. Появление типизированного JavaScript через TypeScript привело к тому, что правила линтинга получили доступ к семантической информации: типам выражений, сигнатурам функций, структуре модулей и выводам компилятора.

Использование типовой информации внутри правил меняет саму модель анализа: вместо проверки «текста кода» происходит проверка «модели программы», построенной TypeScript-компилятором.


Архитектура доступа к типам в ESLint-правилах

Типовая информация становится доступной только в том случае, если ESLint работает через парсер, который умеет взаимодействовать с TypeScript Compiler API. На практике это достигается через @typescript-eslint/parser.

Ключевой механизм — parserServices, который передаёт в правило:

  • program — экземпляр TypeScript Program
  • esTreeNodeToTSNodeMap — соответствие между ESTree-узлами и TypeScript-узлами
  • tsNodeToESTreeNodeMap — обратное соответствие
  • getTypeAtLocation(node) — получение типа выражения

Именно через program правила получают доступ к TypeChecker, который формирует точные типы.


Получение типа узла AST

Основная точка входа — метод getTypeAtLocation.

Типовая схема работы:

  1. ESLint-парсер строит AST (ESTree)
  2. TypeScript строит собственное дерево (TS AST)
  3. Связка между деревьями позволяет сопоставлять узлы
  4. TypeChecker вычисляет тип

Пример логики внутри правила:

const type = services.program.getTypeChecker().getTypeAtLocation(tsNode);

После получения типа правило может анализировать:

  • базовый тип (string, number, boolean)
  • union и intersection типы
  • литеральные типы
  • generics
  • type guards и narrowing

Практическое применение типовой информации

Проверка небезопасных операций

Типы позволяют обнаруживать ошибки, которые невозможны в чистом JavaScript-анализе.

Например, обращение к свойству, отсутствующему в типе:

  • объект имеет тип User
  • правило проверяет доступ к user.password
  • TypeChecker подтверждает отсутствие поля

Такой анализ устойчив к переименованиям и рефакторингу.


Различение перегруженных API

Функции с перегрузками или union-типами невозможно корректно анализировать без типов.

Пример:

  • функция принимает string | number
  • поведение зависит от входного типа

Правило может ветвить логику:

  • если тип string → проверка строки
  • если тип number → числовая логика

Анализ шаблонов типов

Типы позволяют распознавать структуры:

  • Promise<T>
  • Array<T>
  • Record<K, V>

Это даёт возможность писать правила, ориентированные на семантику:

  • запрет Promise без await
  • проверка корректности Array.map callback return type
  • контроль ключей в Record

Работа с TypeChecker

TypeChecker — центральный объект TypeScript API. Через него правила получают:

  • символы (getSymbolAtLocation)
  • сигнатуры функций
  • наследование интерфейсов
  • декларации типов

Пример анализа функции:

  • получить символ функции
  • извлечь сигнатуру
  • проверить аргументы и возвращаемый тип

Это позволяет реализовать сложные правила, например:

  • проверка совместимости API
  • контроль контрактов функций
  • анализ публичных интерфейсов модулей

Ограничения доступа к типам

Типовая информация доступна только при включённой проектной конфигурации:

{
  "parserOptions": {
    "project": "./tsconfig.json"
  }
}

Без этого TypeScript не строит полноценный Program, и parserServices.program отсутствует.

Ограничения:

  • увеличенное время анализа
  • необходимость полной сборки проекта
  • невозможность использования в «быстром режиме» линтинга отдельных файлов

Разделение синтаксического и типового анализа

Правила ESLint делятся на два класса:

Синтаксические правила

Работают без TypeScript:

  • анализ структуры AST
  • проверка стиля
  • поиск паттернов

Они:

  • быстрые
  • не зависят от проекта
  • не знают типов

Типо-зависимые правила

Используют parserServices:

  • требуют TypeScript Program
  • выполняют семантический анализ
  • могут быть медленнее

Они:

  • точнее
  • устойчивее к рефакторингу
  • способны обнаруживать скрытые ошибки

Проверка совместимости типов

Одна из ключевых возможностей — проверка assignability.

Через TypeChecker:

  • определяется тип левого выражения
  • определяется тип правого выражения
  • выполняется проверка совместимости

Если типы несовместимы, правило фиксирует нарушение.

Примеры сценариев:

  • присваивание string в number
  • передача объекта с лишними полями
  • несоответствие интерфейсу

Использование type predicates и narrowing

TypeScript поддерживает narrowing через:

  • typeof
  • instanceof
  • пользовательские type guards

Правила могут анализировать, корректно ли выполнено сужение типа:

  • проверка отсутствия обязательных guards
  • обнаружение недостижимых веток
  • анализ условий, которые всегда true/false из-за типов

Это позволяет находить:

  • мёртвый код
  • логические противоречия
  • избыточные проверки

Работа с generics

Generics представляют отдельную сложность: типы становятся параметризованными.

Пример анализа:

  • функция identity<T>(value: T): T
  • правило проверяет, как используется T
  • извлекается constraint или inferred type

Возможные проверки:

  • потеря типизации (any вместо T)
  • некорректные ограничения generic-параметров
  • нарушение инвариантности типов

Типы как источник документации

TypeScript-типы часто выступают формальной спецификацией API.

Правила ESLint могут использовать это для:

  • проверки соответствия реализации интерфейсу
  • контроля публичных экспортов
  • анализа стабильности API

Если интерфейс описывает контракт, правило сравнивает:

  • декларацию
  • фактическую реализацию

Кэширование и производительность

Работа с типами требует значительных ресурсов, поэтому важна оптимизация:

  • кэширование результатов getTypeAtLocation
  • повторное использование TypeChecker
  • минимизация вызовов анализа внутри циклов AST

Типичные ошибки реализации правил:

  • вызов TypeChecker в каждом узле без кэша
  • пересоздание Program
  • глубокий рекурсивный анализ без мемоизации

Расширенные сценарии анализа

Межмодульный анализ

TypeScript Program позволяет учитывать импортированные модули:

  • типы из зависимостей
  • глобальные декларации
  • расширение типов через module augmentation

Правила могут анализировать:

  • соответствие API библиотек
  • корректность использования сторонних типов

Анализ утечек any

Тип any является точкой потери информации.

Правила могут:

  • отслеживать происхождение any
  • проверять распространение через цепочки вызовов
  • выявлять неявные преобразования типов

Инференс сложных выражений

TypeChecker способен вычислять тип сложных выражений:

  • условные типы
  • mapped types
  • template literal types

Правила могут использовать это для:

  • анализа строковых шаблонов
  • проверки ключей объектов
  • валидации динамических структур

Интеграция с пользовательскими правилами

Типовая информация особенно полезна в кастомных правилах.

Типовой шаблон:

  • получение context.parserServices
  • проверка наличия program
  • получение checker
  • анализ узлов через getTypeAtLocation

Такая структура позволяет создавать правила, которые:

  • зависят от архитектуры проекта
  • учитывают бизнес-логику типов
  • работают поверх всей системы типов

Семантическая строгость анализа

Главное отличие типо-ориентированных правил — переход от поверхностной проверки к семантической модели:

  • синтаксис → структура кода
  • типы → смысл программы

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