javascript

Больше свободы агентам — жёстче проверки: линтинг React 19 на ESLint 10

  • понедельник, 24 августа 2026 г. в 00:00:11
https://habr.com/ru/articles/1073392/

Чем дальше, тем больше доверяю ИИ-агентам, наверное потому что я пока жестко не влетел. Более того, процесс идёт постепенно - тут разрешил машину в облаке запустить когда задача XXX, не спрашивая у меня разрешения на каждый чих, здесь ещё что-то.

Это очень сильно ускоряет работу, хотя и влететь можно сильно и больно. Пока меня это обошло стороной, поэтому в разрезе агентов и их креатива больше всего прилетает о того что они пишут больше кода чем надо, делают мелкие опечатки и ошибки и для борьбы, или хотя бы её видимости во всех проектах натыканы guardrails - механические детерминированные правила, которые запускаются при каждом commit, да и каждый раз когда агенты рапортуют: “Задача выполнена.”

К таким проверкам относятся:

  • tests

  • pre-commit hooks

  • linters

  • formatters

Такие проверки превращают требования в однозначный результат: прошло или не прошло. Небольшие коммиты сужают область поиска, когда что-то ломается.

Есть ещё менее детерминированные, но не менее важные: SKILLs, особенно un-slop - но это отдельная история, на сейчас skills эволюционируют быстрее кода, и тут рано пока делать какие-то выводы.

Линтинг — один из таких защитных механизмов.

И эта история началась, когда привычный React-линтинг сломался при переходе на ESLint 10.

Я поддерживаю несколько React-приложений. Когда я попытался перевести их на ESLint 10, миграция остановилась на eslint-plugin-react.

В ESLint 10 удалили устаревшие API контекста правил. Текущая версия eslint-plugin-react@7.37.5 заявляет поддержку только до ESLint 9 включительно, а под ESLint 10 падает с ошибкой:

TypeError: contextOrFilename.getFilename is not a function

Issue на эту ему открыли 7 февраля 2026 года. На 23 августа она оставалась открытой уже больше шести месяцев. PR с исправлением, открытый 30 июля, тоже оставался открытым.

Хотелось сохранить детерминированную проверку React и перейти на ESLint 10, поэтому выпустил @ternaus/eslint-plugin-react — самостоятельно поддерживаемое продолжение плагина для React 19 и ESLint 10.

Кому подходит пакет

У пакета намеренно узкая матрица поддержки (плясал от своих задач):

Инструмент

Поддерживаемая версия

React

19+

ESLint

10

Biome

2.5.8+

Node.js

22.13, 24 или 26

Конфигурация ESLint

Плоская конфигурация

React 18, ESLint 9 и .eslintrc* не поддерживаются.

Моя конфигурация

В моих проектах форматированием и основной частью линтинга занимается Biome. Он проверяет JavaScript, TypeScript, JSX, DOM и большую часть правил React.

ESLint остаётся для проверок, которых нет в Biome: правил фреймворков и нескольких проверок поведения React 19.

Эта конфигурация выросла из реальных проектов на Next.js и React: Albumentations.ai, sportscategory.info и my-roots.me.

  • Next.js 16

  • React 19

  • TypeScript

  • ESLint 10 с плоской конфигурацией

  • Biome с набором all

  • Yarn 4

  • Node.js 22, 24 и 26

Biome активно развивается и старается быть и linter и formatter, поэтому когда он и Eslint тестируют одно и то же поведение, дублирующая проверка Eslint не нужна. Для других Eslint плагинов я их выключаю в конфиге, а тут я их просто вырезал - меньше кода => меньше багов и прочего головняка.

С чего я начал

Взял за основу исходный репозиторий и сохранил его историю Git, лицензию MIT и сведения об авторах. Дальше проект развивается независимо.

На момент ветвления плагин экспортировал 104 модуля с правилами. Набор all включал 102 активных правила, ещё два были помечены устаревшими. В recommended находилось 22 правила, но react/no-unsafe было явно выключено, поэтому фактически работало 21.

Механическая проверка полезна, только когда понятно, какую проблему она ловит и какой инструмент за неё отвечает. Переносить весь этот объём не имело смысла. Я поставил более узкую задачу: оставить проверки корректности React 19, которые дополняют Biome.

Как 102 активных правила превратились в 11

Я разделил правила по назначению:

Группа

Решение

Biome уже сообщает о той же проблеме

Удалить из плагина

Форматирование, именование, структура файлов или политика команды

Оставить Biome или настройкам проекта

Эвристики, которым нужны данные обо всём проекте или информация о типах

Удалить, чтобы не выдавать ненадёжные предупреждения

React 18, классическая конфигурация ESLint, обходные пути для парсеров или устаревшие API React

Исключить из контракта React 19

Корректность React 19 или полезное React-специфичное предупреждение о производительности

Оставить или реализовать

В первую группу попали ровно 20 правил, среди них jsx-key, no-danger, no-unknown-property и self-closing-comp. Полное сопоставление с Biome записано в описании поддержки правил исходного плагина.

Правила jsx-sort-props, function-component-definition и prefer-stateless-function задают стиль или политику команды, а не проверяют корректность React 19. no-unused-prop-types и no-unused-state пытаются ответить на вопросы обо всём проекте с помощью локального анализа AST, поэтому дают ненадёжный результат. Остальные исключённые правила обслуживали устаревшие конфигурации и версии за пределами матрицы поддержки.

После отбора осталось четыре исходных идентификатора правил: jsx-no-constructed-context-values, no-deprecated, no-direct-mutation-state и no-invalid-html-attribute.

Какие правила я добавил

