バージョンは、現時点で最新の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::FormatLanguage
trait
やっぱり、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
利用は望み薄かなと思いつつも、次回はもう少し整形処理の詳細を見ておく。