White Box技術部

WEB開発のあれこれ(と何か)

【TypeScript】DangerのTSLintプラグイン紹介と導入の補足

Rubyの方のDangerTSLintに対応させたかったので、ESLintのDangerプラグインforkをforkして、DangerのTSLintプラグインを作りました。

danger-tslintの作成経緯

最初はDanger-jsの方のTSLintプラグインを導入したのですが、Dangerでいいなぁと思っていたソースコード中へのコメント追加ができないようだったので、導入は見送って自分で作ることにしました。

せっかく自作するので、自分が必要なオプションはふんだんに盛り込んであります!(๑•̀ㅂ•́)و✧

danger-tslintでできること

README.mdに記載してあるように、以下が実施できます。

  • tslintのインストール先(実行パス)の指定
  • tslintで使用するコンフィグファイルの指定
  • tslint対象とするファイルの指定
  • tslint対象外とするファイルの指定
  • tslintを実行するTypeScriptのプロジェクトディレクトリの指定
  • tslint対象を新規ファイルと更新ファイルに絞るかどうかの指定
    • 絞った場合にtslint対象とするファイルの形式
  • tslintの実行

※オプションの詳細などはこちらも参考にしてください。

danger-tslint利用手順

まずはプロジェクトにGemfileを作成し、以下の内容を記載します。

source 'https://rubygems.org'

gem 'danger'
gem 'danger-tslint', :git => 'https://github.com/seriwb/danger-tslint.git', :branch => "master"

danger-tslintの利用は、

  1. Gitリポジトリのメインの構成がTypeScriptプロジェクトで、
  2. CIの実施タイミング(bundle exec danger)の前に、npm iyarnによりtslintがインストールされており、
  3. tslintのチェック対象をプラグインの設定で絞らない

という条件であれば、Dangerfileにtslint.lintを記載するだけで十分です。

  • Dangerfile
tslint.lint

そしてJenkinsなどのCIサーバ上で、対象プロジェクトのテスト時に、以下の処理が実施されるようにしてください。

npm i
bundle install
bundle exec danger

danger-tslintのオプション解説

ここからは、Spring Bootで構成されたリポジトリのサブディレクトリが、TypeScriptのプロジェクトとなっている場合、

つまり、プロジェクトルート直下のsrcディレクトリはJavaなどの言語用、サブディレクトリ(ここではclient)配下のsrcディレクトリがTypeScript用となる以下のような構成を例にとってオプションの解説をしていきます。

.
├── client
│   └── src
└── src

Dangerfileは、.gitディレクトリのあるプロジェクトルート直下に置く必要があるので、TypeScriptプロジェクトのパスと、tslintの実行パスを以下のように指定する必要があります。

tslint.executable_path = 'client/node_modules/.bin/tslint'
tslint.project_directory = 'client'
tslint.lint

こうすることでproject_directoryのパス配下にtsconfig.jsonやtslint.jsonがあれば、それらが読み込まれた状態でtslintが実行されます。

filteringオプション利用時の注意

filteringオプションを利用すると、tslint対象がプルリクでの対象ファイルに限定されます。

しかし、このオプションを利用した場合、tsconfig.jsonのincludeに当てはまらないTypeScriptのファイルがプルリクで追加されると、 tslintがXXXXX.ts is not included in project.のようなエラーを出すため、以下のようにincludeのファイルとマッチするようにfile_regexを指定する必要があります。 (target_filesはtsconfig.jsonでincludeが指定されていれば、無くても大丈夫なはずです)

tslint.filtering = true
tslint.executable_path = 'client/node_modules/.bin/tslint'
tslint.project_directory = 'client'
tslint.target_files = 'client/src/**/*.{ts,tsx}'
tslint.file_regex = /client\/src\/.*\.tsx?$/
tslint.lint

target_filesにはシェルにおける文字一致のパターンを、file_regexにはRubyにおける正規表現のパターンを記載する必要があり、これらはマッチ内容が一致していると概ね良いと思います。

ちなみにコンフィグファイルは、プロジェクトディレクトリ配下に配置してあるのであれば、プロジェクトディレクトリだけの指定で大丈夫です。

むしろ指定すると私のプロジェクト構成では動かなかったような・・・

ちょっとクセのあるfilteringオプションですが、filteringオプションを有効にしなくても、Dangerのgithub.dismiss_out_of_range_messagesオプションを使えば、プルリク外のファイルにコメントは付かないので、用途によって使い分けてみてください。

初めてまともにRubyのコードを書いてみましたが、なんとか動いて良かったです。

テストは書いてないし、gemに登録もしていないのですが、自プロジェクトではそれっぽく動作していたので、DangerでTSLintを使う際にご利用ください。

