【ESLint】静的解析の改善に取り組んでみて思ったこと
はじめに
2024年の4Qを使って静的解析の改善に取り組んだので振り返っていきます。2025年1月時点の筆者の静的解析に関するレベル感は以下の記事を執筆する程度で、後述するFlat Configやカスタムルールに関しては4Qでキャッチアップしながら進めました。
対応自体はスカンクワークスに該当するので通常のスプリントタスクをこなしながら合間を縫って作業しました。ざっくりとしたスケジュールだと1月にFlat Configへの移行、2月にFlat Configへの移行で気づいた箇所の修正、3月にカスタムルールの作成といった具合でした。
Flat Configへの移行
本業で対応する前に私用のリポジトリで素振りをしてから対応しました。Flat Configに移行することの背景やメリット、プラグイン単位でどのような方針を立てたか、どういったところで躓いたのかは次のスクラップにまとめています。
具体的なタスク
-
Legacy ConfigからFlat Configへの移行
- ESLintをv9へバージョンアップする
- ESLintランタイムで解決していたプラグインをインストールする
- プラグインのインストールで発生したBreaking Changeの対応
- 必要に応じて導入済みプラグインのバージョンアップ
- プラグインのバージョンアップで発生したBreaking Changeの対応
- カスタムルールをFlat Configに対応させる
-
Legacy ConfigからFlat Configへの移行で気づいた箇所の修正
- 無効になっていたno-restricted-syntaxを有効にする
- 未導入だったeslint-plugin-jsx-a11yを導入する
- typescript-eslintの推奨に従って幾つかのeslint-plugin-importのルールを無効にする
- アプリ分割を想定して共通化できるconfig objectを外に切り出しeslint.config.mjsをエッジケースのみにする
- 不要だったgraphql-eslintのschemaに関するルールを削除する
得られた成果
▼ Flat Configへ移行したことで、ESLintのバージョンアップに追従しやすくなった
Flat Configに移行したことで今後のESLintのバージョンアップに追従しやすくなりました。ESLint v10が2024年末〜2025年初頭にリリース予定とのことで、それまでに対応することができてよかったです。
また、Flat Configに移行したことで明示的にインポートする形式に変わったり、設定がフラットになったので可読性やメンテナンス性が向上しました。個人的に印象に残っているのがESLint Config Inspectorを使用できるようになった点です。デバッグが楽になったことでESLintに対する敷居が下がったように思います。
カスタムルールの作成
TypeScriptでカスタムルールを作成できる環境構築、既存のカスタムルールをTypeScriptへリプレイスする対応、横断タスクとして積まれていた2つのカスタムルールの作成に着手しました。
カスタムルール作成時はどのくらいのパターンを考慮するのか、ASTの知見も必要になる実装に対してどのようなアプローチで筆者以外の開発者の認知負荷を和らげるか、ソフトスキルも求められる内容だったと思います。
具体的なタスク
-
TypeScriptでカスタムルールを作成できる環境構築
- 既存のカスタムルールをTypeScriptにリプレイスする
-
カスタムルールの作成
- react-hook-formに関するカスタムルールの作成
- Apollo Clientに関するカスタムルールの作成
作成したカスタムルール
▼ react-hook-formのsetValueにshouldDirtyを強制するルール
shouldDirtyオプションが設定されていない場合に警告を表示し、コードの修正を提案するルールです。
react-hook-formのsetValueを使用する際にshouldDirtyオプションをtrueにしない場合、フォームの状態(isDirty)が適切に管理されません。この背景の共有はガイドラインだけでは周知することが難しいため実装レベルで気付けるように作成しました。
実際に筆者もフォームの状態が適切に管理されない背景を知らずに沼ったので以下の記事を執筆しています。お手数ですがエビデンスとなるIsuue等は以下の記事から参照してください。
✅ 正しいコード例
const { setValue } = useForm();
setValue('fieldName', value, { shouldDirty: true });
const { setValue } = useFormContext();
setValue('fieldName', value, { shouldDirty: true });
❌ 間違ったコード例
const { setValue } = useForm();
setValue('fieldName', value);
const { setValue } = useFormContext();
setValue('fieldName', value);
▼ refetchQueriesのvariablesにsatisfiesを強制するルール
refetchQueriesのvariablesプロパティにsatisfiesが適用されていない場合に警告を表示し、修正を促すルールです。
Apollo ClientのrefetchQueriesを使用する際に、引数はvariablesとして指定する必要がありますが、ここには型チェックが効きません。そのためrefetch対象に指定しているQueryのI/Fの変更があった場合にvariablesの追加・変更漏れに気がつけないため作成しました。
✅ 正しいコード例
const [mutation] = useMutation({
refetchQueries: [
{
query: DocumentNode,
variables: { id: 'id' } satisfies QueryVariables
},
],
})
const [mutation] = useMutation()
const handleClick = () => {
void mutation({
refetchQueries: [
{
query: DocumentNode,
variables: { id: 'id' } satisfies QueryVariables
},
],
})
}
❌ 間違ったコード例
const [mutation] = useMutation({
refetchQueries: [
{
query: DocumentNode,
variables: { id: 'id' }
},
],
})
const [mutation] = useMutation()
const handleClick = () => {
void mutation({
refetchQueries: [
{
query: DocumentNode,
variables: { id: 'id' }
},
],
})
}
得られた成果
▼ Apollo Clientに関するルールを導入したことで、型安全に実装できるようになった
カスタムルールの実装コードの認知負荷と導入することで得られるメリットを比較した結果、react-hook-formに関するカスタムルールは導入を見送ることとなり、Apollo Clientに関するルールは導入することになりました。
Apollo Clientに関するルールは実際にI/Fの変更に気付けずユーザー価値に影響が出る事例があったのが導入に至った要因かなと思っています。
▼ 「カスタムルールを読み解く会」を実施したことで、カスタムルールに対する敷居を下げた
ただ、依然としてカスタムルールの実装コードに対しての認知負荷は高いと思ったので「カスタムルールを読み解く会」を実施しました。開発メンバーがカスタムルールを読み解くためのフックを得ていただくことが目的となっていて、ASTと実装コードを照らし合わせながら読み進めていきました。
実施したところ8名の開発メンバーに参加していただきました。また、「カスタムルールを読み解く会」を開催することで他の方が別の勉強会を開催する起因になったので実施してよかったなと思います。
最後に
Legacy Configで止まっていたESLintに対する知見を今回のFlat Configへの移行とカスタムルールの作成を通してアップデートすることができました。期間でいうと3ヶ月ほどかけて静的解析の改善に取り組みましたが、成果を出すにはちょうど良い期間だったなと感じます。2025年の1Qも何かしらのテーマを持って取り組もうと思いました。
簡単ですが以上になります。最後までご覧いただきありがとうございました。もしこの記事がどなたかの参考になりましたら幸いです。
Discussion