treefmtで統一的なフォーマッタを実現する
はじめに
treefmtというフォーマッタの上に被せるCLIが存在し,これによってあらゆるフォーマッタを統一的に扱うことが出来ます.
つまり
-
Prettier
- TypeScript, JavaScript, Markdown, JSON, CSS, etc...
-
dprint
- TypeScript, JavaScript, Markdown, JSON, etc...
-
nixpkgs-fmt
- Nix
-
alejandra
- Nix
-
taplo
- Toml
-
fourmolu
- Haskell
-
cabal-fmt
- Cabal
-
satysfi-formatter
- SATySFi
-
ocamlformat
- OCaml
-
rustfmt
- Rust
-
elm-format
- Elm
-
black
- Python
-
gofumpt
- Go
-
yamlfix
- YAML
-
dhall format - etc...
これらのフォーマッタが
$ treefmt
というコマンド一発で,指定した全てのファイルに作用させることができます.
具体的な方法
treefmtは設定ファイルをtreefmt.tomlとします.
具体的に,この記事の置き場のtreefmt.tomlを見てみましょう(ここにある)
今,プロジェクトのフォーマッタとしてalejandra(Nixのフォーマッタ)とdprint(Rustで実装された高速なWebフロントエンド系フォーマッタ)がインストールされた状況とします.
[formatter.alejandra]
command = "alejandra"
includes = ["*.nix"]
[formatter.dprint]
command = "dprint"
options = ["fmt"]
includes = ["*.json", "*.toml", "*.md"]
例えば,上の設定ファイルのdprintの場合だとdprint fmt ./package.jsonと渡されます.(commandとoptionsに注意!)
その他上には書いていませんがexcludes = []で個別に除外するファイルなども指定できます.(もちろんフォーマッタ側で指定しても良い.例えば.prettierignoreなどを使って)
これを置いた状態で,treefmtを叩くと…
$ treefmt
[INFO]: #alejandra: 2 files processed in 4.58ms
[INFO]: #dprint: 7 files processed in 41.84ms
traversed 11 files
matched 9 files to formatters
left with 9 files after cache
of whom 1 files were re-formatted
all of this in 44ms
プロジェクト以下の*.nix,*.json,*.toml,*.mdファイルに適応されました.
2回目移行は,変更されたファイルのみに適応されるようにtreefmt側がよしなにやってくれます.
注意するべきことは,フォーマッタが in-place な整形に対応していることが前提です.つまりprettier --writeなど,ファイルパスを受け取ったら整形結果を上書きするような動作,または動作オプションが必要です.[1]
さて これだと あまり旨味が 少ないので…
Custom Local Formatters
私はVSCodeユーザなので,Custom Local Formattersを使って,treefmtとうまく統合させます.
この拡張機能は,関連付けられたファイル[2]に対してカスタムなフォーマッタ(標準入力を受け取って,標準出力に整形結果を返すコマンド)を当てられるようになります.
プロジェクトルートの.vscode/settings.jsonに,下を置く.
{
"customLocalFormatters.formatters": [
{
"command": "treefmt -q --stdin ${file}",
"languages": [
"json",
"jsonc",
"md",
"nix",
"toml"
]
}
],
"[toml]": {
"editor.defaultFormatter": "jkillian.custom-local-formatters"
},
"[nix]": {
"editor.defaultFormatter": "jkillian.custom-local-formatters"
},
"[json]": {
"editor.defaultFormatter": "jkillian.custom-local-formatters"
},
"[jsonc]": {
"editor.defaultFormatter": "jkillian.custom-local-formatters"
},
"[md]": {
"editor.defaultFormatter": "jkillian.custom-local-formatters"
}
}
これによって,関連付けられたファイルにtreefmtが走ってくれるようになります.
これをする利点は,その言語専用の拡張機能が無くても,あるいは,あったとしてもフォーマット機能がなくても,あるいは,フォーマッタを自由に選択することが出来ない拡張機能であったとしても,開発者は自由なフォーマッタを当てることが出来ます.やったね!
おわりに
ロマンなので別にここまでしなくてもいいと思う
ラフな質問
YAMLのフォーマッタってPrettierとyamlfix以外にありませんか?あったらコメントで投げてみてください.
付録.1: 個人的なtreefmt.toml全部詰め合わせ
こっから削ったりしてます
[formatter.alejandra]
command = "alejandra"
includes = ["*.nix"]
[formatter.dprint]
command = "dprint"
options = ["fmt"]
includes = ["*.json", "*.toml", "*.md"]
[formatter.rust]
command = "rustfmt"
includes = ["*.rs"]
[formatter.dhall]
command = "dhall"
options = ["format"]
includes = ["*.dhall"]
[formatter.fourmolu]
command = "fourmolu"
options = [
"--ghc-opt",
"-XImportQualifiedPost",
"--ghc-opt",
"-XTypeApplications",
"--mode",
"inplace",
"--check-idempotence",
]
includes = ["*.hs"]
[formatter.cabal-fmt]
command = "cabal-fmt"
options = ["--inplace"]
includes = ["*.cabal"]
[formatter.yamlfix]
command = "yamlfix"
includes = ["*.yml", "*.yaml"]
[formatter.ocamlformat]
command = "ocamlformat"
options = ["--inplace"]
includes = ["*.ml"]
[formatter.satysfi-fmt]
command = "satysfi-fmt"
options = ["--write"]
includes = ["*.saty"]
付録.2: Nixで頑張る
TODO:
-
実は出来ないこともないが,結構ハッキーな方法をする. ↩︎
-
settings.jsonで[json],[javascriptreact]など書く,アレ. ↩︎
Discussion