TypeScriptのAST・コンパイラAPIとお付き合い

# はじめに
こちらは[Gotanda.js #6 in Oisix](https://gotandajs.connpass.com/event/42328/)のLT資料になります

---

# 自己紹介

- twitter: [@_sisisin](https://twitter.com/_sisisin)
- github: [@sisisin](https://github.com/sisisin)
- 普段触ってる技術
    - 仕事では主にAngular1系+TypeScriptでSPA開発
    - 個人では主にReact,Angular2系,Node.js.最近はちょろっとRuby

---

# ASTとは

- Abstract Syntax Tree(抽象構文木)のこと
- コードをパースして木構造の集合として扱えるように出来る
- 詳しくはazuさんの資料が参考になります（http://azu.github.io/slide/JSojisan/）
- 気軽にASTを試すなら[AST explorer](https://astexplorer.net/)おすすめ

---

# TypeScriptとAST

- TypeScriptのコンパイラの内部ではTypeScriptのソースコードを変換するためにASTを利用している
- 今回はそのコンパイラAPIでASTを取り扱う方法を紹介

---

# コンパイラAPIの概要

- コンパイラAPIではソースファイル = AST Nodeとして扱うことが出来、そのソースファイルノードの集合として1つのプロジェクトを構成している
- それぞれ`Node`, `Program`という型が`typescript.d.ts`に定義されている
    - なお、ソースファイルは`Node`型を継承した`SourceFile`という型が定義されている
    - ソースファイルだけでなく、全てのAST Nodeは`Node`型を継承したinterfaceが定義されている
- プロジェクトに対しての操作などを行うAPIは`CompilerHost`に定義されている
    - この辺使わなかったのでよくわかってません＞＜

---

## 実際にTypeScriptのプロジェクトを読み込んでみる

---

# 動作環境

TypeScript1.8

*2系で試すときはbreaking change入ってるかもなので注意

---

# 準備

- `npm init -y && npm i -S typescript`
- 下記のようなファイルを用意

```TypeScript:index.ts
import * as ts from 'typescript';
```

簡単！

---

- 型定義ファイルも含まれているのでそのままTypeScriptで書けてGood
    - （というか型定義ファイルなしではやってられない
- もちろんトランスパイルしないと動かないので、実行する際は`$(npm bin)/tsc`を実行してから`node index.js`と叩いてやる

---

# 使う

- `ts.createProgram()`を利用して`Program`を作成する
    - 第一引数に対象のファイル名配列、第二引数にコンパイラオプションを渡す
        - コンパイラオプションは`tsconfig.json`まるっと渡しても良い
    - 対象のTypeScriptプロジェクトを取り扱えるメソッド郡を持ったObjectが返ってくる
        - 返り値はクラスのインスタンスではなく生のJSオブジェクトっぽいのでややこしい言い回しになった

---

- `Program.getSourceFiles()`を利用して`SourceFile`を取得する
    - `SourceFile`のインスタンスの配列が返ってくる
    - こいつは`Program`と違ってクラスのインスタンスっぽい（SourceFileObjectクラス）
    - `SourceFile`はファイル名とかが含まれた`Node`になっている

---

- `ts.forEachChild()`を利用して、AST Nodeを走査する
    - 走査対象の`Node`がもつ子`Node`全てに処理を行う関数
        - なお、孫以下は走査されない
    - 第一引数に走査対象`Node`を渡し、第二引数にコールバック関数を渡す
    - コールバック関数の引数は`Node`のインスタンス
        - 更に子の`Node`を探索する場合はこのコールバック内でさらに`ts.forEachChild()`を呼ぶ形になる

---

- `Node.kind`を用いてAST Nodeの種類によって処理を分岐する
    - `Node.kind`には`ts.SyntaxKind`のenum値が格納されている
    - `SyntaxKind`の種類によって扱えるプロパティが違うので、その場合はキャストする
        - 例えば`ts.SyntaxKind.VariableStatement`の場合は`VariableStatement.declarationList`というプロパティを持っていたりする
    - TypeScriptのコードがどの`SyntaxKind`にパースされるかはenumの定義見て察する必要がありそうだった
        - ドキュメント・・・
    - AST explorerで試しながら探してみると非常に捗るのでおすすめ

---

# サンプルコード
クラス定義をコンソールに表示するプログラムのサンプルコードはこんな感じ

```JavaScript:index.ts
import * as ts from 'typescript';

const files = ['sample/s1.ts', 'sample/d/s2.ts'];
const tsconfig = require('../sample/tsconfig.json');
const program = ts.createProgram(files, tsconfig);

for (const sourceFile of program.getSourceFiles()) {
  if (sourceFile.fileName.substr(-5) === '.d.ts') continue;
  ts.forEachChild(sourceFile, visit);
}

function visit(node: ts.Node) {
  if (node.kind === ts.SyntaxKind.ClassDeclaration) {
    console.log((<ts.ClassDeclaration>node).name.text);
  }
  ts.forEachChild(node, visit);
}

```

---

# 終わりに
- 簡単にTypeScriptのコンパイラAPIを用いたASTの扱い方を紹介しました
- 以上の知識があれば最低限TypeScriptのAST使ってみることは出来ると思います
- 正規表現に限界を感じたりしたら是非

---

- 参考資料
    - [TypeScriptのリポジトリのWiki](https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API)
    - [TypeScriptのASTを使ってコードを解析しよう](http://qiita.com/kodo_san/items/12e83c3058ea151eb61a)
    - [TypeScriptのASTを使ってコードを書き換えよう](http://qiita.com/kodo_san/items/b1fbc6b29e138d06d625)

※後者2つの記事ではTypeScript1.4を使ってますが、1.8でもそのまま使えました