バージョンは、現時点で最新の1.9.4を。
Release CLI v1.9.4 · biomejs/biome https://github.com/biomejs/biome/releases/tag/cli/v1.9.4`
CLIから本丸まで
この辺りはなんとなく想像つくので、ざーっと読み流してしまう。
まずはmain()から。
Biomeはオールインワン!ということで、npx @biomejs/biome formatみたくサブコマンドでFormatterが起動する。
というあたりはRustのCLI界隈ではお馴染みのbpafでやってた。
pacak/bpaf: Command line parser with applicative interface https://github.com/pacak/bpaf
設定はこのあたり。
で、そのあとは・・・、
- biome_cli:main.rs:
main()run_workspace(console, command)CliSession.run(command)
- biome_cli:lib.rs:
run_command(session, cli_options, command)CommandRunner.run(session, cli_options)
- biome_cli:commands/mod.rs:
execute_mode(execution, session, cli_options, paths) - biome_cli:execute/mod.rs:
traverse(execution, session, cli_options, paths) - biome_cli:execute/traverse.rs:
traverse_inputs(fs, inputs, ctx)TraversalScope.evaluate(context, path)handle_path(path)handle_file(path)
- biome_cli:execute/process_file.rs:
process_file(ctx, biome_path) - biome_cli:execute/process_file/format.rs:
format(ctx, path)format_with_guard(ctx, workspace_file)
- biome_service:workspace.rs:
format_file()Workspace.format_file(params)
- biome_service:workspace/server.rs:
WorkspaceServer.format_file(params)get_file_capabilities(biome_path): このファイルはどういう言語があり、どういうツールセットを対応させるかなどget_parse(biome_path): ここで対応したparserによってCSTができる
- biome_service:file_handlers/mod.rs:
get_capabilities(biome_path, language_hint) - biome_service:file_handlers/javascript.rs:
capabilities()biome_js_parserでパースしてbiome_js_formatterでフォーマットして・・・などが定義されてる
- biome_service:workspace/server.rs:
WorkspaceServer.format_file(params)format(path, document_file_source, parse, workspace)
- biome_service:file_handlers/javascript.rs:
format(biome_path, document_file_source, parse, settings)format_node(options, tree)
- biome_js_formatter/lib.rs:
format_node(options, root)
とまぁ、こんな感じでメインの整形処理へ。さすがの重厚さであるな。
コード文字列からCSTへの変換、Formatter側ではなくもっと手前でやってるのね。
パスごとに結果が保存されるようで、同じパース結果をLinterとか他の処理でも使うんかね?
biome_formatterとbiome_js_formatterの関係
ここまで読んできたとおり、
biome_serviceで、各言語ごとの対応ツールが決まるbiome_js_parser(およびbiome_js_syntax)とbiome_js_formatterはそこで指定されてる
biome_js_formatterは、biome_formatter::format_node(root, language)を呼び出す
という流れになっていて、 各言語側としては、CSTのノードごとのAsFormatや、そのFormatLanguageやら各種Traitを実装しておく必要がある。
ということが、実はガイドにも書いてあった。親切。
詳細はひとまず置いておくけど、この流儀に従って、Biome本体にはhtml / css / js /json / glaphql / gritのフォーマッタが実装されてるとのこと。
見た感じ、Biome内で扱ってる言語共通のインフラではあるけど、Biomeの外の世界に対してもオープンなのか?ってのはまだわかってない。
このガイドや型をみる限り、biome_rowanというcrateを使ったCSTやらのみを想定してるようにも見える。
biome_formatter
フォーマッタとしてのインフラという立ち位置。
FormatElementというIRの素を定義していて、それで整形処理を行う- https://github.com/biomejs/biome/blob/fa93a147abe64e9c85908d317a8dd1de343ad132/crates/biome_formatter/src/format_element.rs#L19
- いわゆる
buildersもprelude.rsから公開されてて、各言語側はそれを使う - PrettierのDocコマンドとはまた似て非なる感じで、
Tagという特別な目印?もあった
- format_element/document.rs
impl FormatでIRの文字列化をやってるwrite!やformat!といったmacroを使ってる- https://github.com/biomejs/biome/blob/fa93a147abe64e9c85908d317a8dd1de343ad132/crates/biome_formatter/src/macros.rs
IndentStyleみたいなオプションも定義してるけど、値を用意するのはbiome_serviceの各言語側の設定っぽい- comments.rs
- Prettierでも見たような
DecoratedCommentがいた - https://github.com/biomejs/biome/blob/fa93a147abe64e9c85908d317a8dd1de343ad132/crates/biome_formatter/src/comments.rs#L253
- Prettierでも見たような
己のRust力が低すぎて、Traitやらmacroやらが入り混じったコードをうまく追えなくてつらい!
まあでも共通機能を提供する層だからこその書き方なんやろうとも思うし、あまり深追いはしないでおく。 Rustだからこその制約でこうなってるみたいな記述もあったし。
ともあれ、各言語側はノードと対応する整形処理を用意して、biome_formatter::format_node(root, language)を呼べばOKってことか。
pub fn format_node<L: FormatLanguage>(
root: &SyntaxNode<L::SyntaxLanguage>,
language: L,
) -> FormatResult<Formatted<L::Context>> {
// ...
この引数の型はこう。
root:biome_rowan::SyntaxNode<biome_rowan::Language>structlanguage:biome_formatter::FormatLanguagetrait
やっぱり、RowanもといCSTなツリーを受け取ることを想定してそうである。
https://github.com/biomejs/biome/tree/fa93a147abe64e9c85908d317a8dd1de343ad132/crates/biome_rowan
biome_js_formatter
インフラを利用する各言語側。
- lib.rs
FormatNodeRuleのfmt(node, formatter)が入口か- https://github.com/biomejs/biome/blob/fa93a147abe64e9c85908d317a8dd1de343ad132/crates/biome_js_formatter/src/lib.rs#L344
fmt(node, f)- 以下を順に呼んでる
fmt_leading_comments(node, f)fmt_node(node, f)fmt_dangling_comments(node, f)fmt_trailing_comments(node, f)
fmt_node(node, f)- Prettierでもお馴染みの
needs_parantheses(item)からのfmt_fields(node, f)
- Prettierでもお馴染みの
- syntax_rewriter.rs
transform(root)という関数を公開していて、これはbiome_formatter側でformat_node()時に呼ばれる- https://github.com/biomejs/biome/blob/fa93a147abe64e9c85908d317a8dd1de343ad132/crates/biome_formatter/src/lib.rs#L1368
- CSTに対する前処理で、Prettierでもやってたやつ
AnyJsParanthesizedとJsLogicalExpressionの2種類を置換してる- https://github.com/biomejs/biome/blob/fa93a147abe64e9c85908d317a8dd1de343ad132/crates/biome_js_formatter/src/syntax_rewriter.rs#L53
- comments.rs
- Prettierでもみたコメントのplacementに関するコードがあった
- Rustになって読みやすくはなったが、やはりこの数に圧倒される
- https://github.com/biomejs/biome/blob/fa93a147abe64e9c85908d317a8dd1de343ad132/crates/biome_js_formatter/src/comments.rs#L98
あとは、jsとjsxとtsディレクトリ配下に、すべてのCSTノードに対してfmt_fields(node, f)やneeds_parantheses(item)を実装するコードがあるだけ。
だいたい400ノードくらいに対して、FormatNodeRuleを実装するコードがある。400て!
fmt_fields(node, f)内で見つかった子ノードは、その子ノードのformat()をその都度呼び出すことで、再起で処理されていく。
needs_parantheses()は、そもそもCSTのノードを定義する側に実装があるようだった。
コメントに関するfmt_xxx_comments()も個別に実装の必要があれば、biome_formatter側でformat_xxx_comments()みたいなのが公開されてるのでそれを使う。
ノードごとの対応が必要ない場合は、大元でやってもらえるのに任せておけばOKという感じ。
おわりに
Prettier互換というだけあってか、やってることの流れはPrettierと一緒だなって印象だった。 他言語向けへのインフラとしての機能や、その取り回しなんかは圧倒的にクリーンになってるけど。
OXCでのbiome_formatter利用は望み薄かなと思いつつも、次回はもう少し整形処理の詳細を見ておく。