Skip to content

Oxlint の型対応プレビュー

本投稿では、型対応リントの技術的プレビューを発表します。 統合性とルールカバレッジが向上した最新のアルファ版については、型対応リントのアルファ版発表 をご確認ください。


oxlint における型対応リントの登場をお知らせできることを大変嬉しく思います!

待ち望んでいた no-floating-promises および関連するルールがついに登場しました。

このプレビューリリースは、意思決定プロセスと技術的詳細を文書化することで、コミュニティとの協力と議論を促進することを目的としています。

クイックスタート

oxlint が既に設定済みの場合、oxlint-tsgolint をインストールし、--type-aware フラグを付けて oxlint を実行してください:

bash
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint --type-aware

oxlint がまだ設定されていないが、no-floating-promises を実際に試したい場合:

bash
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint@latest --type-aware -A all -D typescript/no-floating-promises

以下のようになります(例):

js
 × typescript-eslint(no-floating-promises): プロミスは待機するか、.catch の呼び出しで終了するか、失敗ハンドラを持つ .then で終了するか、または `void` 演算子で明示的に無視されたものでなければなりません。
   ╭─[packages/rolldown/src/api/watch/watcher.ts:30:7]
29await this.close();
30originClose();
   ·       ──────────────
31 │     };
   ╰────

その他の構成オプションについては、使い方ガイド をご参照ください。

パフォーマンス

テスト結果によると、以前 typescript-eslint で1分かかっていたリポジトリが、現在では10秒未満で完了するようになりました。

これは、10倍速いTypeScript を実装した Go言語による TypeScript を活用することで達成されています。

oxc-ecosystem-ci からのプロジェクトを用いた測定結果:

プロジェクトファイル数時間
napi-rs1441.0s
preact2452.7s
rolldown3141.5s
bluesky11527.0s

型対応リント

エコシステム内での型対応リントの現状について理解するために、「RustベースのJavaScriptリントツール:高速だが、現時点では型対応リントなし」 を参照してください。

技術的詳細

この新機能の中心となるのは oxc-project/tsgolint です。

tsgolint プロジェクトは当初、typescript-eslint/tsgolint としてプロトタイプ化されました。しかし、typescript-eslint チームは、今後も ESLint での型対応リント開発を継続する予定であるため、このプロトタイプへの開発リソースの割り当てをしないことになりました。

@boshen@auvred に、oxlint 向けに簡略化されたフォーク版の開発を依頼しました。このバージョンは、フルリントツールに必要な高度な構成解決機能を持たず、型対応ルールのみを含むものです。

@auvred は、このプロジェクトの開発を Oxc 組織下で継続することに快く同意してくれました。

アーキテクチャ

oxlint(Rustで記述)と tsgolint(Go言語で記述)はそれぞれ個別のバイナリとしてコンパイルされます。

oxlinttsgolint の「フロントエンド」として機能し、CLI処理、パス走査、無視ロジック、診断出力などを担当します。

tsgolintoxlint のバックエンドとして機能し、パスと構成を入力として受け取り、構造化された診断を出力します。

これによりシンプルなパイプラインが構成されます:

oxlint CLI(パス + ルール + 構成を返す)
  -> tsgolint(診断を返す)
  -> oxlint CLI(診断を出力する)

tsgolint

tsgolint は公開APIを通じて typescript-go と通信しません。

代わりに、内部APIをシムすることでそれらを公開しています。

すべての型対応ルールは、このシムされたAPIに対して直接書かれています。

内部的なアクセスとしては推奨されませんが、動作はしています!

決定プロセス

自前で型チェッカーを実装する

過去に失敗に終わった型チェッカーの実装試みには、以下が含まれます:

また、独自の型推論実装を持つ進行中の Biome 2.0 も存在します。

私たちが自前で型推論器や型チェッカーを実装することは現実的ではないと考えた理由は、常に変化するターゲットである TypeScript に対応しつつ維持することが困難だからです。

TypeScript コンパイラとの通信

typescript-go の導入以前、プロジェクトは、その公開APIにプラグインインターフェースを追加するために、いずれかの方法で AST を estree にマッピングするか、直接タイプスクリプトの AST を走査していました。代表的な例には以下があります:

また、oxlint とのプロセス間通信の検討も行いましたが、このアイデアは放棄しました。

typescript-go では、マイクロソフトのチームは、タイプスクリプトのASTをエンコードし、JavaScript側でデコードする 方向性に傾いています。

これらのアプローチは動作しますが、依然として以下のような問題があります:

  • oxlint のパフォーマンス特性に合わない、程度の異なるパフォーマンス課題。
  • タイプスクリプトのASTからestreeへのマッピングを維持するコスト。

考慮事項

tsgolint はパフォーマンス問題を解決していますが、他にも技術的課題が残っています。

別のTypeScriptバージョンが必要

typescript-go のスナップショットバージョンをリリースし、バージョン番号をタイプスクリプトと一致させる予定です。そうすれば、適切なタイプスクリプトバージョンを持つ oxlint-typescript をインストールできます。

このアプローチの欠点は、oxlint-tsgolint が変更を要求した場合、タイプスクリプトのアップグレードが必要になる可能性があることです。

tsgolint のメンテナンスコスト

タイプスクリプトの内部APIをシムすることは多少リスクを伴います。しかし、タイプスクリプトのASTとそのビジターは実際非常に安定しています。私たちはこのリスクを受け入れ、typescript-go のアップグレード時に破壊的変更を修正します。

typescript-go のバージョンは毎日同期されています。

パフォーマンス問題

tsgolint は、数百のプロジェクトや大量のプロジェクト参照を含む大きなモノレポでは、性能が十分に発揮できません。

バグが発生すると、デッドロックに陥るか、メモリ不足(OOM)を引き起こす可能性があります。

私たちはこれらの問題に対処中であり、typescript-go へのプロファイリングと改善提案を進めています。これにより、すべての typescript-go ユーザーが恩恵を受けます。

コアチームメンバー @camc314 は、すでにいくつかの プルリクエスト を提出し、複数のコードパスを大幅に高速化しています。

v1.0リリース

tsgolint v1.0 に向けて、以下の対応を予定しています:

  • 大きなモノレポのパフォーマンス問題
  • 個別ルールの構成可能化
  • 各ルールの正確性
  • IDEサポート
  • 全体的な安定性

謝辞

以下の方々に感謝申し上げます:

  • typescript-go の作成に貢献したタイプスクリプトチーム
  • 慶賛的な支援を提供してくれた typescript-eslint チーム
  • tsgolint の作成に貢献した @auvred
  • oxlint + tsgolint の統合に貢献した @camchenry
  • パフォーマンス問題に関する作業に貢献した @camc314

カンペーンに参加しよう

oxlint と型対応リントに対するフィードバックをお聞かせいただけると嬉しいです。開発ワークフローの改善に役立つことを楽しみにしています。

私たちとつながろう:

次のステップ

oxlint をインストール:

bash
pnpm add -D oxlint@latest oxlint-tsgolint@latest
pnpm dlx oxlint --init # .oxlintrc.json を生成

または、インストールガイド を参照してください。

--type-aware CLIフラグを使用します。

bash
pnpm dlx oxlint --type-aware

.oxlintrc.json 内の任意の型対応ルールで遊んでみてください:

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"
  }
}