oxc/crates/oxc_prettier at main · oxc-project/oxc https://github.com/oxc-project/oxc/tree/main/crates/oxc_prettier
実はOXCにもあるんですよFormatter、WIPやけど。
oxlint
と違ってバイナリも公開されてないし。
あらすじ
最新情報がわかるIssueはこちらに。
Rework
oxc_prettier
· Issue #5068 · oxc-project/oxc https://github.com/oxc-project/oxc/issues/5068
ざっと書いておくと、現状のコードは、氏が例のPrettierチャレンジの時にざっと書き上げた状態のままなので、それをなんとか形にしたいね〜という話。
これまでの流れとしては、
- まずPrettierのコードを読んでみる
- 読んだ結果、ものすごい労力が必要だとわかった
- もしやPrettierの100%互換を目指すつもり?
- 互換性は高いほど良いけど、100%である必要はない
- コメントまわりめちゃめちゃ頑張らないと無理やし・・
- 長い目でみたとき、独自のカスタマイズができるとなお良い
biome_formatter
のコード、使えたりしない?- 調べてみたけど難しそうだったわ
- やっぱり独自でがんばるしかないな〜
って感じ。
で、Biomeの資産が使えないことがわかった今、どうリスタートする?何から着手する?ってのを、まさに今考えてるところ。
兎にも角にも情報が欲しいので、まずは現状を把握していきたいというわけ。(書いておかないと、フルタイムでやってるわけじゃないからすぐ忘れちゃう)
Prettierとの互換性
いちおう、Prettierのsnapshotと比較してるテストがある。
https://github.com/oxc-project/oxc/tree/main/tasks/prettier_conformance
最新の値だと、JS: 39%, TS: 34%という感じ。
ただBiomeのそれと比べると、計算方法もignoreしてるファイルも違うので、なるほどな〜くらいで。 (そのうち揃えたいとは思う)
コードの構成
では本題へ。
/oxc/crates/oxc_prettier/src
├── printer
│ ├── command.rs
│ └── mod.rs
├── format
│ ├── array.rs
│ ├── arrow_function.rs
│ ├── assignment.rs
│ ├── binaryish.rs
│ ├── block.rs
│ ├── call_arguments.rs
│ ├── call_expression.rs
│ ├── class.rs
│ ├── function.rs
│ ├── function_parameters.rs
│ ├── misc.rs
│ ├── mod.rs
│ ├── module.rs
│ ├── object.rs
│ ├── property.rs
│ ├── statement.rs
│ ├── string.rs
│ ├── template_literal.rs
│ └── ternary.rs
├── comments
│ ├── mod.rs
│ └── print.rs
├── lib.rs
├── options.rs
├── doc.rs
├── macros.rs
├── needs_parens.rs
├── binaryish.rs
└── utils
├── document.rs
└── mod.rs
tree
してちょっと並べ替えたものがこちら。
ファイル数だけで見るとぜんぜん少なくて、これまで見てきたのに比べたらかわいいもんよ。
lib.rs
Prettier
structがあるbuild(parsed.program)
という形式で、OXCのASTを受け取って、整形後String
を返す- 各ノードの
format()
が呼ばれるとき、必ず渡される - いわばコンテキストのような状態としても使われる
stack
というVec<AstKind<'a>>
があるenter_node(kind)
とleave_node()
によって、処理の途中で親を遡れるようになってる
source_text
を使って、has_newline()
のようなユーティリティも提供する
もう少し役割ごとに正規化できそうな感じはするけど、まあ。
options.rs
Prettier同様のオプションが定義された、PrettierOptions
structがある。
特記事項なし。
comments/
mod.rs
には、Comment
という、PrettierやBiomeでいうDecoratedComment
相当がある、が、WIPである。
というか、現状はどこでも使われてない。
print.rs
にはそんなコメントをIRに変換するコード片はあるけど、中身は空っぽという感じ。
なんかのタイミングで、中途半端なコードをいったん決してなかったことにする!ってなってた記憶がある。
doc.rs
- Prettierの
Doc
相当がある- ただPrettierが定義してるすべてが揃ってるわけではない
- align, trim, line-suffix-boundary, labelあたりがない
- 省略してるというより、単に未着手な雰囲気
DocBuilder
trait- lib.rsにあった
Prettier
structが実装してる - 本体のASTと同じく、
oxc_allocator
を使ってDocを積むために必要な諸々 join()
というDoc
を結合するやつだけ、少し毛色が違う
- lib.rsにあった
print_doc_to_debug()
とfmt::Display
- デバッグ用に、PrettierのDocCommandのフォーマットに表示できる
macros.rs
- 基本的には、Prettierでいう
builders
相当- IRを生み出すためのmacroが定義されてる
- しかし実態としては、macroを使わず直接
Doc::
で書かれてるコードも多い - macroである必要性は不明だが、Biomeみたいにただの関数にしてもいいのかも
string!
は必要・・・?
wrap!
というmacroだけは特別で、BiomeでいうFormatNodeRule
のfmt
のような存在
#[macro_export]
macro_rules! wrap {
($p:ident, $self:expr, $kind:ident, $block:block) => {{
let kind = AstKind::$kind($p.alloc($self));
$p.enter_node(kind);
let leading = $p.print_leading_comments(kind.span());
let doc = $block;
let doc = $p.wrap_parens(doc, kind);
let trailing = $p.print_trailing_comments(kind.span());
let doc = $p.print_comments(leading, doc, trailing);
$p.leave_node();
doc
}};
}
Doc
化の要というやつか。
needs_parens.rs
wrap!
macroでも呼ばれてるwrap_parens()
の実装があるPrettier
に対して実装してる
- その
AstKind
ごとに、(
と)
が必要かどうかを判定する長大な処理- Prettierもこのスタイル
- Biomeはそれぞれのノード側で定義してたので、そういう風にもできるのであろう
- BiomeのAST/CSTだからこそ可能なのかもしれんけど不明
binaryish.rs
BinaryOperator
とLogicalOperator
をまとめてBinaryishOperator
と呼んでる。
そのまま、BinaryExpression
とLogicalExpression
を処理するときに使われる便利なもの。
utils/
現状、document.rsのwill_break()
という関数だけが存在する。
Prettierにも同名のユーティリティがあるけど、挙動はちょっと違う・・?
format/
整形処理の本丸であるmod.rsと、その他のノード種類ごとの処理がある。
まずmod.rsから。
- mod.rsだけで3000Lもある
Format
traitがあり、自身のDoc
を返すformat()
を実装する- 現状でも、OXC ASTとして存在するstructやenumのほぼすべてに対して
Format
が実装されてる- TSまわりなど、漏れてるやつもある
Format
は実装されてるが、その中身はとりあえずってやつも多い
wrap!
を使わず手動でenter_node()
してるものもある- macroも使ったり使わなかったり
あとはそれ以外のファイル。
- ファイル構成は、Prettierのlanguage-js/print配下に似てる
- ほとんど同じだが、リテラルまわりだけ差異
- 足りてないものもある
- 実装はまちまち
- Prettierをそのままコピーしてるのもあれば
- リファクタした風になってるものもあり
- WIPなのか簡略化されてたり
これだけコードがあって、40%にも届かんのか〜って感じ。
むしろこうなってくると気になるのは、それぞれの完成度がどれほど?ってところかな。 1つずつ見て、Prettierのコードと見比べないと、何がDONEで何がTODOかがわからない。
printer/
- ネストを含む
Doc
を受け取り、String
にする - Prettierはネストを再起で処理してたところ、フラットなループでやってる
- command.rsにある
Command
structでループ - breakモードと、インデントの情報を状態として管理
- command.rsにある
Doc
の種類ごとに、handle_xxx()
する- お馴染みの
propagate_breaks()
やfits()
もある
その他のローカルな変数もPrinter
が抱えてるけど、ちゃんとPrettier準拠になってるように見える。
ので、これもまた完成度を測るところからかな。
まとめ
というわけで・・・、
- 名の通り、Prettierクローンを地で行く実装
- しかし細かいところの完成度はまちまち
- 具体的にどこに過不足があるかも不明
というのが現状。
この先どうするかは今から考えるけど、どこまでPrettier or Biomeに寄せるのかが悩ましい。
Biomeの設計は綺麗やけど、AST/CSTの構造がぜんぜん違うので、細かい実装では参考にできないであろう懸念があるのと、普通にその内容を理解するのに時間がかかりすぎる気がする。
Prettierは、扱うASTに差がないことと、コードは良くも悪くもJSではあるけど、デバッグが圧倒的にやりやすい。
というわけで、やっぱり基本的にはPrettierに寄せつつ、リファクタの一環としてBiomeのエッセンスを取り入れる・・・みたいなバランスがいいのかな〜。