最近、気になったJavaScript AST関係のものについてのメモです。

JavaScript ASTについては以下などを見て下さい。

esnext

元々ES6 Module Transpiler等やっていたSquare社がesnextというプロジェクトを立ち上げました。

esnextTraceurと同様にES6のコードを今日のJavaScriptに変換するツールです。

@eventualbuddhaさんがメインでやっていて、それぞれのシンタックスの変換はes6-spreadのように、ひとつのモジュールとして分けて作られています。

また変換の書き方も大体統一したやり方(まあ一人で大体書かれてるので)が取られていて、
Facebookの@benjamnさんが作っているast-typesrecastを使ったものになっています。

テストはexample-runnerを使っています。
examples/*.jsにあるテストファイルを変換モジュールで変換した結果がresultsディレクトリに出力されて、それの実行結果が期待通りになるかという感じで書かれています。

それぞれが小さくシンプルに書かれているので、JavaScript ASTを使った変換モジュールを書きたい人は参考になるかもしれません。

後、この辺のES6 -> なものはaddyosmani/es6-toolsを見ると大体まとまってると思います。

CST

JavaScriptのAST(抽象構文木)に対してCST(解析木)の標準を決めようというプロジェクトがgetify/concrete-syntax-treeで議論されています。

JavaScriptのASTはParser APIによるものが大体標準といえますが、
このASTのNodeオブジェクトには、ホワイトスペースの位置やコメント、( )という文字そのもの等、含んでない情報があります。(Esprimaなどパーサーによってはtokens等で取得できるようになっていますが)

そのような情報も含めたCSTの標準を決めようというものを@getifyさんが中心になってやっています。

まだ始まったばかりで、ちゃんとConsensus取れるものになるか微妙なところです。

doctrine

JavaScript ASTを触ると必ず触れることになるコンストウェアと呼ばれるものの一つにdoctrineというJSDocパーサーがあります。

doctrine自体はJavaScript ASTに直接関係ありませんが、JSDocをパースした結果をTreeのオブジェクトとして取得することが出来ます。
(そのオブジェクトの構造はASTに似ているところはある)

ここ1-2ヶ月で@jonathanKingstonさん等が中心となって一気にJSDocのtagのサポートが増えました。

ESLintvalid-jsdocルールでも使われていて、closure-tsなどJSDocを扱ったツールが増えてくるかもしれません。

AST Query

大体の場合JS ASTを使ったものを作るときにEstraverse等の走査をする関数を使ったりすると思います。

DOMにもTreeWalkerというTraverse関数はありますが、普段ウェブ開発とかで特定のElementを取得する場合はDOM API(getElement..やquerySelector)のようなものを使うと思います。

AST Queryというライブラリは、DOM APIのような感じでASTから特定のnodeを取得したり、変更したりできるライブラリです。

DOM APIのように全てをカバーするわけではありませんが、最近0.2.0がリリースされて少しづつできることが増えてきました。

決まった名前の変数や関数を変更したい等の用途だとかなり直感的に書けるようになると思います。

var tree = program('var a = 1');
var variable = tree.var("a");
console.log(JSON.stringify(variable, null, 4));
/*
{
    "nodes": [
        {
            "type": "VariableDeclarator",
            "id": {
                "type": "Identifier",
                "name": "a"
            },
            "init": {
                "type": "Literal",
                "value": 1,
                "raw": "1"
            }
        }
    ],
    "length": 1
}
*/
console.log(variable.value());
/*
{
    node: { type: 'Literal', value: 1, raw: '1' },
    type: 'Literal'
}
*/

// ==> rename + update
variable.rename("b");
variable.value("2");

console.log(JSON.stringify(variable, null, 4));
/*
{
    "nodes": [
        {
            "type": "VariableDeclarator",
            "id": {
                "type": "Identifier",
                "name": "b"
            },
            "init": {
                "type": "Literal",
                "value": 2,
                "raw": "2"
            }
        }
    ],
    "length": 1
}
 */