AST変換関数を作るのは簡単。 だが、実際に変換する際の入力はコードであり、コードを入力とする場合にパーサが必要となり、 最終的な出力はコードであるため、ジェネレータが必要となる。
parse(コード) -> AST -> AST変換関数(AST) -> AST -> generate(AST) -> コード
AST変換関数を使ったものは、基本的にはコードを入力としコードを出力とする流れがあり、 それぞれのAST変換関数ごとにこれを提供するのは大変となる。
なので、
parse(コード) -> AST -> AST変換関数(AST) -> AST -> generate(AST) -> コード
太字の部分を提供出来るようにするのが目的。
つまりAST変換関数を補助するツール。
類似プロジェクトとして、asterjs/asterが存在し、asterはGruntやGulpのようにビルドツールとして動いてる。
ビルドツールである場合に、ユーザがそのビルドツールに依存しなければならないため、求めるものとは少し異なる。
"コードを入力としてコードを出力するものを簡単に作れる" という事ができれば目的は達成できるのではないかと思う。
ただ、最近はesprima、espree、acorn、Babylonなど色々なパーサが存在するため、 ESTreeレベル(現在だとES6レベル)だと互換性はあるが、ES.nextやJSXといた拡張に対する扱い、ASTではなくCSTレベルの互換性は存在していない。
そのため、コード -> ASTへと変換した際に、そのAST変換関数が求めるASTであるかどうか、 厳密にはCSTに依存したものであるかという条件を知り得ないと"コードを入力としてコードを出力するものを簡単に作れる"とならないAST変換関数が存在する。
- コメントが入ったASTが欲しい
- token(CST)が入ったものが欲しい
- ES.nextに対応したASTで欲しい
- これは下位互換があるので、AST変換関数はバージョンは気にしなくて良いはず
- ASTとしてvalidであるならばAST変換関数はASTにおいてエラーはほぼない(変換できないだけ)
つまり?
- パーサ的にあるオプションが設定されていて欲しい
- このパーサを使って欲しい
という条件が設定されたパーサでパースされた結果をASTが求めるものであるとかんがえる。
AST変換関数同士を組み合わせるときに考える問題
- 変換の順番
- 正常なASTであること
- 変換中にそのAST変換関数が求めるASTとなっているかどうか
変換の順番はユーザ/AST変換関数を組み合わせる開発者が決めるべき問題である。
そのため複数のAST変換関数を組み合わせというものは、以下のように変換毎にvalidateを行えば大体解決できるはず。
validateAST(bTransform(validateAST(aTransform(AST))))
AST変換関数が直接パーサを指定すると何が問題なのかというと、 ユーザーが与えたコードをパースできないという問題が起きる可能性があるためである。
例えば、JSXを使ったコードをesprimaに渡すとパースエラーとなり、そのAST変換関数はそのコードを変換できないという事になってしまう。
何でもパースできるだろうという過程でBabylonを使えばいいというわけでもなく、先ほど書いたようにCSTにおける互換性はないため、babel/babel-eslintのようにtokenに関する変換が必要になるかもしれない。
しかし、結局受け入れるのユーザが書いたコードであるためそれがパースできれば問題ないと思える。
つまり、AST変換関数を補助するツールに求められるものは、
- ユーザのコードをパースできること
- AST変換関数が必要としてる情報がASTに含まれてる事を保証すること
現状ではESTreeというデファクトが存在し、そのコードがESTreeで扱えないものであるならば、何らかのTranspileが必要となるはずである。
つまり、そのプロジェクトにはTranspileの依存が含まれているとかんがえる
- babelがあるなら -> babelをパーサとして使う
- それ以外?
- それ以外であるならばesprimaでパースする
つまりBabelはpeerDependenciesに落とすことができるはず。
parseOptions
var AST = parse(code, options);
var modifiedAST = transform(AST);
generate(AST)
=> parse
var output = new Source(code, options)
.transform(transform)
.output();
var code = output.code;
var sourcemap = output.map;
// == equel
var source = new Source(options);
source.input(code);
source.transform(transform);
source.output();