LoginSignup
24
11

More than 5 years have passed since last update.

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

Posted at
1 / 17

はじめに

こちらはGotanda.js #6 in OisixのLT資料になります


自己紹介

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

ASTとは

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

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
  • 下記のようなファイルを用意
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で試しながら探してみると非常に捗るのでおすすめ

サンプルコード

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

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使ってみることは出来ると思います
  • 正規表現に限界を感じたりしたら是非

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

24
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
11