Closed6

Biome コードリーディング

nissy-devnissy-dev

CLI の最初から読んでいく

biome check の entry

https://github.com/biomejs/biome/blob/main/crates/biome_cli/src/commands/check.rs

execute_mode がメインの処理。

https://github.com/biomejs/biome/blob/af24597c1877c7b5a96bb7cc59bab655a577116f/crates/biome_cli/src/execute/mod.rs#L218

exeute はさらに traverse を呼び出している。

https://github.com/biomejs/biome/blob/af24597c1877c7b5a96bb7cc59bab655a577116f/crates/biome_cli/src/execute/traverse.rs#L58

traverse は複雑だけど、メインの処理は thread::scope で囲われている traverse_inputs の処理

https://github.com/biomejs/biome/blob/af24597c1877c7b5a96bb7cc59bab655a577116f/crates/biome_cli/src/execute/traverse.rs#L90

thread::scope で囲われているから複数スレッドで traverse していそう。rayon で threadpool 作っていた。

https://github.com/biomejs/biome/blob/af24597c1877c7b5a96bb7cc59bab655a577116f/crates/biome_cli/src/execute/traverse.rs#L230-L238

rayon というライブラリを使ってた

https://github.com/rayon-rs/rayon

nissy-devnissy-dev

traverse_inputs が難しい

https://github.com/biomejs/biome/blob/af24597c1877c7b5a96bb7cc59bab655a577116f/crates/biome_cli/src/execute/traverse.rs#L242-L252

引数の fs は dyn なので動的ディスパッチ。fs.traversal は、説明は次のとおり

This method creates a new "traversal scope" that can be used to efficiently batch many filesystem read operations

ファイルを読み込んで traverse のスコープを生成する関数ってことかな。fs の実装は、OsFileSystem と MemoryFileSystem がありそう。

scope.spawn はスコープの生成を実行してそうで、これも実際の実装は OsTraversalScope や MemoryTraversalScope にある。

https://github.com/biomejs/biome/blob/72c2bee26bf6db747426613ff74cb68f66148692/crates/biome_fs/src/fs/os.rs#L108-L151

ここでは rayon のスコープを生成しながら ctx.handle_file を実行している。
ctx は TraversalOptions のことで、実際の handle_file は次の処理になっている。

https://github.com/biomejs/biome/blob/72c2bee26bf6db747426613ff74cb68f66148692/crates/biome_cli/src/execute/traverse.rs#L707-L732

ここでよんでいる process_file は次のとおり。コメントにも書かれているとおり、ここが実際のファイルを読み込んだり、パースしたりしている箇所らしい。確かに、 lint / check などを関数を実行していて、次はこれ読んでいくのが良さそう。ここまで来るのが長かった。

https://github.com/biomejs/biome/blob/72c2bee26bf6db747426613ff74cb68f66148692/crates/biome_cli/src/execute/process_file.rs#L108-L233

nissy-devnissy-dev

check 関数を見ていく

https://github.com/biomejs/biome/blob/main/crates/biome_cli/src/execute/process_file/check.rs

ここでは lint_with_guard / format_with_guard などを呼んでいる。

lint_with_guard 関数を見ていく。みると guard の pull_diagnostics とかを呼んでいそう。

https://github.com/biomejs/biome/blob/main/crates/biome_cli/src/execute/process_file/lint.rs

guard の実体は FileGuard

https://github.com/biomejs/biome/blob/72c2bee26bf6db747426613ff74cb68f66148692/crates/biome_cli/src/execute/process_file/workspace_file.rs#L37

FileGuard の実装は workspace の pull_diagnostics を呼んでいる。

https://github.com/biomejs/biome/blob/72c2bee26bf6db747426613ff74cb68f66148692/crates/biome_service/src/workspace.rs#L667-L677

この workspace は実装を辿っていくと、cli の main.rs で生成されている workspace だった。

https://github.com/biomejs/biome/blob/72c2bee26bf6db747426613ff74cb68f66148692/crates/biome_cli/src/main.rs#L65-L73

