Decoratorの付与されたTypeScriptのクラスを書き換える


[gulp-typescript-angular](https://www.npmjs.com/package/gulp-typescript-angular)でクラス名のネーミングルールでAngularJSへ自動登録できるようにしたが不要になりそうだ。TypeScript1.5でデコレーターが導入されて、[@Quramy氏](http://qiita.com/Quramy)の[TypeScriptのDecoratorメモ](http://qiita.com/Quramy/items/e3a43bb1734b8a7331e8)のストックにあるように、[@vvakame氏](http://qiita.com/vvakame)の[gist](https://gist.github.com/vvakame/29a0f26c9b64877354c6)のコードを使うと、DecoratorでAngularJSへの登録はできる。ただし、Decoratorでは、コンストラクタのパラメーター名がとれないので、勉強がてら[$inject Property Annotation](https://docs.angularjs.org/guide/di)を自動的に付与する機能を[gulp-typescript-angular](https://www.npmjs.com/package/gulp-typescript-angular)に追加してみた。

## DecoratorのJavaScriptの変換後の形
Decorator が付与された TypeScriptのクラスをコンパイルすると

``` typescript:Controller
module sample {

    @sample.Service
    class SampleService {
        constructor(public $q: angular.IQService) {
        }
    }

}
```

<code>__decorate</code>メソッドが追加され、クラス宣言の最後でDecoratorを呼び出すコードが追加される。
なので、<code>SampleController = __decorate([sample.Controller], SampleController);
</code>の部分を抽出すれば、クラスに付与されているDecoratorが取れる。

``` javascript
var __decorate = this.__decorate || function (decorators, target, key, value) {
    var kind = typeof (arguments.length == 2 ? value = target : value);
    for (var i = decorators.length - 1; i >= 0; --i) {
        var decorator = decorators[i];
        switch (kind) {
            case "function": value = decorator(value) || value; break;
            case "number": decorator(target, key, value); break;
            case "undefined": decorator(target, key); break;
            case "object": value = decorator(target, key, value) || value; break;
        }
    }
    return value;
};
var sample;
(function (sample) {
    var SampleController = (function () {
        function SampleController($scope) {
            this.$scope = $scope;
        }
        SampleController = __decorate([sample.Controller], SampleController);
        return SampleController;
    })();
})(sample || (sample = {}));
```

これをfalafelを使ってJavaScriptのASTを解析していくことで、クラスの書き換えます。

### 1. JavaScriptのASTからクラスを抽出する
TypeScriptから生成されるJavaScriptでは、クラスは下記のようになる。

```js
 var SampleClass = (function(){/*クラスの定義*/ return SampleClass})();
```

このパターンに対応するASTを絞り込む。
falafelを使うとASTのnodeごとに判定することができるので、
それを使って、書きのコードでClass宣言を絞り込む。

```js
 if (node.type === 'VariableDeclaration' &&
     (decls = node.declarations) && decls.length === 1 &&
     (decl = decls[0]) && decl.init && decl.init.type === 'CallExpression' ) 
       if(!decl.init.callee.body){
         return;
       }
```

if文を分解していくと、最初の判定は変数宣言であること "var xxx,yyy...." 

```js
 if (node.type === 'VariableDeclaration' &&
```

次の条件は、変数宣言の数が一つであること "var xxx"

```js
     (decls = node.declarations) && decls.length === 1 &&
```

次の条件が、初期化処理が関数呼び出しであること "var xxx = func()"

```js
     (decl = decls[0]) && decl.init && decl.init.type === 'CallExpression' ) 
```

最後の条件が、初期化関数が無名関数であること "var xxx = (function(){})()"

```js
       if(!decl.init.callee.body){
         return;
       }
```

TypeScriptでコーディングする場合には、 "var xxx = (function(){})()"のようなコードを書くことはないので、個人的にはTypeScriptのクラスを絞り込むのに十分だと思いますが、網羅的には検証できていないので、バグったら条件を厳しくしていく必要があります。

### 2. クラスの宣言から Decorator を取り出す
Decoratorのロジックはreturn文の直前に追加される。

```js
SampleDirective = __decorate([sample.Directive], SampleDirective);
return SampleDirective;
```

<code>decl.init.callee.body.body</code>にクラス宣言の命令がListで入っているので、最後から2番目を取るとdecoratorの命令が取れる。

```js
 var body = decl.init.callee.body;
 var decoratorBlock = body.body[body.body.length-2];
```
decoratorBlockが<code>SampleDirective = __decorate([sample.Directive], SampleDirective);</code>に対応する。

なので、decoratorBlockの右辺のメソッドの第一引数をとれば良い。

```js
 var decorate = decoratorBlock.expression.right;
 var decorators = decorate[0].elements.map(function(element){
  return element.source();
 })

```
これでdecoratorsに["sample.Directive"]がはいる。

### 3. constructorの後にコードを追加する
Decoratorは、クラス宣言の最後に呼び出されるので、クラス宣言の後に$inject Propery Annotationを追加しても遅い。コンストラクタの後に追加する必要がある。
コンストラクタは、クラス宣言の最初の文なので、

```js
 var constructor = decl.init.callee.body.body[0];
```
で取得できる。
このコンストラクタに対して、updateメソッドを使って、コードを追加する。

```js
    var source = '/*<generated_code>*/';
    ...
    constructor.update(constructor.source() + source);
```

下記のように、SampleControllerのコンストラクタの直後にコードを追加することができる。

```js
var sample;
(function (sample) {
    var SampleController = (function () {
        function SampleController($scope) {
            this.$scope = $scope;
        }/*<auto_generate>*/SampleController.$inject = ['$scope'];SampleController.$componentName = 'SampleController'/*</auto_generate>*/
        SampleController = __decorate([sample.Controller], SampleController);
        return SampleController;
    })();
})(sample || (sample = {}));
```

## まとめ
falafelのASTはconsole.logで出力すると、ASTの構造が分かりやすく出力されるので、console.logでデバッグしながら開発していくと、ASTの解析は簡単に実装できました。