Kotlinを使ったWEBアプリケーション開発の始め方

先日、とらのあな主催のKotlin勉強会で、Kotlinを使ったWEBアプリケーション開発の始め方という題で発表してきました。

ちょっと時間が押していたので発表は巻きになってしまったのですが、Kotlinで開発を始める後押しができていたら幸いです。

といっても、Java資産周りの技術解説いりますよね、本当に始めるなら

Kotlinのリフレクション(protected/privateメソッド呼び出し)

protectedメソッドへのアクセス

Kotlinにおいてprotectedのアクセス修飾子は、Javaと異なり同一パッケージからのアクセスを許容しません。 そのため、テストコードなどでprotectedのメソッドを実行したい場合は、リフレクションを使う必要があります。

継承してテストするという方法もありそうですが、今までしなかった発想なのでどうなんですかね。

リフレクションによる実行

例えばTestDataRepositoryにcreateSampleDataというLongのパラメータを1つ取り、SampleDataクラスを返却するprotectedのメソッドあるとした場合、以下が対象のメソッドを実行するコードになっています。

var testSuite = TestDataRepository()

val testMethod = testSuite::class.memberFunctions.find { it.name == "createSampleData" }
val actual = testMethod?.let {
    it.isAccessible = true
    it.call(testSuite, 100L)
}

コード解説

  1. まずは実行対象のメソッドがあるクラスのKClass(testSuite::class)から、
    KFunctionとして対象メソッドを取得(.memberFunctions.find { it.name == "createSampleData" })します
  2. KFunctionのインスタンスが確保できていれば(testMethod?.)、
    呼び出し結果をactualに保持するようにします(val actual = testMethod?.let {
  3. 実行するためにアクセスを許可し(it.isAccessible = true)、
    実行するインスタンスとメソッドに与えるパラメータ(100L)を指定して実行します(it.call(testSuite, 100L)

以上になります。

protectedのテストが面倒になった?

Javaの場合はprotectedメソッドであれば同一パッケージからすんなり呼べたので、テストが楽でよかったのですが、Kotlinの場合はちょっと工夫がいりそうです。 もうちょっと簡単な方法もありそうなのですが(PowerMockのWhiteboxクラス使うとか)、おいおい調べてみないといけないなという感じです。

【Slackアプリ】Botkitのsimple_storageを拡張する

BotkitにはSlackアプリがJSONでユーザ、チャンネル、チームの情報を保持できる簡易DBのような仕組みがあります。

今回はこれを拡張し、保持できる単位を増やしてみました。

simple_storageの拡張

Botkitで利用されている簡易DBは、simple_storage.jsとして実装されているので、そのコードを参考にすることで拡張ができました。

まずは必要となるjfsパッケージをインストールします。

npm i jfs

そしてsimple_storage.jsのstorageに、追加したいDBを足して返す関数を作ります。

ここでは、勤怠情報を保持するスペースとしてkintaiを作っています。ファイル名はstorage.jsの想定です。

'use strict';

import simple_storage from 'botkit/lib/storage/simple_storage.js';
import Store from 'jfs';

export function kintai_storage(config) {

  if (!config) {
    config = {
      path: './',
    };
  }

  let storage = simple_storage(config);
  let kintai_db = new Store(config.path + '/kintai', {saveId: 'id'});

  let objectsToList = (cb) => {
    return (err, data) => {
      if (err) {
        cb(err, data);
      } else {
        cb(err, Object.keys(data).map((key) => data[key]));
      }
    };
  };

  storage.kintai = {
    get: (kintai_id, cb) => {
      kintai_db.get(kintai_id, cb);
    },
    save: (kintai_data, cb) => {
      kintai_db.save(kintai_data.id, kintai_data, cb);
    },
    delete: (kintai_id, cb) => {
      kintai_db.delete(kintai_id, cb);
    },
    all: (cb) => {
      kintai_db.all(objectsToList(cb));
    }
  };

  return storage;
}

そして、この新しいstorageを利用するには、slackbotのパラメータに、storageキーの値を先ほど作成した関数としたオブジェクトを渡してください。

import Botkit from 'botkit';
import { kintai_storage } from './storage';

const controller = Botkit.slackbot({
  storage: kintai_storage({path: './data/'}),
}).configureSlackApp(
...以下省略

このプログラムを起動すると以下のように、保持できるスペースとして新たにkintaiが追加されていることを確認できます。

data
├── channels
├── kintai
├── teams
└── users

本格的に管理するのであれば、Sqliteなどを利用した方がいいと思いますが、一つ増やしたいくらいであれば、このような方法もいいのではないでしょうか。