Совместимость парсеров и плагинов

Архитектура линтинга в JavaScript опирается на разделение ответственности между парсером, ядром линтера и плагинами правил. Парсер преобразует исходный код в абстрактное синтаксическое дерево (AST), ядро анализирует структуру и применяет правила, а плагины расширяют набор проверок. Любое нарушение совместимости между этими компонентами приводит к ошибкам анализа, ложным срабатываниям или невозможности выполнения правил.

Роль парсера в экосистеме линтинга

Парсер определяет форму AST, с которой работает вся система правил. В экосистеме ESLint по умолчанию используется парсер Espree, реализующий стандарт ESTree-совместимое дерево.

Ключевые задачи парсера:

  • преобразование JavaScript/TypeScript-кода в AST
  • поддержка современных стандартов ECMAScript
  • формирование информации о токенах и комментариях
  • передача дополнительных данных (например, диапазонов и позиций)

Любой плагин ESLint опирается на структуру AST, поэтому несовместимость на уровне парсера автоматически ломает правила анализа.

ESTree как фундамент совместимости

Большинство парсеров ориентируются на спецификацию ESTree. Это де-факто стандарт описания JavaScript AST.

Основные элементы ESTree:

  • узлы выражений (Expression)
  • узлы инструкций (Statement)
  • декларации (Declaration)
  • модульная структура (Program, ImportDeclaration)

Плагины ESLint предполагают наличие этих узлов. Если парсер вводит собственные расширения без обратной совместимости, правила могут перестать работать.

Основные альтернативные парсеры

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

  • @babel/eslint-parser — поддерживает современные ECMAScript-фичи и нестандартные синтаксические расширения Babel
  • @typescript-eslint/parser — обеспечивает разбор TypeScript-кода и генерацию расширенного AST
  • Acorn (как основа для некоторых кастомных решений)
  • Esprima (исторически использовался до перехода на Espree)

Каждый из этих парсеров может формировать AST с отличиями, которые влияют на совместимость плагинов.

Проблема различий AST между парсерами

Несовместимость чаще всего проявляется не в синтаксисе языка, а в деталях структуры дерева:

  • различие в типах узлов (TSInterfaceDeclaration против отсутствия аналогов в стандартном AST)
  • дополнительные поля (parent, loc, range)
  • разные способы представления JSX или generics
  • отсутствие некоторых узлов в базовом ESTree

Плагины, рассчитанные на один тип AST, могут некорректно работать с другим.

parserOptions и их влияние на совместимость

Конфигурация parserOptions определяет поведение парсера и напрямую влияет на то, какой AST будет сгенерирован.

Основные параметры:

  • ecmaVersion — версия стандарта ECMAScript
  • sourceTypescript или module
  • ecmaFeatures.jsx — включение JSX
  • дополнительные настройки для библиотек (например, проектов TypeScript)

Несовпадение ecmaVersion между парсером и ожиданиями плагина часто приводит к ошибкам разбора или отсутствию нужных узлов.

Плагины и их зависимость от AST

Плагин в ESLint представляет собой набор правил, которые работают с AST. Примеры популярных плагинов:

  • eslint-plugin-react
  • eslint-plugin-import

Каждый плагин предполагает определённую форму дерева и наличие специфических узлов.

Типы зависимостей плагинов от парсера

  1. Структурная зависимость

    • ожидаются конкретные типы узлов
    • пример: JSX-узлы в React-плагинах
  2. Семантическая зависимость

    • анализ контекста (scope, bindings)
    • требует корректной передачи информации от парсера
  3. Type-aware зависимость

    • требует дополнительных сервисов (например, TypeScript Program)
    • используется в @typescript-eslint

parserServices и расширенная совместимость

Некоторые парсеры предоставляют parserServices — дополнительный слой информации поверх AST.

Пример:

  • TypeScript parser предоставляет доступ к TypeScript Program
  • это позволяет выполнять типовой анализ внутри правил

Однако использование parserServices ограничивает совместимость:

  • правила становятся привязанными к конкретному парсеру
  • невозможен перенос между Espree и TypeScript AST без адаптации

Конфликты версий и peerDependencies

Совместимость часто нарушается на уровне зависимостей:

  • плагины требуют определённые версии ESLint
  • парсеры зависят от конкретных версий ESTree или TypeScript
  • несоответствие peerDependencies приводит к runtime-ошибкам

Типичный сценарий конфликта:

  • плагин использует API ESLint нового поколения
  • проект работает на старой версии ESLint
  • результат — отсутствие поддержки правил или падение анализа

JSX и расширенные синтаксисы

JSX является частым источником несовместимости:

  • Espree поддерживает JSX только при включённой опции
  • Babel parser расширяет поддержку JSX-синтаксиса
  • плагины React ожидают корректную JSX-структуру AST

Несовпадение парсера и плагина приводит к тому, что JSX воспринимается как неизвестный синтаксис.

TypeScript как источник несовместимости

Использование @typescript-eslint/parser вводит дополнительные уровни сложности:

  • наличие TS-специфичных узлов
  • необходимость синхронизации версий TypeScript
  • различия между type-only и runtime AST

Плагины, не рассчитанные на TypeScript AST, игнорируют или некорректно обрабатывают узлы.

Babel-совместимость и трансформации AST

@babel/eslint-parser создаёт AST, который может отличаться от стандартного ESTree из-за:

  • транспиляции новых синтаксических конструкций
  • введения вспомогательных узлов
  • изменения структуры import/export

Некоторые плагины требуют нормализации AST перед анализом.

Стратегии обеспечения совместимости

В практике разработки используются несколько подходов:

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

Flat config и влияние на совместимость

Новая модель конфигурации ESLint (flat config) усиливает явное управление парсером на уровне каждого набора файлов. Это уменьшает скрытые конфликты, но делает зависимости более явными:

  • парсер задаётся локально для конкретного блока конфигурации
  • плагины привязываются к конкретному контексту выполнения
  • различия AST становятся более предсказуемыми

Итоговые классы несовместимости

Совместимость парсеров и плагинов можно разделить на несколько категорий:

  • синтаксическая несовместимость (неподдерживаемый язык)
  • структурная несовместимость AST
  • API-несовместимость ESLint и плагинов
  • зависимостная несовместимость версий
  • семантическая несовместимость анализа

Каждый класс требует отдельного подхода на уровне конфигурации и выбора инструментов, поскольку единая модель AST является лишь теоретической основой, но на практике реализуется разными парсерами с различной степенью отклонений.