JSDocで型チェックする

JavaScriptでも静的な型が求められるようになりTypeScriptやFlowが使われるようになってきました。
しかし、それらが無かったころのJavaScriptのコードやトランスパイラを使わずに開発している人はいてると思います。
この記事はコードの変更なしでJSDocのみで型検査をする方法を紹介します。

# TL;DR
- JSDocの型定義で型チェックをする
- TypeScriptのallowJsとcheckJsを使う
- 必要に応じてd.tsをインストールする

# JSDocの型定義
TypeScriptやFlowが登場するより前からJSDocは存在します。JavadocやPHPDocのJavaScript版のようなものです。
そのJSDocは型定義が書けるようになっています。
エディタによってはJSDocの型定義で型のチェックを行ってくれることもあります。
JSDocについては[@use JSDoc](http://usejsdoc.org/)にすべて載っています。
JSDocの型定義の書き方は以下のような感じです。

```js
/**
 * @param {number} a
 * @param {number} b
 * @return {number}
 */
function add(a, b) {
  return a + b;
}
```

# JSDocの型定義を使って型のチェックをする
型のチェックにはTypeScriptを使います。**トランスパイルは行いません。**

## TypeScriptの準備
まずはTypeScriptをインストールします。

```
npm install --save-dev typescript
```

次にtsconfig.jsonを作成します。

```
npx tsc --init
```

## allowJsとcheckJsオプションを有効にする
自動生成されたtsconfig.jsonの`allowJs`と`checkJs`プロパティを有効にします。

```js
{
    // "allowJs": true,
    // "checkJs": true,
}
```

以下のようにします。

```js
{
    "allowJs": true,
    "checkJs": true,
}
```

## トランスパイル結果を出力しない
**トランスパイルは行いません。**と述べましたが、厳密には**トランスパイル結果を出力しない**が正しいです。
tsconfig.jsonの`noEmit`を有効にします。

```js
{
    // "noEmit": true,
}
```

以下のようにします


```js
{
    "noEmit": true,
}
```

## 型チェックしたいディレクトリを指定する
tsconfig.jsonの`include`を指定します。

```js
{
    "include": [
        "src/"
    ]
}
```

逆にチェックしたくないディレクトリも指定できます。
`node_modules`以下のチェックも不要だと思いますので、ここで指定した方が良いでしょう。

```js
{
    "exclude": [
        "dist/",
        "node_modules"
    ]
}
```


# 型定義をインストールする
TypeScriptには有名なライブラリやフレームワーク用に型定義ファイルが用意されています。
例えばExpressの型定義ファイルは[@types/express](https://www.npmjs.com/package/@types/express)です。

```
npm install --save-dev @types/express
```

```js:app.js
import express from "express";

// 途中略

app.use(errorHander);

/**
 * @param {express.Request} req
 * @param {express.Response} res
 * @param {express.NextFunction} next
 */
function errorHander(req, res, next) {
  res.end("Error");
}
```

他にも型定義ファイルはたくさんあるので[TypeSearch](https://microsoft.github.io/TypeSearch/)から探してみてください。

# 型チェックを実行する
npm-scriptsから実行できるように`package.json`に記載しておくとよいでしょう。

```json:package.json
{
  "scripts" {
    "typecheck": "tsc"
  }
}
```

```
npm run typecheck
```

試しに以下のようなソースコードで型チェックが正しく行われているか確認してみましょう。

```js:src/index.js
import {add} from './calc';

console.log(add("1", 2));
```

```js:src/calc.js
/**
 * 
 * @param {number} a 
 * @param {number} b 
 * @return {number}
 */
export function add(a, b) {
    return a + b;
}
```

実行してみます。

```
npm run typecheck
```

以下のようなエラーが表示されました。

```
> tsc
src/index.js:3:17 - error TS2345: Argument of type '"1"' is not assignable to parameter of type 'number'.

3 console.log(add("1", 2));
```

`index.js`を修正します。

```js:src/index.js
import {add} from './calc';

console.log(add(1, 2));
```

実行してみます。

```
npm run typecheck
```

```
> tsc
```

エラーがなくなりました。


`add`関数の引数に`number`型で渡すとエラーになりませんが、`string`型を渡すとエラーになるので、正しくJSDocの型定義から型チェックできていることがわかります。

# 最後に
既にJSDoc書いているのであれば既存のコードは変更せずに済みます。またJSDocに型定義書いてFlowやTypeScriptの型定義も書いてって冗長になることもありません。
もし、既存のコードを修正したくないけど型チェックを行いたいといった場合は今回の記事の内容が参考になれば幸いです。
これから新規で開発する場合は素直にTypeScriptで書いた方がいいと思います。またコードの変更が許容されるならFlowで段階的に型チェックすると良いかと思います。
いずれにせよ型チェックはしたほうが品質は上がると思います。

最後までお読み頂きありがとうございました。質問や不備があればコメント欄またはTwitter([@shisama_](https://twitter.com/shisama_))までお願いいたします。