fourside.github.io

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.tsuser.tsx からのみ依存されているとします。JavaScriptには当然ディレクトリ単位での可視性のスコープなんてものはないため、user.css.tsdashboard.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する)。

参考にさせてもらったリポジトリや記事

おわりに

今回は単純なimport文だったのでASTの深いところまで潜らずに済みましたが、もうちょっと勉強できるお題があったらよかった…。


fourside

Written by fourside