コードリーディングをする過程で、Prettierのデバッグ方法についてもなんとなくわかってきたのでまとめておく。
Playground
左のサイドバーにDebug
のセクションがあって、
- show AST: 指定したパーサーから出力されるAST
- show comments: コメントがASTにどう紐づけられたのかの詳細
- show preprocessed AST: そのASTに対して、
comments
を各ノードに生やしたAST - show doc: そのASTから生成されたDoc
というのをそれぞれ見られる。
あらゆるオプションを有効にすると、右ペインが7窓になってなかなかに壮観である。
あとは、パーサーの中からdoc-explorer
というものを選ぶと、Doc自体を解析できる特別なモードになる。
const a = 42;
を入力した場合、これがDocで、
[
group(["const", " ", group([group("a"), " =", " ", "42"]), indent([]), ";"]),
hardline,
]
こっちが解析されたDocのAST。
[
{
"type": "group",
"contents": [
"const",
" ",
{
"type": "group",
"contents": [
{
"type": "group",
"contents": "a",
"break": false
},
" =",
" ",
"42"
],
"break": false
},
{
"type": "indent",
"contents": []
},
";"
],
"break": false
},
[
{
"type": "line",
"hard": true
},
{
"type": "break-parent"
}
]
]
DocはJSONではなくただのJavaScriptなので、って感じかね。
CLI
--help
しても出てこないけど、隠しフラグがある・・・!
--debug-benchmark
: ベンチマーク実行してくれる--debug-check
: 2重に整形して、差分があるかチェックする--debug-print-ast
: 指定されたパーサーから出力されるASTを表示--debug-print-comments
: コメントのASTへの紐づけ結果を表示--debug-print-doc
: Doc(ASTじゃないほう)へ変換した結果を表示--debug-repeat N
: N回実行して、平均実行時間を取ってくれる
いくつかのフラグについては、CONTRIBUTING.md
にも記載がある。
https://github.com/prettier/prettier/blob/3.3.3/CONTRIBUTING.md
ちなみに、これらのフラグをさばいてるコードはこのへん。
API
ってことは、生のAPIもあるんじゃろ?ってことで探すとビンゴだった。
Playgroundでも使われた。
https://github.com/prettier/prettier/blob/3.3.3/website/static/worker.js
通常版でもstandalone
版でも、__debug
というオブジェクトがこっそり公開されてる。
parse(text, options)
: 指定したパーサーで出力したASTと元コードを返すformatAST(ast, options)
: ASTを渡すと整形後の文字列を返すoptions.originalText
が生えてることが必須(なんでやねん)
printToDoc(text, options)
: ASTを渡すとDoc ASTを返すformatDoc(doc, options)
: Doc ASTを渡すとDocを返す__js_expression
という内部的なパーサーを強制的に使用するらしく、それが(なぜか)実装されているbabel
のプラグインが必要- Playgroundの
doc-explorer
のインプットと同じ形式
printDocToString(doc, options)
: Doc ASTを渡すと整形後の文字列にして返す
全部まとめるとこういうイメージ。
import { __debug } from "./standalone.js";
import * as babelPlugin from "./src/plugins/babel.js";
import * as meriyahPlugin from "./src/plugins/meriyah.js";
import * as estreePlugin from "./src/plugins/estree.js";
const plugins = [estreePlugin, babelPlugin, meriyahPlugin];
const source = `
// 1
let a
= 42 // 2
`.trim();
for (const parser of ["meriyah"]) {
console.log(`🦄 ${parser}`);
const options = { parser, plugins };
const parsed = await __debug.parse(source, options);
console.log(JSON.stringify(parsed, null, 2));
const f = await __debug.formatAST(parsed.ast, {
...options,
originalText: parsed.text, // 🤔
});
console.log(f);
const doc = await __debug.printToDoc(source, options);
console.log(JSON.stringify(doc, null, 2));
const ff = await __debug.formatDoc(doc, options);
console.log(ff);
const fff = await __debug.printDocToString(doc, options);
console.log(fff.formatted);
}
というわけで、結局すべての道はDocに通ずるのであるなあ。
__debug.parse()
の隠しオプション
秘密の第3引数が存在してた・・。
preprocessForPrint: boolean
: Doc化の直前に行うコメントの紐づけまでやるようにするmassage: boolean
: デバッグに不要な情報を削ってる?
ちなみに、同時に利用することはできない。