とは言っても、あんな巨大コードベースを一気に読み切れるわけがないので、まずは大枠のイメージを掴むことを目標にしてみる。
バージョンは、現時点で最新のリリースである3.3.3
で。v4系はなんとなくやめておいた。
知りたいのはメインの整形処理だけではあるけど、いちおう大枠から見ていく。何か発見があるかもしらんし。
はじめに
- TSじゃなくてJSで書かれてるの知らんかった
- それ自体はそこまで否定的ではない派
- JSDoc TSなところと、そうでないところがある
- そして思ったより
exclude
指定されてる・・・ - 知りたい!と思ったところに型がない
- まあ型つけるのも大変そうではあるけども
- そして思ったより
おま環かもしれないが、LSPが機能しない場面もあって、コードリーディングの難易度は高めかもと思った。 LSPに慣れきった現代人の地力を見極めようとしてる?!って。(メンテしてる人たちはどうやってんの。)
いちおうd.ts
は置いてあるけど、手動管理っぽいし、コード内では参照されてなさそう?
https://github.com/prettier/prettier/blob/3.3.3/src/index.d.ts
エントリーポイント
閑話休題、まずはpackage.json
から見えてるこのあたりから。
- bin:
./bin/prettier.cjs
./src/cli/index.js
を呼んでる
- require:
./src/index.cjs
- default:
./src/index.js
- browser:
./standalone.js
./src/standalone.js
に同じ
PrettierといえばCLIなイメージが強いけど、Node.jsのAPIとして使ったり、ブラウザで動かせたり、エディタから呼ばれたり、いろんな用途もあるなあ確かに。
src/cli/index.js
https://github.com/prettier/prettier/blob/3.3.3/src/cli/index.js
その名の通りのCLI。
CLI引数を精査して、./cli/format.js
で定義したこの2つを呼び分けるだけ。
formatStdin(context)
formatFiles(context)
src/cli/format.js
https://github.com/prettier/prettier/blob/3.3.3/src/cli/format.js
標準入力にしろファイルにしろ、だいたいの流れは同じで、
- 実行条件を精査し
- ファイル名からパーサーなどオプションを準備
- ファイルからコードを読み出し
format(context, input, options)
に渡して整形
ファイルに書き出す場合、その処理があったり高速化のためにキャッシュを保存したりもしてた。
format(context, input, options)
は、隠しCLI引数である--debug-*
シリーズに応じて挙動が変わる。
(デバッグ方法についてはまた別途で調べる)
けど実態としては、./src/index.js
のformatWithCursor(input, opt)
を呼ぶだけ。
src/index.js, index.cjs
https://github.com/prettier/prettier/blob/3.3.3/src/index.js https://github.com/prettier/prettier/blob/3.3.3/src/index.cjs
似てるようで、どこか違うようで・・・。
詳細は追わないけど、.cjs
のほうで.js
をimportしてるので、.js
のほうが主体なのであろう。
さっきのformatWithCursor()
は、
./main/core.js
のformatWithCursor()
をwithPlugins()
という高階関数でラップ
したもの。
どうやらメインの整形ロジックは、./main/core.js
あたりから追っていけそう。
withPlugin()
でやってるビルトインプラグインの自動ロードが、standalone
verではなくなってる。
src/standalone.js
https://github.com/prettier/prettier/blob/3.3.3/src/standalone.js
見た感じ、./src/index.js
からNode.js依存を取り除いたバージョンとみた。
コードを見る限り、デフォルトのオプションやプラグインなども一切なさそうで、使いたいものだけ自分でセットアップして使うものっぽい。
// These file paths are based on prettier repo root
import { format } from "./standalone.js";
import * as meriyahPlugin from "./src/plugins/meriyah.js";
import * as estreePlugin from "./src/plugins/estree.js";
const SOURCE = `
let a
= 'x'
`;
const formatted = await format(SOURCE, {
parser: "meriyah",
plugins: [meriyahPlugin, estreePlugin],
});
console.log(formatted); // let a = "x";
プラグインのI/Oみたいなのも後で調べておく。
src/main/core.js
format(text, options)
というものもあるけど、これもformatWithCursor(text, options)
を呼んでるだけなので、こっちがメインの処理。
カーソル位置をよしなにする必要がない用途にはfomrat(text, options)
ということね。
https://github.com/prettier/prettier/blob/3.3.3/src/main/core.js#L268
やってるのは、
normalizeInputAndOptions(text, options)
- BOMや改行コードやらを事前に処理してる風
- この
options
は、normalizeFormatOptions(options)
を通したもの./src/main/normalize-format-options.js
- どういうパーサーを使うかなどが決まる
coreFormat(text, options)
なので、つまりはcoreFormat(text, options)
が整形処理の正体。
src/main/normalize-format-options.js
https://github.com/prettier/prettier/blob/3.3.3/src/main/normalize-format-options.js#L21
元のオプションに対して、デフォルト値を埋めていくとのこと。
inferParser(options)
っていうずばりなやつもいて、.js
ファイルに対して、パースするのにBabelを使う・・・みたいな設定はここで決まってた。
https://github.com/prettier/prettier/blob/3.3.3/src/utils/infer-parser.js#L61
手動でoptions.parser
を指定してた場合は、それが使われる。
parser
とprinter
の初期化なんかもやってるけど、ほとんどはoptions
オブジェクトをこねくり回すターンなので、あまり追ってない。
coreFormat(text, options)
https://github.com/prettier/prettier/blob/3.3.3/src/main/core.js#L25
いよいよ本丸に。
整形された文字列を手に入れるまでの流れは、
parse(text, options)
してASTに変換./src/main/parse.js
parser
の指定があったものを使う
printAstToDoc(ast, options)
でDocと呼ばれる抽象構造(IR)に変換./src/main/ast-to-doc.js
- コメントを事前にASTに対応させる
printer
の指定があったものを使う
printDocToString(doc, options)
で文字列化./src/document/printer.js
さて、言うは易し、この中身を理解したいというのが本題で・・・、次回へ続く。
プラグインの構成
最後にこれだけ。
ここに書いてあるように、
- languages
- parsers
- printers
- options
- defaultOptions
これらを実装したセットのことをそう呼んでるらしい。 ビルトインで対応してない言語をサポートするために必要な一式という感じ。
ただこれは3rd向けのルールであり、ビルトインはその限りではなさそう。
たとえば、ESTree形式のASTに対応する用であろうこのビルトインのプラグインには、parsers
がないし。
https://github.com/prettier/prettier/blob/3.3.3/src/plugins/estree.js
逆にparsers
だけ、languages
だけのやつもあった。
原理的には、実行時に必要なものがトータルで揃ってれば、プラグインという単位は別になってても良いってことね。
plugins: [meriyahPlugin, estreePlugin],
// どっちでもいい
plugins: [{ ...meriyahPlugin, ...estreePlugin }],
standalone
の使い方のページで、estree
プラグインが別途で必要って書いてあったのはそういうことかね。
https://prettier.io/docs/en/browser#prettierformatcode-options
parsers
を指定するとき、そのパーサーの出力であるastFormat
をあわせて記載しておくと、それがprinters
のキーから選ばれる。
ここまでのまとめ
- 整形処理の実体は、
coreFormat(text, options)
- 指定されたパーサーを使って、ソースコードをASTに
- 指定されたプリンターが、そのASTをDocに
- 指定された設定を元に、Docが文字列に
options
オブジェクトに気をつけろ- 場所によって中身が別物になってる
次回以降は、
coreFormat()
の詳細- Docの構造
というあたりを。