何かJavaScriptのソースコードを機械的にチェックするためのツールを作りたいという場合に、JavaScriptのASTというものを触る必要が出てくると思います。

この記事では、その取っ掛かりとなる案内を簡単にまとめたものです。

ASTとは

AST

AST(Abstract Syntax Tree)はコードをパースした抽象構文木のこと。 JavaScriptの場合はJavaScriptオブジェクト(JSON)として表現されます。

コード:

var a = 1;

AST:

{
  "range": [
    0,
    10
  ],
  "type": "Program",
  "body": [
    {
      "range": [
        0,
        10
      ],
      "type": "VariableDeclaration",
      "declarations": [
        {
          "range": [
            4,
            9
          ],
          "type": "VariableDeclarator",
          "id": {
            "range": [
              4,
              5
            ],
            "type": "Identifier",
            "name": "a"
          },
          "init": {
            "range": [
              8,
              9
            ],
            "type": "Literal",
            "value": 1,
            "raw": "1"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "module"
}

ASTのコミュニティ標準

ESTreeというデファクトスタンダードがあります(ES6までは定義されている)

経緯: [2015-02] 最近のJavaScript AST標準化の動き | Web Scratch

パーサ

どのパーサもいろんなツールで使われてる実績があります。 Esprimaで試して、何か足りないならAcorn -> Babylonという感じでやるのが良いと思います。

大きく分けると以下の2系統になっています。

  • Esprima
  • Acorn

BabylonはEStreeの仕様外について積極的にサポートしているため、 それらが必要な場合はBabylonを使うが、そうでないなら他のパーサの方が安定しています。

関連ツール

よくある流れ

Parser、Traverser、Generatorは基本的にセットで同じ系統のツールを使う形になります。

そういうのを考えるのが面倒なので、そこを抽象化するライブラリを作ったりしていました。 コンセプトの証明的なものなので、実用的に使う場合は未対応の部分があると思います。(維持コストが高い) コントリビュートは歓迎です。

例) evalの中身を解析したい

evalの中を取り出して安全に評価したいというケース

evalの中身

eval("var a = 20")

のようにパースして、evalの中身を取り出すことが可能。

  • 何かを防ぐという仕組みから連想できるものはホワイトリスト
  • ホワイトリストでevalの中で使えるプロパティの名前などを制限する
  • 未知のプロパティを見つけらたらその弾く?
  • ブラックリストだと何を防止すればいいのかが難しい

解析のフェーズ

適当な考え方(正しいのかは自信ないけど)

  1. evalのなかを取り出す
  2. evalのなかのコードをパースする
  3. 安全な部分だけを取り出す
  4. 安全な部分で評価する

安全な部分 が何かを決める必要がありそう。 安全でないことが分かったら実行は諦める。

というような考え方でやるのがよくあるケースだと思います。

おわりに

上記のようなチェックツールを書くには、まずESLintのルールを見てみるのが参考になるはずです。

書き換えをしたい場合はそのまま書き換えるとASTのTree内で不整合が起きて大変になります。 jscodeshiftast-typesなどのライブラリが使えないか検討してみてください。

また、同様の機能を持つツールがどういうライブラリを使っているかを調べてみるのが近道になるはずです。 例えば、モジュールbundleツールのrollupmagic-stringというライブラリで文字列の操作をしていることが分かります。

現在のASTの状況はESTreeより先のデファクトがないため新しい構文を扱おうとすると色々考えることが出てきます。 そういった時にはast-sourceのような抽象層が必要になってくるかもしれません。