Septeni Engineer's Blog

セプテーニエンジニアが綴る技術ブログ

TypeScriptの静的解析ツールを導入する

こんばんは、ゆあさです。

今回はTypeScript用の静的解析ツールであるtslintGruntのタスクとしてプロジェクトへ導入するまでの流れを書いていきたいと思います。

tslintとは

tslintとはTypeScriptのソースコードを静的に解析して潜在的な問題を検出してくれるコード品質管理ツールです。

grunt-tslintのインストール

grunt-tslintというGrunt用のプラグインが存在するため今回はこれを使います。
インストールは

npm install grunt-tslint --save-dev

を実行、
またはpackage.json

"grunt-tslint": "~2.2.0"

を手動で追加して

npm install

を実行します。

ルール定義

解析のルール定義ファイルをjson形式で作成します。

設定できるルール一覧はこちらにありますが、まずは本家のサンプルをそのまま使用してみるのがよいでしょう。
sample.tslint.json
このファイルをプロジェクトのルートディレクトリへtslint.jsonという名前で保存します。

Gruntjile.js

Gruntjile.jsを修正します。
今回はtslint.jsonを読み込み、srcディレクトリ配下の全てのtsファイルをチェックするように設定しました。

grunt.initConfig({
  〜〜〜
  tslint: {
    options: {
      configuration: grunt.file.readJSON("tslint.json")
    },
    files: {
      src: ["src/**/*.ts"]
    }
  }
});
grunt.loadNpmTasks("grunt-tslint");

実行

これでtslintをgruntから実行する準備が整いました。

grunt tslint

を実行すると以下のような警告を出してくれるようになっているはずです。

Running "tslint:files" (tslint) task
>> src/Hoge.ts[23, 26]: missing semicolon
>> src/Hoge.ts[73, 35]: expected parameter: 'hoge' to have a typedef
>> src/Hoge.ts[77, 30]: missing whitespace
>> src/hoge.ts[80, 2]: file should end with a newline
>> 4 errors in 1 files

あとは自分たちのプロジェクトのビルドタスクにtslintタスクを入れるなどすればビルド時に強制的に解析を走らせられるようになります。

最後に

以上のように導入するだけならものの数分で導入することができます。

ですがやはり、既に大きなコードベースが存在するプロジェクトへ後からいきなり導入するのは難しいため、まずはtslintが手動で実行できる状態までで仮導入してコツコツと警告を修正していくのが現実的かと思います。
私達のプロジェクトでも仮導入した時点では定義ルールをある程度プロジェクトに則したルールに変更しても400件以上の警告の存在していましたが、(コンフリクトを避けるため)更新頻度の低いソースから段階的に警告を修正していき、警告が0になった時点でビルドタスクへの追加をしました。

後からの導入は根気がいりますが、導入すれば潜在的な問題を検出してくれるのはもちろんのこと、

  • コーディングスタイルの強制
  • コードレビュー負荷削減
  • TypeScript未経験のメンバー加入時の教育コスト削減

などの効果があり、導入メリットは大きいと感じています。

ご参考までに、以下に私たちのプロジェクトで使用しているルール定義を貼っておきます。
それではまた。

{
    "rules": {
        "class-name": true,
        "curly": true,
        "eofline": false,
        "forin": true,
        "indent": [true, "spaces"],
        "label-position": true,
        "label-undefined": true,
        "max-line-length": [true, 140],
        "no-arg": true,
        "no-bitwise": true,
        "no-consecutive-blank-lines": true,
        "no-console": [true,
            "debug",
            "info",
            "time",
            "timeEnd",
            "trace"
        ],
        "no-construct": true,
        "no-constructor-vars": false,
        "no-debugger": true,
        "no-duplicate-key": true,
        "no-duplicate-variable": true,
        "no-empty": false,
        "no-eval": true,
        "no-string-literal": false,
        "no-switch-case-fall-through": true,
        "no-trailing-comma": true,
        "no-trailing-whitespace": true,
        "no-unused-expression": true,
        "no-unused-variable": false,
        "no-unreachable": true,
        "no-use-before-declare": true,
        "one-line": [true,
            "check-open-brace",
            "check-whitespace"
        ],
        "quotemark": [true, "double"],
        "radix": false,
        "semicolon": true,
        "typedef-whitespace": [true,
            {
                "index-signature": "nospace",
                "parameter": "nospace",
                "property-declaration": "nospace",
                "variable-declaration": "nospace"
            }
        ],
        "variable-name": false,
        "whitespace": [true,
            "check-branch",
            "check-decl",
            "check-operator",
            "check-separator"
        ]
    }
}