Технический превью типозависимого линтинга в Oxlint
Эта заметка анонсирует технический превью типозависимого линтинга. Актуальная альфа с улучшенной стабильностью, настройкой и покрытием правил — в анонсе альфы типозависимого линтинга.
Мы рады объявить о типозависимом линтинге в oxlint!
Долгожданные no-floating-promises и связанные правила уже здесь.
Этот превью-релиз призван вовлечь сообщество в обсуждение за счёт документирования решений и технических деталей.
Быстрый старт
Если oxlint уже настроен, установите oxlint-tsgolint и запускайте с флагом --type-aware:
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint --type-awareЕсли oxlint не настроен, но нужно увидеть no-floating-promises в действии:
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint@latest --type-aware -A all -D typescript/no-floating-promisesОжидается вывод вида:
× typescript-eslint(no-floating-promises): Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator.
╭─[packages/rolldown/src/api/watch/watcher.ts:30:7]
29 │ await this.close();
30 │ originClose();
· ──────────────
31 │ };
╰────Подробнее о настройке см. руководство по использованию.
Производительность
По нашим замерам репозитории, где раньше typescript-eslint работал около минуты, теперь завершаются менее чем за 10 секунд.
Это достигается за счёт typescript-go, «TypeScript примерно в 10 раз быстрее» на Go.
Проекты из oxc-ecosystem-ci:
| Проект | Файлы | Время |
|---|---|---|
| napi-rs | 144 | 1.0s |
| preact | 245 | 2.7s |
| rolldown | 314 | 1.5s |
| bluesky | 1152 | 7.0s |
Типозависимый линтинг
См. материал Rust-Based JavaScript Linters: Fast, But No Typed Linting Right Now о текущем состоянии типозависимого линтинга в экосистеме.
Технические детали
Ядро новой функциональности — oxc-project/tsgolint.
Проект tsgolint изначально прототипировался как typescript-eslint/tsgolint. Команда typescript-eslint решила не вкладываться в этот прототип, планируя развивать типизированный линтинг в ESLInt через typescript-eslint.
@boshen обратился к @auvred за форком с ограниченным охватом под oxlint — только типозависимые правила без полного разрешения конфигурации линтера.
@auvred любезно продолжил разработку в организации Oxc.
Архитектура
oxlint (Rust) и tsgolint (Go) собираются в отдельные бинарники.
oxlint — «фронтенд» для tsgolint: CLI, обход путей, ignore и вывод диагностик.
tsgolint — бэкенд: принимает пути и конфигурацию, возвращает структурированные диагностики.
Простой конвейер:
oxlint CLI (передаёт пути + правила + конфигурацию)
-> tsgolint (возвращает диагностики)
-> oxlint CLI (печатает диагностики)tsgolint
tsgolint не общается с typescript-go через публичные API.
Вместо этого компилируется typescript-go с shim внутренних API для их открытия.
Все типозависимые правила пишутся напрямую к этим shim API.
Это не рекомендуемый способ доступа к internals, но он работает!
Процесс решений
Написать свой типчекер
Ранее заброшенные попытки включали:
- Собственная попытка написать вывод типов
- Интеграция ezno от @kaleidawave
- stc от @kdy1
- Множество попыток сообщества без долгосрочного результата.
Есть ещё работа над Biome 2.0 со своим выводом типов.
Мы решили, что собственный инференсер или типчекер нецелесообразен из‑за необходимости поспевать за быстро меняющимся TypeScript.
Общение с компилятором TypeScript
До typescript-go проекты добавляли плагины к публичному API TS, маппя AST в estree или обходя AST TS. Примеры:
Мы также рассматривали IPC с oxlint, но отказались.
С typescript-go команда TypeScript склоняется к кодированию AST TS и декодированию на стороне JS через IPC.
Эти подходы работают, но всё равно дают:
- проблемы производительности, не подходящие под профиль oxlint;
- затраты на поддержку маппинга AST TypeScript.
Замечания
tsgolint решает проблему производительности, но остаются другие технические вызовы.
Нужна другая версия TypeScript
Планируем выпускать снапшоты версий typescript-go и синхронизировать номера с TypeScript. Тогда можно будет ставить oxlint-typescript с подходящей версией TS.
Минус: может потребоваться обновлять TypeScript, если oxlint-tsgolint требует изменений.
Стоимость сопровождения tsgolint
Shim внутренних API TS несёт риск. Но AST и visitor в TS довольно стабильны. Мы принимаем риск и чиним поломки при обновлении typescript-go.
Наш форк typescript-go синхронизируется ежедневно.
Проблемы производительности
tsgolint пока плохо масштабируется на огромные монорепы с сотнями проектов или множеством project references.
Возможны зависания, deadlock или OOM при ошибках.
Мы активно это устраняем, профилируем и шлём улучшения в typescript-go на благо всех пользователей.
Член команды @camc314 уже прислал множество PR, заметно ускоривших отдельные пути.
Релиз v1.0 для tsgolint
Для tsgolint v1.0 запланировано:
- производительность на больших монорепах;
- настройка отдельных правил;
- корректность каждого правила;
- поддержка IDE;
- общая стабильность.
Благодарности
Благодарим:
- команду TypeScript за
typescript-go; - команду
typescript-eslintза тёплую поддержку; - @auvred за создание
tsgolint; - @camchenry за интеграцию
oxlint+tsgolint; - @camc314 за работу над производительностью.
Сообщество
Нам интересна обратная связь по oxlint и типозависимому линтингу.
Контакты:
- Discord: сервер сообщества
- GitHub: GitHub Discussions
- Issues: баги
oxlint— oxc, типозависимого линтинга — tsgolint.
Дальнейшие шаги
Установите oxlint:
pnpm add -D oxlint@latest oxlint-tsgolint@latest
pnpm dlx oxlint --init # создать .oxlintrc.jsonили следуйте руководству по установке.
Используйте флаг CLI --type-aware.
pnpm dlx oxlint --type-awareЭкспериментируйте с типозависимыми правилами в .oxlintrc.json:
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"rules": {
"typescript/await-thenable": "error",
"typescript/no-array-delete": "error",
"typescript/no-base-to-string": "error",
"typescript/no-confusing-void-expression": "error",
"typescript/no-duplicate-type-constituents": "error",
"typescript/no-floating-promises": "error",
"typescript/no-for-in-array": "error",
"typescript/no-implied-eval": "error",
"typescript/no-meaningless-void-operator": "error",
"typescript/no-misused-promises": "error",
"typescript/no-misused-spread": "error",
"typescript/no-mixed-enums": "error",
"typescript/no-redundant-type-constituents": "error",
"typescript/no-unnecessary-boolean-literal-compare": "error",
"typescript/no-unnecessary-template-expression": "error",
"typescript/no-unnecessary-type-arguments": "error",
"typescript/no-unnecessary-type-assertion": "error",
"typescript/no-unsafe-argument": "error",
"typescript/no-unsafe-assignment": "error",
"typescript/no-unsafe-call": "error",
"typescript/no-unsafe-enum-comparison": "error",
"typescript/no-unsafe-member-access": "error",
"typescript/no-unsafe-return": "error",
"typescript/no-unsafe-type-assertion": "error",
"typescript/no-unsafe-unary-minus": "error",
"typescript/non-nullable-type-assertion-style": "error",
"typescript/only-throw-error": "error",
"typescript/prefer-promise-reject-errors": "error",
"typescript/prefer-reduce-type-parameter": "error",
"typescript/prefer-return-this-type": "error",
"typescript/promise-function-async": "error",
"typescript/related-getter-setter-pairs": "error",
"typescript/require-array-sort-compare": "error",
"typescript/require-await": "error",
"typescript/restrict-plus-operands": "error",
"typescript/restrict-template-expressions": "error",
"typescript/return-await": "error",
"typescript/switch-exhaustiveness-check": "error",
"typescript/unbound-method": "error",
"typescript/use-unknown-in-catch-callback-variable": "error"
}
}


