TypeScriptのASTを操作する方法が、GitHubのWikiに公開されていました。これを使えば,TypeScriptのコードを解析することができます。
準備
TypeScriptのインストール
npm install typescript
を実行しTypeScriptを取得します。
tsconfig.jsonを設定する(Atomを利用している場合)
./node_modules/typescript/bin/typescript.d.ts
をfilesGlobに追加することで、TypeScriptのASTのAPIのコード補完が可能となります(./node_modules/typescript/bin以下のd.tsファイルを追加すると、Atomが重くなりコード補完は使い物になりません)
{
"version": "1.4.1",
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false
},
"filesGlob": [
"./**/*.ts",
"!./node_modules/**/*.ts",
"./node_modules/typescript/bin/typescript.d.ts"
],
"files": [
"./index.ts",
"./typings/node/node.d.ts",
"./node_modules/typescript/bin/typescript.d.ts"
]
}
AST解析の進め方
ASTの解析はVisitorパターンを使って、ASTのNodeの種類ごとに、解析を進めていきます。
サンプルプログラムでは、クラスの一覧を標準出力に出力します。
1. typescript を import する
import * as ts from 'typescript'
でtypescritをインポートします
2. 解析対象のソースを準備する
解析対象のソースを準備します。本来はfileから読み込むべきですが、ファイル操作のコードを省略するために、変数として用意します。
let source = `
class Sample{
}
class Sample2{
}
`;
3. SourceFile を生成する
TypeScriptのASTのエントリポイントは、ソースコードをParseして得られるSourceFileから開始します。
let sourceFile = ts.createSourceFile(
/*ファイル名*/ 'sample.ts',
/*ソース*/ source,
/*ターゲット*/ ts.ScriptTarget.ES6,
/*setParentNodes */ true);
4. Visitor パターンを実装する
TypeScriptのAPIにts.forEachChild
用意されており、これを使うことでVisitorパターンを実装することができます.
下記のサンプルはClassの宣言を見つけるためのVisitorパターンの実装です。node.kindとts.SyntaxKindを比較することで、Nodeの種類を特定することができます。
ts.forEachChild(sourceFile, each);
function each(node: ts.Node) {
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration:
classDeclaration(<ts.ClassDeclaration>node);
break;
default:
next();
}
function next() {
ts.forEachChild(node, each);
}
}
5. クラス宣言の場合のコールバックメソッドを作成する
クラス宣言の場合のコールバックメソッドでコンソールにクラス名を出力します。
function classDeclaration(node: ts.ClassDeclaration) {
console.log(node.name.text);
}
動作確認
作成したコードを実行すると、解析対象のソースにある2つのクラス名がコンソールに出力されます
$ node index.js
Sample
Sample2
まとめ
TypeScriptにASTを操作するためAPIが用意されているので、Visitorパターンを利用すれば、ソースコードの解析は簡単に実現できます。しかし、ソースコードを書き換える処理を実装しようとすると、falafelに比べて、APIが足りないので面倒な実装をしなければなりません。ソースコードを書き換えは、次回の記事に書こうと思います。
開発したソース
import * as ts from 'typescript'
let source = `
class Sample{
}
class Sample2{
}
`;
let sourceFile = ts.createSourceFile('sample.ts', source, ts.ScriptTarget.ES6, /*setParentNodes */ true);
ts.forEachChild(sourceFile, each);
function each(node: ts.Node) {
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration:
classDeclaration(<ts.ClassDeclaration>node);
break;
default:
next();
}
function next() {
ts.forEachChild(node, each);
}
}
function classDeclaration(node: ts.ClassDeclaration) {
console.log(node.name.text);
}