課題 - react-scripts@2.xを用いたTypeScriptのカバレッジレポート

# 概要
[IstanbulJS - レポート改善 - ソースコード修正](https://qiita.com/TakuyaHara/items/defc3284929161ac4809#typescript---react-scripts2x%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88%E3%81%AE%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88) の課題の検証。

react-scripts@<wbr>2.xを用いたTypeScript環境にて、`IstanbulJS` で生成したHTML形式のカバレッジレポートにおいて以下の問題が発見されたため、原因と解決策を調査しながら随時更新する。
🙅 関数の定義部のハイライトが戻り値の型までカバーされていない。
🙅 クラスメソッドの public キーワードがハイライトされている。

![Screen Shot 2019-01-18 at 3.03.46.png](https://qiita-image-store.s3.amazonaws.com/0/286440/8ea62c84-b247-a7f3-d6dc-57b6fa6511bb.png)

# 調査
## JSONレポートにおけるソースマップが存在しない
TypeScript環境では、以下の流れでカバレッジレポートを生成する。

1. **Jest**: 何らかのプログラムを呼び出す。
2. **？？？**: TypeScriptコードをJavaScriptへトランスパイルする。
3. **Jest**: トランスパイルされたJavaScriptコードにてテストを実行し、`IstanbulJS` を呼び出す。
4. **IstanbulJS**: JSONレポートを生成する。以降、このレポートを「一時レポート」と記載する。
5. **nyc**: 一時レポート中のソースマップを用いてソースマッピングを行い、TypeScriptコード用のレポートを生成する。

react-scripts@<wbr>2.xとreact-scripts@<wbr>1.xの両環境における一時レポートを比較したところ、後者にはソースマップが含まれていないことが判った。ソースマップが無ければ `nyc` はソースマッピングをできないため、ハイライトが正しく行われない。

一時レポートにソースマップを含めることが解決策になりうると仮説を立てて調査を続行した。

### 再現
[設定ファイルの作成](https://qiita.com/TakuyaHara/items/978f0037c9c20c7b6380#%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90) では、`nyc` が用いる一時レポートの格納先とソースマッピング後のレポートの出力先を同一にしており[^1]、一時レポートは上書きされるため内容を確認することができない。内容を確認するため、ソースマッピング後のレポートの出力先を別のディレクトリへ変更する。

```json:.nycrc
{
    "reporter": [
        "text-summary",
        "json",
        "html"
    ],
    "report-dir": "./coverage-final",
    "temp-dir": "./coverage"
}
```

レポートを再び生成して一時レポートを確認すると、ソースマップが含まれていないことが判る(下記画像左)。他方、react-scripts@<wbr>1.xの環境では、一時レポートにソースマップが含まれている(同画像右)。

![Screen Shot 2019-01-14 at 4.23.02.png](https://qiita-image-store.s3.amazonaws.com/0/286440/92f79e7e-20fa-6786-5877-b81b60611761.png)


[^1]: ソースマッピング後のレポートの出力先はデフォルトで `./coverage` が用いられる。

## `ts-jest` が存在しない
react-scripts@<wbr>1.xの環境でソースマップを出力しているコードを調べたところ、[ts-jest](https://github.com/kulshekhar/ts-jest) の以下のコードが関わっていることが判った。

```javascript:node_modules/ts-jest/dist/utils.js
    config.inlineSourceMap = true;
```

`config.inlineSourceMap` を `false` にすると、一時レポートにソースマップが含まれなくなった。このため、`ts-jest` がソースマップを生成していると仮説を立てた。また、react-scripts@<wbr>2.xの環境では `Babel7` がTypeScriptのトランスパイルをサポートするため、`ts-jest` が存在しないことが判った。

## `nyc` が不要になった
`ts-jest` をインストールして一時レポートを再び生成すると、依然ソースマップが含まれていないものの、ステートメントなど各種の範囲が正しく記録されていた。[レポート形式の指定](https://qiita.com/TakuyaHara/items/978f0037c9c20c7b6380#%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88%E5%BD%A2%E5%BC%8F%E3%81%AE%E6%8C%87%E5%AE%9A) で実施した以下の設定を削除し、HTMLレポートも出力して内容を確認すると、期待通りのハイライトが行われていた。

```javascript:package.json
 "jest": {
    "coverageReporters": [
      "json"
    ]
  }
```

## カバレッジの変換
`nyc` を使うことなく期待通りのレポートが得られた理由を調査した。以下は、一時レポートに書き込みを行う前段のコードである。

```javascript:node_modules/jest-cli/build/reporters/coverage_reporter.js
      var _sourceMapStore$trans = _this._sourceMapStore.transformCoverage(
        _this._coverageMap
      );

      const map = _sourceMapStore$trans.map,
        sourceFinder = _sourceMapStore$trans.sourceFinder;

              (略)

        reporter.write(map, sourceFinder && {sourceFinder: sourceFinder});
```

`reporter.write` は、カバレッジを格納した `map` を内部のコードへ渡し、一時レポートへ書き込むラッパー関数である。react-scripts@<wbr>1.xの環境では、`map` 内にソースマップを格納した `inputSourceMap` というオブジェクトが含まれており、これが一時レポートに書き込まれる[^2]。

`_this._sourceMapStore.transformCoverage` は、JavaScriptにトランスパイルされたコードに対するカバレッジを格納した `_this._coverageMap` を元のTypeScriptコードに対するカバレッジへ変換する関数である。

---

react-scripts@<wbr>2.xの環境で `ts-jest` を用いた場合は、`_this._coverageMap` に `inputSourceMap` が含まれ、期待通りのレポートが得られる。他方、`Babel7` を用いた場合は、`inputSourceMap` が含まれず、期待通りのレポートが得られない。このため、カバレッジを正しく変換するには `inputSourceMap` が必要であると考えられる。

[^2]: react-scripts@<wbr>2.xの環境とは `Jest` のバージョンが異なるが、`reporter.write` に `map` を渡す点は同じである。

## カバレッジの変換(内部コード)
以下は、前述した `_this._sourceMapStore.transformCoverage` の内部コードである。

```javascript:node_modules/istanbul-lib-source-maps/lib/map-store.js
MapStore.prototype.transformCoverage = function (coverageMap) {
            (略)

    if (Object.keys(this.data).length === 0) {
        return {
            map: coverageMap,
            sourceFinder: sourceFinder
        };
    }
    mappedCoverage = transformer.create(function (filePath) {

            (略)

    }).transform(coverageMap);

    return {
        map: mappedCoverage,
        sourceFinder: sourceFinder
    };
};
```

`ts-jest` を用いた場合は、末尾の `return` まで処理が進み、変換された情報が返される。他方、`Babel7` を用いた場合は、中間の `return` で変換前の情報が返される。このため、`inputSourceMap` の有無はカバレッジ変換の正誤ではなく、変換自体の有無に関係することが判った。

# 原因
`Babel7` を用いたことでソースマップを含まないカバレッジが生成される。このため、カバレッジがTypeScript用に変換されず、TypeScriptコードと対応しないレポートが生成される。

# 解決策
react-scripts@<wbr>2.xを用いて、正しいカバレッジレポートを生成するTypeScript環境の作成手順を以下に示す。

## Reactプロジェクトの作成

```bash
$ create-react-app fix_typescript --typescript
$ cd fix_typescript
$ yarn eject
$ yarn add -D ts-jest
```

## package.json
### scriptsの追加
以下の設定を追加する。

```json:package.json
  "scripts": {
    "coverage": "rm -rf coverage && yarn test --coverage"
  }
```

### jest.transformの修正
設定を以下の通りに修正する。

```json:package.json
  "jest": {
    "transform": {
      "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
      "^.+\\.tsx?$": "ts-jest",
      "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
      "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
    }
  }
```

## tsconfig.json
### compilerOptions.targetの修正
es2015の記法を用いるため、設定を以下の通りに修正する。

```json:tsconfig.json
"compilerOptions": {
    "target": "es2015"
  }
```

## 周辺ファイルの修正
[IstanbulJS - レポート改善 - ソースコード修正](https://qiita.com/TakuyaHara/items/defc3284929161ac4809)、あるいは [IstanbulJS - レポート改善 ver.2 - ソースコード修正](https://qiita.com/TakuyaHara/items/11c6c1249fe85bc9609b) に従って周辺ファイルを修正する。

🚨 [#前提](https://qiita.com/TakuyaHara/items/defc3284929161ac4809#%E5%89%8D%E6%8F%90) に記載の手順はスキップすること。

# 検証
修正を実施後に再度生成したレポートが、定義したハイライトのルールに従っているか検証する。ここでは、[IstanbulJS - レポート改善 - ソースコード修正](https://qiita.com/TakuyaHara/items/defc3284929161ac4809#%E3%83%AB%E3%83%BC%E3%83%AB) のルールで検証を行なった。

## [TypeScript - react-scripts@2.xのテンプレートを使用](https://takuyahara.github.io/share/20190104_IstanbulJS_Coverage_Issue/20_Fix/typescript_v2/coverage/lcov-report/index.html)

👍 定義したルールに従ってハイライトされている。

# まとめ
周辺ファイルを修正する前に `ts-jest` をインストールすることで、正しいカバレッジレポートの生成を実現した。最終的な処理の流れを以下に示す。

1. **Jest**: `.tsx` ファイルをトランスパイルするため、`ts-jest` を呼び出す。
2. **ts-jest**: TypeScriptコードをJavaScriptへトランスパイルする。
3. **Jest**: トランスパイルされたJavaScriptコードにてテストを実行し、得られたカバレッジをTypeScriptコード用に変換して `IstanbulJS` を呼び出す。
4. **IstanbulJS**: HTMLレポートを生成する。