検索/静的解析(lint)のルールを、コードの動きを変えない自動修正つきで作る

静的解析(lint)のルールを、コードの動きを変えない自動修正つきで作る

静的解析(lint)のルールを作る時に使う。よくある書き方だけを対象にし、既存のヘルパーを使い回し、安全な時だけ自動修正を付け、まぎらわしい構文もテストし、生成される文書は手で直さず作り直す。

このAIルールを使うには
## よくある書き方だけを対象にし、まれな場合は対象から外す 各ルールは概念的に単純に保つ。繰り返し現れる価値の高いパターンを対象とし、まれな境界条件への対応がルールを分かりにくくしたり、修正の危険性を高めたりする場合は、その条件をサポート対象から外す。 作成関数とメタデータを備えた安定したルール契約を定義する。イベントベースのアダプターでは、一般的なビジターマップを返すのではなく、用意されたフックを通じて開始時と終了時のリスナーを登録する。リスナーは問題オブジェクトを返すかyieldし、報告はアダプターに任せる。 各問題には関係するノードとメッセージを指定し、必要な場合だけデータ、修正、提案を加える。修正APIに中止機構がある場合、安全な編集を証明できなければ必ず使う。オプションの既定値はメタデータに置き、ルール側で統合処理を重複実装せず、フレームワークが一貫して統合できるようにする。 ## ルールを入れるプリセットを決め、オプション名をcheckで始める プリセットへの配置は製品上の判断として扱う。 - 異論の出にくいルールは最も安全なプリセットと、それより広いすべてのプリセットに入れる。 - 有用だが方針を含む既定値は、推奨プリセットとそれより広いコレクションだけに入れる。 - 用途が限定的な挙動や明示的な選択を要する挙動は、既定プリセットに入れない。 「特定の方針を押しつけない」と「方針を押しつけすぎる」を取り違えない。判断に迷う場合は、提案するレベルを明示し、メンテナーに決定を求める。 公開するブールオプションは、`true`が一貫して挙動を有効にするよう、`check*`形式の肯定的な名前にする。無視するパターンの一覧には、`ignore`などの慣例的な名前を使ってもよい。対応する言語をすべて明示する。ロジックがファイル名、生テキスト、コメント、ディレクティブだけに依存する場合は、実用的な範囲で複数言語に対応し、言語固有のノード型を抽象化するルート用ヘルパーとコメント用ヘルパーを使う。 ## 返す物が分かる名前をヘルパーに付け、既存のユーティリティを使い回す 非公開ヘルパーは、外部から観察できる契約に基づいて命名する。`is*`、`has*`、`should*`、`can*`、`needs*`は必ずブール値を返し、意図せずundefinedになるのではなく、明示的な`false`を返すことが望ましい。省略可能な問題を1件返す場合は`get*Problem`、複数件返す場合は`get*Problems`を使う。`report*`は直接報告するヘルパーだけに使う。 報告またはyieldと述語の結果を組み合わせない。問題を別に組み立て、呼び出し側でブール判定する。 ヘルパーを追加する前に、リポジトリのAST、汎用ユーティリティ、修正処理、共有ルールの各モジュールを調べ、次に信頼できるエコシステムのユーティリティを確認する。静的評価、変数検索、副作用検出、トークン検査、参照解決、括弧、セミコロン、修正構築には、バレルエクスポートと確立済みのヘルパーを再利用する。明確に再利用できる場合だけヘルパーを共通化し、小さなルール固有ロジックはローカルに保つ。 ## コードが必ず抜けるかを調べる時だけ、制御フロー解析を使う return、throw、break、continue、網羅的な分岐、無限ループなどを通じて、分岐または関数が必ず終了するか判定する必要がある場合は、フレームワークの制御フロー解析を使う。例外処理、ラベル、ネスト、到達不能区間を扱う際、ASTを手作業でたどる方法は一般に精度が低い。 分岐解析では、リポジトリの分岐終了トラッカーを登録し、外側の条件分岐を抜けた後に問い合わせる。関数解析ではコードパスごとにセグメント集合を保持し、ネストした関数が親関数へ影響しないようにする。関係するASTノードを抜ける時点で到達可能性を記録する。フレームワークが現在のセグメントをすでにクリアしている場合は、コードパスの終了まで待たない。 確認したいことが、最後の文が特定の型か、returnがどこにあるか、ローカルな修正処理がreturnまたはthrowで終わるかだけなら、より単純な構文検査を使う。本当に到達可能性を判定する必要がなければ、制御フローの仕組みを導入しない。 ## 自動生成の索引や文書の見出しは、手で直さず作り直す 編集前に、自動生成ファイルと自動生成領域を特定する。自動生成された索引、表、ドキュメントヘッダーを手作業で編集してはならない。ソースルールを追加、廃止、名前変更した後は、その生成物を管理するジェネレーターを実行する。競合を解消する際は正本の項目を保ち、決定的に生成される成果物を手作業で組み直さず、再生成する。 人が執筆するドキュメントは、自動生成ヘッダーの境界より下に置く。例ではリポジトリが推奨する設定構文を使い、例自体が明示的にJSONである場合を除いて、引用符の多いJSON風の例へ切り替えない。 共有ルールのパターンが関連リポジトリや関連パッケージにも存在する場合は、同じ変更を移植すべきか検討する。自動的に別プロジェクトまで編集せず、明示的な互換性判断として扱う。 ## 作っている間は作っているルールのテストだけ、最後に全部のテストを回す 実装前または実装と並行して、焦点を絞ったテストを書く。テスト基盤が診断、修正、提案をまとめて記録できる場合はスナップショット形式のテストを優先し、専用のパーサーモードは必要なケースだけに使う。 開発中は対象ルールのテストだけを実行する。スナップショットは意図的に更新し、完了前に一時的なフォーカステスト用マーカーを外す。通常の編集ループでは時間のかかる統合テストを実行しない。修正によって別の場所の違反が明らかになることが多いため、新しいルールの実装後とロジックを修正するたびにドッグフーディングを実行する。 最後に、リポジトリ本来のリンター、対象テスト、ドッグフーディングを実行し、その後で全テストスイートを1回実行する。別のlintツールが問題なしと報告したという理由だけで、本来のリンターを置き換えてはならない。 ## コメントやまぎらわしい構文で、自動修正をテストする 重複した網羅を作らず、挙動、非該当ケース、修正、提案をテストする。次の分類から関係するケースを含める。 - 型付き構文、型アサーション、ジェネリクス、オプショナルチェーン。 - JSX、コメント、余分な括弧、ネストした式、チェーンした式。 - 計算プロパティ、テンプレート、スプレッド、分割代入。 - return値が使われる場合と使われない場合。 - ルールが型に依存する場合の、構文解析だけのモードと完全な型情報を使うモード。 ルールが意図的に無視するケースもテストし、境界を文書化する。コメントがある場合と、自動セミコロン挿入による危険がある場合の修正を検証する。別のパーサーが必要なケースでは、要件を表せる最小のテスト範囲に設定する。 ## コードの動きを変えない時だけ自動修正を付け、コメントを残す 実行時の挙動が変わらない場合だけ自動修正を提供する。挙動が変わる可能性がある場合は、修正提案を優先する。コメントの保持は必須とする。置換する範囲や取り除く範囲にコメントが含まれる場合は、修正を中止するか、コメントをその位置に保つ範囲指定の編集を使う。 修正を返す前に、置換内容に括弧が必要か、先頭の角括弧または丸括弧のためにセミコロンが必要か、隣接するトークン間に空白が必要かを確認する。複数箇所を編集する操作にはジェネレーター形式の修正を使う。オプショナルチェーンの挙動を保ち、値が単独式とは異なる形で使われる場合は危険な編集を避ける。 リポジトリに単純なローカル保護策がすでに存在する場合を除き、シャドーされた組み込み要素、アクセサーやプロキシの副作用、疎配列の空要素を保つためだけに、修正を不釣り合いに複雑にしない。安全性を確立できない場合は、修正を付けずに問題だけを報告する。 ## ルールに名前を付け、雛形から作り、テストからコミットまで進める 対象の構造と意図を説明する名前を選ぶ。`no-`、`prefer-`、`require-`、`consistent-`などの接頭辞で挙動が明確になる場合は使う。提案する置換先ではなく対象を名前にし、曖昧な名前を避ける。 リポジトリの雛形作成機能を使って、実装、テスト、ドキュメント、自動生成される索引の項目を作る。その後、次の順序に従う。 1. 失敗するテストまたは代表的なテストを追加する。 2. そのテストを満たす最小限のルールロジックを実装する。 3. 挙動と意図的に対象外とする条件を文書化する。 4. 対象テストと正本のlintコマンドを実行する。 5. ドッグフーディングを実行し、最後に全テストスイートを実行する。 6. 自動生成ファイルが最新で、一時作業ファイルが残っていないことを確認する。 ルール名やオプション名を囲むために必要な句読点を含め、プロジェクトのコミットメッセージ規約を使う。ソース、テスト、ドキュメント、自動生成される領域、検証結果が一致した時だけ、提供完了とする。