Eslint Pluginを作りました
2022/03/28 13:15:00 +00:00
問題
下記のようなディレクトリ構成のプロジェクトがあったとします。
src/components/
model/
user/
user.tsx
user.css.ts
user.test.tsx
user.stories.tsx
page/
dashboard/
dashboard.tsx
dashboard.css.ts
dashboard.test.tsx
dashboard.stories.tsx
ui/
button/
button.tsx
button.css.ts
button.test.tsx
button.stories.tsx
このプロジェクトでは、例えば user.css.ts
は user.tsx
からのみ依存されているとします。JavaScriptには当然ディレクトリ単位での可視性のスコープなんてものはないため、user.css.ts
は dashboard.tsx
からも参照できてしまい、他のコンポーネントを参照する場合はエディタの補完候補に目を凝らさないといけません。
どうするか
eslint-plugin-import-only-from-index を作りました。
eslintrcにはこのように指定します。
"plugin": ["import-only-from-index"],
"rules": {
"import-only-from-index/import-only-from-index": ["error", ["src/components"]]
}
オプションで指定するディレクトリ(上記例では src/components
)からexportしたものはlint errorとします。ただし、indexファイルからexportしたものはerrorとしません。つまり指定ディレクトリから外部に公開したいものは、indexファイルからre-exportすることを強制します。
これは完全な個人用途なのでpublishしなくてもいいかと思ったんですが、自分で使うときにnpm installできるのは便利だし、特に誰も困らないだろうからいいだろうと決めてpublishしました。
やったこと
- Eslint Pluginの作成
- TypeScript Server Pluginの作成
Eslint Plugin
Working with Plugins - ESLint では Yeoman のテンプレートが内されていますが、npx一発では作れないので諦めました。がそれ以外は、plugin作成用のlint ruleの案内があったりしてよかったです。
初手としては、Quramy/eslint-plugin-tutorial というチュートリアルが分かりやすかったです。pluginが求めるインターフェイス、簡単なルールの書き方、そのテストのやり方、ASTについて分かりやすく案内されています。
そしてここから先はロジックの話ですが、作ってみて気づいたのが、import文の曖昧なパターンの存在です。import * from './hoge';
としたとき、二つの解釈が生まれます。
hoge
ディレクトリにあるindex.ts
からのインポートhoge
ファイルで、何らかの拡張子が省略されている(hoge.ts
なのか、hoge.tsx
なのかは字句のみでは分からない)
それらしい方法も思いつかないので、愚直にファイルシステムにアクセスして何が存在するのか確かめることにしています。それに伴って、テストでもbeforeAll/afterAllでディレクトリやファイルを作って後始末をしたくなるんですが、eslintのtest runnerにはそれがありません。そこで Vitestでeslint test runnerを囲うことにしました。Vitestは依存ライブラリが少ないし簡単に実行できてよいですね。簡単なテストにはmochaを選びがちだったんですが、これからはVitestでよさそうです。
TypeScript Server Plugin
Eslintのpluginだけではエディタの補完候補までは制御できないので、Language Serverのレスポンスに介入できるTypeScript Server Pluginを作ってみました。コンパイラオプションの plugins
に指定します。
"compilerOptions": {
"plugins": [
{
"name": "eslint-plugin-import-only-from-index",
"restrictedPath": "src/components"
}
]
}
Writing a Language Service Plugin · microsoft/TypeScript Wiki が入門にちょうどよかったです。テストに関しては実際にプロジェクトを作って確かめろというスタンスで、どうにかテストできないかなと模索したんですが、そもそもtsserverの型定義がなく諦めました。(Quramy/typescript-eslint-language-service を見ると丁寧にやってて、ここまでやる気力はわかなかった…)
ユニットテストできない、かつプリントデバッグできないので、環境変数TSS_LOG
を使って動作確認していくのがしんどかったです。多少苦労してもtsserverを扱ったほうがよいのかも。
ハマりどころとして、VS Codeで使用するTypeScriptをworkspaceのものを指定しなければ動作しないというのがありました。.vscode/settings.json
に "typescript.tsdk": "node_modules/typescript/lib"
と書くやつですね(F1キーでTypeScript: Select TypeScript Version
する)。
参考にさせてもらったリポジトリや記事
- uhyo/eslint-plugin-import-access
- eslint pluginとtypescript server pluginを同じエントリポイントで公開する方法が参考になりました
- TSServerの使い方メモ - Qiita
おわりに
今回は単純なimport文だったのでASTの深いところまで潜らずに済みましたが、もうちょっと勉強できるお題があったらよかった…。