ということで pull_diagnostics の本当の中身はここにある。

https://github.com/biomejs/biome/blob/72c2bee26bf6db747426613ff74cb68f66148692/crates/biome_service/src/workspace/server.rs#L458-L524

nissy-devnissy-dev

pull_diagnostics の中身を読む。言語に依存しない処理になっている。

get_parse では GreenNode を含む AnyParse という構造体を返していそう。

https://github.com/biomejs/biome/blob/8a5f2792f25ffd4184ed6464fd1d9ae98fa2216f/crates/biome_parser/src/lib.rs#L845-L849

https://github.com/biomejs/biome/blob/72c2bee26bf6db747426613ff74cb68f66148692/crates/biome_rowan/src/syntax/node.rs#L813-L817

lint は LintParams を受け取って、LintResults を返す関数。

parse も lint も関数の実態は、JSだと以下のファイルに実装されている。

https://github.com/biomejs/biome/blob/main/crates/biome_service/src/file_handlers/javascript.rs

parse は parse_js_with_cache が根本の処理。event、trivia、error の配列を parse 関数がまずは返していそう。events を使って green node (tree) を構築している。

https://github.com/biomejs/biome/blob/32d40cffc4f57c18a69a2f9ee82a691839453da9/crates/biome_js_parser/src/parse.rs#L260-L273

lint は analyze が根本の処理で、さらにたどると以下の関数に辿り着く。add_visitor してたりして、いかにもという感じのコード。

https://github.com/biomejs/biome/blob/32d40cffc4f57c18a69a2f9ee82a691839453da9/crates/biome_js_analyze/src/lib.rs#L57-L142

nissy-devnissy-dev

lint が実際に実行するまでの流れを次に理解する。

parse.tree() で AST を構築してそう。構築のロジックはあんまりわからず... SynatxNode をツリーの親から再帰的にキャストしてそう? token の kind の情報を使いながらキャストしている。 (多分)

https://github.com/biomejs/biome/blob/52dae46505805ecf87969544867c88a388df6b93/crates/biome_service/src/file_handlers/javascript.rs#L291

Parse の構造体も関係してそう。Generics の T には AnyJsRoot が渡っている。

https://github.com/biomejs/biome/blob/52dae46505805ecf87969544867c88a388df6b93/crates/biome_js_parser/src/parse.rs#L14

AST を作ったあとは、visitor の登録をしている。実際の処理は次の run 関数。

https://github.com/biomejs/biome/blob/52dae46505805ecf87969544867c88a388df6b93/crates/biome_analyze/src/lib.rs#L119

preorder でツリーを走査して、イベントに対して visitor を適用させている。

https://github.com/biomejs/biome/blob/52dae46505805ecf87969544867c88a388df6b93/crates/biome_analyze/src/lib.rs#L297-L320

ルールは visit_registry で visitor として登録されていそう。最終的に record_rules が呼ばれていて、そこで Ast の構造体に実装されている build_visitor が呼ばれてた。

https://github.com/biomejs/biome/blob/52dae46505805ecf87969544867c88a388df6b93/crates/biome_analyze/src/registry.rs#L39-L56

https://github.com/biomejs/biome/blob/52dae46505805ecf87969544867c88a388df6b93/crates/biome_analyze/src/registry.rs#L226

build_visitor では次のような visitor を登録する。

https://github.com/biomejs/biome/blob/52dae46505805ecf87969544867c88a388df6b93/crates/biome_analyze/src/syntax.rs#L61-L90

match_query が重要で、ここで対応する node に関する lint ルールが実行されていそう

https://github.com/biomejs/biome/blob/52dae46505805ecf87969544867c88a388df6b93/crates/biome_analyze/src/registry.rs#L290

nissy-devnissy-dev

難しかった... ふわっとした理解をしているところもそれなりにある。
カスタムルール対応はどうすればいんだろか... もう少し lint 周りを読んで、カスタムルールを WASM にコンパイルしたものを既存のルールと I/F 合わせる wrapper みたいなのかませたらいけるのだろうか

このスクラップは2023/11/04にクローズされました