TypeScriptでnoImplicitAnyを導入するために、妥協してsuppressImplicitAnyIndexErrorsを導入する

この記事は[フラーアドベントカレンダー 2019](https://adventar.org/calendars/4155) 23日目の記事です。

# noImplicitAnyとは？

TypeScriptには多くの[Compiler Options](https://www.typescriptlang.org/docs/handbook/compiler-options.html)が存在します。`Compiler Options`の設定によって、型に対しての書き方を厳密にすることも、緩くすることもできます。

型の厳密さに関するTypeScriptの`Compiler Options`の代表的なもの一つは`--noImplicitAny`です。`--noImplicitAny`の公式ドキュメントの説明は次の通りです。

> Raise error on expressions and declarations with an implied any type.

実際のコードでみてみましょう。次のようなコードがあります。

```typescript
function greeting(name) {
    return `Hello ${name}!`;
}

console.log(greeting("Ryota"));
```

このコードは、`Compiler Options`として`--noImplicitAny`を有効にしていた場合、次のようなエラーとなります。

```
TSError: ⨯ Unable to compile TypeScript:
main.ts:1:22 - error TS7006: Parameter 'name' implicitly has an 'any' type.

1 function loadMessage(name) {
```

1行目のgreeting関数のnameパラメーターが原因でコンパイルエラーになっています。

`--noImplicitAny`は、「型を何も書かない場合は暗黙的にanyになるけど、それはだめで、何かしら型を明示的にしないとだめだよ。」という`Compiler Options`です。このコンパイルエラーを解決するために、次のように型を明示してあげましょう。

```typescript
function greeting(name: string) {
    return `Hello ${name}!`;
}

console.log(greeting("Ryota"));
```

greeting関数は、stringをパラメーターとしてとることを明示的にしました。

ちなみに次のコードでも大丈夫です。

```typescript
function greeting(name: any) {
    return `Hello ${name}!`;
}

console.log(greeting("Ryota"));
```

暗黙的なany型はだめですが、明示的にany型を記述することは全く問題ありません。


# suppressImplicitAnyIndexErrorsとは？

`Compiler Options`として`--noImplicitAny`が有効になっている場合、次のコードはコンパイルエラーになります。

```typescript
const gradeFactors = {
    gradeA: 1.0,
    gradeB: 0.5,
    gradeC: 0.3
};

const grade: string = 'gradeA';

const factor = gradeFactors[grade];

console.log(`${grade} : ${factor}`);
```

エラーは次の通りです。

```
main.ts:9:16 - error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ gradeA: number; gradeB: number; gradeC: number; }'.
  No index signature with a parameter of type 'string' was found on type '{ gradeA: number; gradeB: number; gradeC: number; }'.

9 const factor = gradeFactors[grade];
```

`string`は、`{ gradeA: number; gradeB: number; gradeC: number; }`のインデックスタイプとして使えないからです。インデックスアクセスの部分がコンパイルエラーとなります。

これを解決する方法は2つあります。

1つ目はアクセスできるように型を変える方法です。



gradeの型を次のように`string`から「`gradeA` | `gradeB` | `gradeC`」などにすることでコンパイルエラーを解消することができます。

```typescript
const gradeFactors = {
    gradeA: 1.0,
    gradeB: 0.5,
    gradeC: 0.3
};

const grade: 'gradeA' | 'gradeB' | 'gradeC' = 'gradeA';
// これ以外にも以下などでもOK
// const grade = 'gradeA';
// const grade: 'gradeA' = 'gradeA';
// const grade: 'gradeA' | 'gradeB' = 'gradeA';

// または次のようにGrade型を作るのも有効
// type Grade = 'gradeA' | 'gradeB' | 'gradeC';
// const grade: Grade = 'gradeA';

const factor = gradeFactors[grade];

console.log(`${grade} : ${factor}`);
```

2つ目はコンパイルオプションを追加することです。

`Compiler Options`に`--suppressImplicitAnyIndexErrors`というオプションがあります。
これは「厳密な型チェックを一部妥協する」コンパイルオプションです。
`--noImplicitAny`が有効な時に、先のコンパイルエラーのようなインデックスアクセスによるコンパイルエラーを抑制する機能があります。

先のコードは`--noImplicitAny`に加えて、`--suppressImplicitAnyIndexErrors`を有効にすることで、コンパイルエラーを解消することができます。

# 実プロダクトでnoImplicitAnyを導入するために妥協する

実プロダクトで`--noImplictAny`を導入することを考えてみましょう。例えば、コードベースはある程度大きく、すでに数年開発が続き、複数人で開発していて、お金も生み出している実プロダクトで、です。そんなプロダクトを改善するために、今まで導入されていなかった`--noImplictAny`を導入しましょう。

`--noImplictAny`を導入するために、先に説明した「フィールドやメソッドのパラメータの型が明示的になっていない箇所」、「無効な型でインデクサにアクセスしている箇所」を修正する必要があります。

フィールドやメソッドのパラメータに対して、単純に型を明示する作業はある程度簡単です。（実際は変更箇所が多かったり、同僚とコンフリクトしないように気をつける必要があり、そんなに簡単ではありません。）

一方で、「無効な型でインデクサにアクセスしている箇所」を修正する作業はかなり大変になる場合があります。型を定義・変更しないといけない、その型の変更が伝版することがあるからです。

筆者個人の意見として、`--noImplictAny`を導入するために、`--suppressImplicitAnyIndexErrors`を使って妥協することは、有効な戦略だと思います。