npm install / yarn add したら tsc が通らなくなった話

React のカスタムフックをテストするために [`@testing-library/react-hooks`](https://github.com/testing-library/react-hooks-testing-library) をインストールしました。
そして `tsc` を実行すると types 周りがコケる現象が発生しました。

```bash
$ tsc --noEmit
node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts(3092,14): error TS2300: Duplicate identifier 'LibraryManagedAttributes'.
node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts(3103,13): error TS2717: Subsequent property declarations must have the same type.  Property 'a' must be of type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>', but here has type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
...
```

本記事はこのエラーの考察と解決方法を記録したものです。

# TL;DR

違うバージョンの `@types/*` が解決されたので

- `npm` なら `npm dedupe && npm install`
- `yarn` なら `npx yarn-deduplicate && yarn install`[^1]

を実行して一個にまとめること。

# 発生経緯

説明用のサンプルを[公式](https://github.com/testing-library/react-hooks-testing-library)から拝借しました。
こんな感じにテストケースを書いています。

```typescript:useCounter.spec.ts
import { renderHook, act } from '@testing-library/react-hooks'
import useCounter from './useCounter'

test('should increment counter', () => {
  const { result } = renderHook(() => useCounter())

  act(() => {
    result.current.increment()
  })

  expect(result.current.count).toBe(1)
})
```

このテストケースを `jest` から実行するときはなんの問題もありませんが、`tsc` を実行したときだけコケました。

# 問題の原因

プロジェクトの構造はこんな感じにしています。

```text
├── README.md 
├── src
│   ├── components   <- React のコンポーネントを置くディレクトリ
│   │   └── MyComponent.ts
│   └── models       <- カスタムフックとかを置くディレクトリ
│       ├── useCounter.ts
│       ├── useCounter.spec.ts
```

`tests` フォルダを別に切り出すのではなく、テストファイルをテストしたいもののすぐ近くに置く構成にしています。
そして `tsconfig.json` の `include` フィールドに `src` 以下を追加すると

```json:tsconfig
{
  "include": [
    "src/**/*"
  ]
}
```

`tsc` を実行する時ファイルパスを指定しなくてもOKになります。

```bash
$ tsc --noEmit
```

この構成にしているため、`tsc` の実行時は `src` 以下のソースファイルとテストファイルの両方を見るようになっています。

今回のエラーメッセージ

```bash
$ tsc --noEmit
node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts(3092,14): error TS2300: Duplicate identifier 'LibraryManagedAttributes'.
node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts(3103,13): error TS2717: Subsequent property declarations must have the same type.  Property 'a' must be of type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>', but here has type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
...
```

はずばり `@types/react/index.d.ts` を読み込んだ時、型が二重定義されていたため起こったエラーです。

## 型の二重定義

`node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts` を見ればわかりますが、
![スクリーンショット 2021-10-11 033915.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57546/a3e87d33-35ae-a8bd-9813-dc664961e8c4.png)
`global` 以下に定義されたものがコンフリクトしています。
これが起こったのは `@types/react` が複数存在しているためです。

```bash
$ yarn list @types/react
yarn list v1.22.11
warning Filtering by arguments is deprecated. Please use the pattern option instead.
├─ @testing-library/react-hooks@7.0.2
│  └─ @types/react@17.0.27
└─ @types/react@17.0.15
```

プロジェクトからは `@types/react@17.0.15` を直接利用していますが、
`@testing-library/react-hooks@7.0.2` からは違うバージョンの `@types/react@17.0.27` が間接的に import されています。
`tsc` を実行するときは、プロジェクトから直接依存している `types` は自動的に読み込まれますが、テストファイルの

```ts
import { renderHook, act } from '@testing-library/react-hooks'
```

のこれも `@testing-library/react-hooks` 内の `@types/react@17.0.27` を読み込んでいます。
この２つの `@types/react` は同時に `global` に型を定義しているため、同じ名前の型が二重定義されてコンフリクトしています。

## 複数のnode_modules

`npm` / `yarn` を利用するプロジェクトはこのように、`node_modules` 以下にさらに `node_modules` を保つことができます。

```text
./node_modules
./node_modules/make-dir/node_modules
./node_modules/npm/node_modules/resolve/test/shadowed_core/node_modules
./node_modules/eslint-plugin-react/node_modules/resolve/test/resolver/symlinked/_/node_modules
```

例えば `a` と `b` から複数のライブラリのから依存されているライブラリ `c` はもし同じバージョンに解決できたら、共通でルート以下の `./node_modules` にその共用のライブラリを置かれますが、
同じバージョンに解決されなかった場合はそれぞれのディレクトリ以下

- `./node_modules/a/node_modules/c`
- `./node_modules/b/node_modules/c`

に置くことになります。

今回の `@testing-library/react-hooks` でいうと、実は `"@types/react": ">=16.9.0",` を[満たせば問題ない](https://github.com/testing-library/react-hooks-testing-library/blob/565c9f80ff969c3b9f20d8b2efdc033996d9ec27/package.json#L53)ので、プロジェクトの `@types/react` と同じバージョンに解決することができたはずです。

# 解決方法

ここに解決方法が書かれています。

https://github.com/testing-library/react-hooks-testing-library/issues/110

かいつまんで言うと、

- `npm` なら `npm dedupe && npm install`
- `yarn` なら `npx yarn-deduplicate && yarn install`[^1]

[^1]: なぜか `yarn dedupe` だと効かなくて `npx yarn-deduplicate` にしないといけないようです

を実行したら、２つバージョンが違った `@types/react` を一個に解決できて、今回のエラーを修正できました。

```bash
$ yarn list @types/react
yarn list v1.22.11
warning Filtering by arguments is deprecated. Please use the pattern option instead.
└─ @types/react@17.0.27
```