Адаптеры для старых конфигураций

С переходом ESLint к новой системе плоских конфигураций возникла необходимость сохранить обратную совместимость с огромным количеством проектов, использующих классический формат .eslintrc. Адаптеры для старых конфигураций решают задачу плавной миграции, позволяя использовать существующие правила, плагины и пресеты без полного переписывания конфигурации.

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

  • .eslintrc.json
  • .eslintrc.js
  • .eslintrc.yaml
  • package.json (eslintConfig)

Эта система поддерживала:

  • каскадное наследование конфигов;
  • extends для базовых конфигураций;
  • overrides для частичных переопределений;
  • автоматическое разрешение плагинов через строки вида plugin:react/recommended.

Новая модель (Flat Config) изменила фундамент:

  • отсутствует extends в прежнем виде;
  • конфигурация — это массив объектов;
  • плагины импортируются явно;
  • порядок применения правил строго линейный.

Из-за этого старые конфигурации невозможно использовать напрямую без промежуточного слоя преобразования.

FlatCompat как основной адаптер

Ключевой механизм обратной совместимости реализован в пакете @eslint/eslintrc через утилиту FlatCompat.

FlatCompat преобразует legacy-конфигурации в flat-структуру, имитируя поведение старой системы.

Базовая инициализация

import { FlatCompat } from "@eslint/eslintrc";
import path from "path";
import { fileURLToPath } from "url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const compat = new FlatCompat({
  baseDirectory: __dirname
});

Параметр baseDirectory критически важен:

  • используется для резолва extends;
  • определяет контекст поиска плагинов;
  • влияет на разрешение конфигурационных пакетов.

Преобразование legacy-конфигураций

FlatCompat предоставляет методы, которые эмулируют старую систему расширений.

Использование extends

export default [
  ...compat.extends("eslint:recommended"),
  ...compat.extends("plugin:react/recommended")
];

Каждый вызов:

  • разбирает строку plugin:...;
  • загружает соответствующий конфиг;
  • преобразует его в flat-формат;
  • возвращает массив конфигурационных объектов.

Подключение конфигов из пакетов

export default [
  ...compat.extends("airbnb-base"),
  ...compat.extends("plugin:@typescript-eslint/recommended")
];

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

  • находит пакет в node_modules;
  • извлекает его .eslintrc-структуру;
  • конвертирует правила и плагины.

Работа с плагинами

В legacy-системе плагины подключались строками:

{
  "plugins": ["react"]
}

FlatCompat преобразует это в явные импорты:

import react from "eslint-plugin-react";

export default [
  ...compat.plugins("react")
];

При этом важно понимать:

  • плагины не подгружаются автоматически в flat config;
  • каждый плагин должен быть явно разрешён через адаптер или импортирован вручную.

Механизм преобразования правил

FlatCompat выполняет трансформацию правил в несколько этапов:

  1. Разбор legacy-конфига

  2. Нормализация структуры (extends, plugins, rules)

  3. Преобразование в flat-объекты:

    • files
    • ignores
    • languageOptions
    • rules
  4. Слияние в итоговый массив конфигураций

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

{
  files: ["**/*.js"],
  rules: {
    "no-console": "warn"
  }
}

Ограничения адаптера

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

1. Частичная эмуляция extends

extends в legacy-системе поддерживал сложное дерево наследования. FlatCompat:

  • распаковывает конфигурации в линейный массив;
  • теряет семантику “родительских” конфигов;
  • не сохраняет структуру зависимостей.

2. Неявные зависимости плагинов

Старые конфиги могли ссылаться на плагины строками без явного импорта. В flat-модели это приводит к необходимости:

  • ручного контроля импортов;
  • явного управления версиями плагинов.

3. Ограниченная поддержка динамических конфигов

Некоторые legacy-конфигурации экспортировали функции:

module.exports = (env) => ({
  rules: {
    "no-debugger": env.production ? "error" : "off"
  }
});

FlatCompat может частично интерпретировать такие случаи, но:

  • сложная логика условий не всегда корректно переносится;
  • поведение может отличаться от оригинала.

Комбинирование flat-конфига и адаптера

На практике адаптер используется как слой совместимости внутри нового формата:

import { FlatCompat } from "@eslint/eslintrc";
import path from "path";
import { fileURLToPath } from "url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const compat = new FlatCompat({ baseDirectory: __dirname });

export default [
  {
    ignores: ["dist/**"]
  },

  ...compat.extends("eslint:recommended"),
  ...compat.extends("plugin:react/recommended"),

  {
    files: ["**/*.js"],
    rules: {
      "no-console": "warn"
    }
  }
];

Такой подход позволяет:

  • сохранить старые конфигурации;
  • постепенно внедрять flat-структуру;
  • изолировать legacy-часть от новой логики.

Обработка overrides

Legacy overrides представлял собой массив условных конфигураций:

{
  "overrides": [
    {
      "files": ["*.test.js"],
      "rules": {
        "no-unused-expressions": "off"
      }
    }
  ]
}

FlatCompat преобразует это в отдельные объекты:

export default [
  ...compat.extends("eslint:recommended"),

  {
    files: ["*.test.js"],
    rules: {
      "no-unused-expressions": "off"
    }
  }
];

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

Поддержка TypeScript-конфигов

При использовании @typescript-eslint адаптер учитывает специфику:

export default [
  ...compat.extends("plugin:@typescript-eslint/recommended")
];

Преобразование включает:

  • подключение parser’а;
  • настройку parserOptions;
  • перенос TypeScript-специфичных правил.

Однако сложные комбинации:

  • project-based linting;
  • multiple tsconfig references;

могут требовать ручной донастройки после адаптации.

Проблемы порядка применения конфигураций

Flat model строго линейна, поэтому порядок имеет решающее значение:

export default [
  ...compat.extends("plugin:react/recommended"),
  {
    rules: {
      "react/prop-types": "off"
    }
  }
];

Если изменить порядок:

  • правила из extends могут переопределить локальные настройки;
  • поведение линтера станет отличаться от legacy-модели.

Роль адаптеров в миграции проектов

Адаптеры выполняют промежуточную функцию:

  • позволяют запускать линтер без переписывания конфигурации;
  • обеспечивают постепенный переход;
  • сохраняют совместимость с экосистемой старых плагинов.

При этом они не являются долгосрочной архитектурой:

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