eslint-plugin-react-refresh周りを調べてあわよくばbiomeに突っ込めたら嬉しいなをゆるりと考える
react-refresh
react-refreshはreactの公式パッケージで、実行中のReactアプリをリアルタイムで更新できるFast Refresh機能をバンドラに提供する機能。
react-nativeの他、@vitejs/plugin-react・@parcel/runtime-react-refresh等開発環境の裏側でよく用いられている。
所謂ホットリロードだが、Stateを保持したまま、編集したコンポーネントのみについて分的に更新することができることが特徴。
元々React native向けの機能だったようだが、技術的詳細は誰かがまとめてくれたところを参照する。
この機能が有効になるにはReactコンポーネントのみをエクスポートするという条件がある様子。コンポーネント以外のエクスポートを含むファイルを編集すると、全部リロードされてしまうとのこと。
eslint-plugin-react-refresh
eslint-plugin-react-refreshは@vitejs/plugin-reactからもリンクされているプラグインで、react-refreshを正常に機能させるためのルールを定義している。
BiomeのLinterルールの追加に関して
最近はプラグイン機能のリリースの動きなどもあるようだが、基本的に本体に追加することになっている。
本体に追加するルールはなるべく実用的で普遍的なものを、ということで議論したうえで追加したりなんだったりが進むらしい
ここにおいて、eslint-plugin-react-refresh
は、現時点ではほとんど進んでいない。issueを立てること自体は問題無さそうではある。
eslint-plugin-react-refreshの中身
lintルールの本体はonly-export-components.ts
というファイルにある。
create
の引数の関数内でTSESTree(構文木)を調べていって、問題があればcontext.report
関数を実行する、といった具合である。
-
Program
ノードのハンドラで、いくつかの初期化処理を行っています。
-
hasExports
: ファイルにエクスポートがあるかどうかを追跡する変数 -
mayHaveReactExport
: 可能性のあるReactコンポーネントのエクスポートがあるかどうかを追跡する変数 -
reactIsInScope
: react がインポートされているかどうかを追跡する変数 -
localComponents
: ファイル内で定義されたコンポーネントの識別子を格納する配列 -
nonComponentExports
: コンポーネント以外のエクスポートの識別子を格納する配列
2.handleLocalIdentifier
関数は、ファイル内で定義された識別子をチェックし、可能性のあるReactコンポーネントの識別子を localComponents 配列に追加します。
-
handleExportIdentifier
関数は、エクスポートされた識別子をチェックし、コンポーネントかどうかを判断します。コンポーネントでない場合は nonComponentExports 配列に追加します。
https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/b7e61e73e368665a44fbc332356e8e5144e4c721/src/only-export-components.ts#L92-L136 -
handleExportDeclaration
関数は、エクスポート宣言ノードを処理します。ノードの種類に応じて、handleExportIdentifier
関数を呼び出してエクスポートされた識別子を処理します。
https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/b7e61e73e368665a44fbc332356e8e5144e4c721/src/only-export-components.ts#L138-L167 -
プログラムの本体ノードを探索し、エクスポート宣言ノードを見つけたら
handleExportDeclaration
関数を呼び出します。また、変数宣言や関数宣言のノードではhandleLocalIdentifier
関数を呼び出して、ファイル内で定義された識別子を処理します。
https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/b7e61e73e368665a44fbc332356e8e5144e4c721/src/only-export-components.ts#L169-L209 -
プログラムの探索が完了したら、
hasExports
、mayHaveReactExport
、localComponents
、nonComponentExports
の値に基づいて、適切な警告メッセージを出力します。
https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/b7e61e73e368665a44fbc332356e8e5144e4c721/src/only-export-components.ts#L211-L226
fast-refreshのためにやっていること
- exportAll:
export *
は非対応なのでエラーExportAllDeclaration
- Biome: noReExportAll
- namedExport: コンポーネントでないものが同時にエクスポートされていると機能しないのでエラー
- Reactエクスポートがあるとき、Reactでないエクスポートに対して
- React要素の判定は識別子の名前で行う
- Reactエクスポートがあるとき、Reactでないエクスポートに対して
- anonymousExport: 匿名関数のエクスポート
export default function()
では機能しないのでエラー- エクスポートされたものの内
-
FunctionDeclaration
のnode.id
がnull
-
CallExpression
のうち匿名の物
-
- import/no-anonymous-default-export
- エクスポートされたものの内
- localComponents: エクスポートされないコンポーネントでは機能しないのでエラー
- noExport: エクスポートされないコンポーネントでは機能しないのでエラー(判定は上記と同一だが、エクスポートの有無でメッセージが変わる)
eslint-plugin-react-refresh上記ルールの挙動:eslint online playground
他の命名lintルールと合わせれば
- Jsxファイルでは(指定する例外を除き)パスカルケース要素のみエクスポートを許可する
- Jsxファイル内ではエクスポートされないパスカルケース要素を許容しない
みたいな
Discordに投げたら勧められたので議論を開いた
ルールを作る際に参考にする箇所
とりあえず議論が進むまで試しで実装
ちまちまやってるけどルール自体は作れそう
構文木情報
ここでJsExportDefaultExpressionClause
がAnyJsExpression
なの嫌すぎるな。
classが入る時とか一意じゃない気がする。
export function Component() {};
export const Aa = 'a'
これエラー出さないようにできるけど出せるようにしないとまずいのかな
Viteの挙動見る限り、無視してよさそう
ClassComponentを禁じるパッケージ
import/no-anonymous-default-exportの実装の話を投げたらちょっとわちゃわちゃしている。
俺としてはぶっちゃけnoDefaultExportでよかったな。失敗
でもあると便利そうだし……わっかんね
eslint-plugin-reactから拒絶された理由は以下のような感じだった
とりあえずnoAnonymousDefaultExportを実装してみたり。Rustには慣れてきたかな(採用できるかはシラネ)
議論の催促の仕方が分からん
ついに作ったissue
ついに作ったプルリクテスト書けと言われたけどfactoryどうなってんねん
テストはsyntaxを構築しなきゃいけないけど、そこで使われてるnode_factory.rsは自動生成。
buildとか欲しいんだけど。
以下のコードで生成されているらしい。
勘違いしてたけど、js.ungramがオリジナルなんだ
オプショナルが無いとBuilderは発生しないっぽい
1.9.2にて無事リリースされた模様
ルール作るコマンド
$ just new-js-lintrule noHoge
フォーマット・リント・テスト・コード生成
$ just f
$ just l
$ just t
$ just test-lintrule noHoge
$ just gen-lint