Среди предложений, относящихся к переходу на React 19, я рассмотрел три правила, которые так и не были приняты в исходный плагин:

Сначала я реализовал все три. Затем удалил no-render-return-undefined: React 19 разрешает возвращать undefined, поэтому запрет выражал бы соглашение команды, замаскированное под требование фреймворка. Два других предложения стали правилами no-function-default-props и prefer-use-state-lazy-initialization. Первое находит механизм, который React 19 игнорирует. Второе предупреждает о лишних вычислениях при каждом рендере.

Ещё пять правил я добавил или сузил до конкретного поведения React 19: controlled-form-requires-handler, jsx-no-key-after-spread, no-implicit-ref-callback-return, no-misspelled-lifecycle-methods и no-prop-types.

В версии 8.0.0 осталось 11 правил. Все они входят в recommended: девять проверок корректности имеют уровень error, две проверки производительности — warn. Отдельного набора all нет: он либо полностью повторял бы recommended, либо отличался бы только уровнем предупреждений.

А потом начал добавлять в реальные проекты и полезли corner cases

К версии 8.0.0-rc.3 проходили все модульные тесты и проверки пакета. Затем я подключил плагин к Rooted и Albumentations.ai — и нашёл проблемы, которых не было в тестах.

Первый класс ошибок был связан с метаданными HTML-атрибутов. Правило no-invalid-html-attribute отклоняло корректные атрибуты: alt, accept, name, loading, form, а также value у <select>, <option> и <textarea>. Исправления пришлось выпускать в три этапа: задача #21 и PR #22, затем задача #25 и PR #26, и наконец задача #29 и PR #31. В финальной реализации HTML-атрибуты из стандарта WHATWG и свойства React DOM стали двумя отдельными слоями. Один источник метаданных не описывает оба контракта полностью.

Вторую ошибку обнаружил Next.js. eslint-config-next@16 собирает плоскую конфигурацию, но читает правила из поля старой формы — react.configs.recommended.rules. Мой пакет экспортировал только react.configs.flat.recommended, поэтому конфигурация падала ещё до проверки файлов. В задаче #24 и PR #27 я добавил поле configs.recommended.rules, которого ждал Next.js, не возвращая поддержку .eslintrc. Также пришлось описать resolutions для Yarn: Next.js импортирует плагин как eslint-plugin-react, без префикса @ternaus/.

Эти интеграции привели к выпускам RC4, RC5 и RC6. Они изменили и стратегию тестирования. Финальная версия проверяет собранный npm-архив с помощью publint, загружает его из тестовых проектов на ESM, CommonJS и TypeScript и проверяет форму конфигурации, которую использует Next.js. CI запускается на Node.js 22.13, 24 и 26.

В результате пакет использует нативный ESM, плоскую конфигурацию ESLint и привычное пространство имён правил react/*.

Как подключить пакет

В примерах используется Yarn 4.

Установите Biome, ESLint 10 и плагин:

yarn add --dev @biomejs/biome@'>=2.5.8' eslint@^10 @ternaus/eslint-plugin-react@^8.0.0

Включите полный стабильный набор правил Biome и домен React:

{
  "linter": {
    "domains": {
      "react": "all"
    },
    "rules": {
      "preset": "all"
    }
  }
}

Затем добавьте оставшиеся React-проверки в eslint.config.js:

import react from '@ternaus/eslint-plugin-react';

export default [
  {
    files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'],
    ...react.configs.flat.recommended,
  },
];

Если шаблон файлов включает TypeScript, парсер с его поддержкой нужно подключить выше в плоской конфигурации.

Запускайте оба инструмента:

yarn biome check .
yarn eslint .

Идентификаторы правил остаются в пространстве имён react:

{
  rules: {
    'react/no-deprecated': 'error',
    'react/no-implicit-ref-callback-return': 'error',
  },
}

Подключение к Next.js

eslint-config-next импортирует пакет как eslint-plugin-react, без префикса @ternaus/. В Yarn эту зависимость нужно направить на поддерживаемый пакет:

{
  "devDependencies": {
    "@ternaus/eslint-plugin-react": "8.0.0"
  },
  "resolutions": {
    "eslint-plugin-react": "npm:@ternaus/eslint-plugin-react@8.0.0"
  }
}

Версии в обоих полях должны совпадать. Тогда eslint-config-next не установит рядом исходный плагин, который поддерживает только ESLint 9.

Next.js регистрирует пакет под именем react, поэтому существующие идентификаторы react/* продолжат работать.

Когда пакет не подходит

Этот пакет не содержит все правила исходного eslint-plugin-react.

Если конфигурация зависит от react/jsx-sort-props, react/display-name, react/prop-types или других отсутствующих правил, перед миграцией проверьте полный каталог.

Для React Native отдельного контракта совместимости тоже нет. DOM-правила анализируют только однозначно распознанные HTML-элементы, записанные в нижнем регистре.

Что это меняет в работе с агентами

В моих проектах свобода агентов растёт вместе с числом контрактов, которые можно запустить как обычную команду. Агент сам выполняет те же хуки pre-commit, тесты, правила линтера и проверки пакета, которые позже запустит CI.

Решения об архитектуре, компромиссах и исключениях остаются за человеком. Одиннадцать правил @ternaus/eslint-plugin-react отсекают предсказуемые ошибки React 19 до ручной проверки. Правила стиля и устаревшие слои совместимости остались за границами пакета.

Если ваша конфигурация совпадает с матрицей поддержки, начните с recommended, запустите Biome и ESLint на реальном проекте и проверьте каталог удалённых правил до миграции. Именно интеграция в настоящие приложения нашла проблемы, которых не было в модульных тестах.

Ссылки