LoginSignup
5
7

More than 5 years have passed since last update.

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

Posted at

gulp-typescript-angularでクラス名のネーミングルールでAngularJSへ自動登録できるようにしたが不要になりそうだ。TypeScript1.5でデコレーターが導入されて、@Quramy氏TypeScriptのDecoratorメモのストックにあるように、@vvakame氏gistのコードを使うと、DecoratorでAngularJSへの登録はできる。ただし、Decoratorでは、コンストラクタのパラメーター名がとれないので、勉強がてら$inject Property Annotationを自動的に付与する機能をgulp-typescript-angularに追加してみた。

DecoratorのJavaScriptの変換後の形

Decorator が付与された TypeScriptのクラスをコンパイルすると

Controller
module sample {

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

}

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

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では、クラスは下記のようになる。

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

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

 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...."

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

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

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

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

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

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

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

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

2. クラスの宣言から Decorator を取り出す

Decoratorのロジックはreturn文の直前に追加される。

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

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

 var body = decl.init.callee.body;
 var decoratorBlock = body.body[body.body.length-2];

decoratorBlockがSampleDirective = __decorate([sample.Directive], SampleDirective);に対応する。

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

 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を追加しても遅い。コンストラクタの後に追加する必要がある。
コンストラクタは、クラス宣言の最初の文なので、

 var constructor = decl.init.callee.body.body[0];

で取得できる。
このコンストラクタに対して、updateメソッドを使って、コードを追加する。

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

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

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の解析は簡単に実装できました。

5
7
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
5
7