JavaScript
TypeScript
JSDoc

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にすべて載っています。
JSDocの型定義の書き方は以下のような感じです。

/**
 * @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のallowJscheckJsプロパティを有効にします。

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

以下のようにします。

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

トランスパイル結果を出力しない

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

{
    // "noEmit": true,
}

以下のようにします

{
    "noEmit": true,
}

型チェックしたいディレクトリを指定する

tsconfig.jsonのincludeを指定します。

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

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

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

型定義をインストールする

TypeScriptには有名なライブラリやフレームワーク用に型定義ファイルが用意されています。
例えばExpressの型定義ファイルは@types/expressです。

npm install --save-dev @types/express
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から探してみてください。

型チェックを実行する

npm-scriptsから実行できるようにpackage.jsonに記載しておくとよいでしょう。

package.json
{
  "scripts" {
    "typecheck": "tsc"
  }
}
npm run typecheck

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

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

console.log(add("1", 2));
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を修正します。

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_)までお願いいたします。