any警察24時👮‍♂️

これは [TypeScript Advent Calendar 2019](https://qiita.com/advent-calendar/2019/typescript) 22日目の代打記事です。

[前回の投稿](https://qiita.com/Takepepe/items/3353159894ed57b6f0a8)が好評だったため、ブラッシュアップ・CLIツール化し、any警察👮‍♂️を npm publish しました。READMEに記載したとおり、devDependencies に追加し、`npx anycop`を実行するとプロジェクトのany診断ができます。
anycop: [https://www.npmjs.com/package/anycop](https://www.npmjs.com/package/anycop)


## おことわり
これはanyを駆逐せよ、という目的で作ったものではありません。anyの使用状況が可視化されれば、課題点を把握し、チームで共有する事が出来るでしょう。時間のある時、型安全のカバレッジを向上する様な取り組みが出来れば、それはとても素晴らしい事です。any は悪者ではありません。TypeScript にこの仕様がなければ、今の JavaScript エコシステムに静的型付けを適用することは出来なかったでしょう。このツールで、any とより良い関係を築けたら幸いです。


# 変更点
前回投稿では「変数定義のany推論」に限定しましたが、パワーアップし、次の anyを検知する様になりました。

- 変数宣言の any型推論
- 関数引数の any型推論
- BindingElementの any型推論
- 関数の any戻り型推論
- Arrow関数の any戻り型推論
- AsExpression（as any アサーション）

TypeScriptプロジェクトにインストールし、実行した結果は以下の様になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59071/f680349a-cdb1-e332-9d30-d0dfde7b11f9.png)


### 変数宣言の any型推論
変数の明示的なany注釈はもちろん、any 推論も検知します。

```typescript
function greet(message: any) {
  return message
}
// inferred "any" at VariableDeclaration
const message = greet('hello')
```


### 関数引数の any型推論
引数の明示的なany注釈はもちろん、参照している型定義が any 相当の場合も検知します。

```typescript
// annotation of "any" at ParameterDeclaration (message)
function greet(message: any) {
  return message
}
```


### BindingElementの any型推論
BindingElementで展開された参照が、any推論されている時も検知します。

```typescript
type Props = { a: any, b: any }
// inferred "any" at BindingElement (a & b)
function greet({ a, b }: Props) {
  return { a, b }
}
```


### 関数の any戻り型推論
戻り型の明示的なany注釈はもちろん、any になってしまっている戻り型も検知します。

```typescript
// inferred "any" at FunctionDeclReturn
function greet() {
  const message: any = 'hello'
  return message
}
```


### Arrow関数の any戻り型推論
戻り型の明示的なany注釈はもちろん、any になってしまっている戻り型も検知します。

```typescript
// inferred "any" at ArrowFunctionReturn
const abc = (param: string) => {
  switch (param) {
    case 'a':
      return true
    case 'b':
      return false
    case 'c':
      return '' as any
  }
}
```


### AsExpression（as any アサーション）
asキーワードによるアサーションを検知します。

```typescript
function greet() {
  return 'hello' as any // at AsExpression
}
```


# anycop.config.js
プロジェクトルートに`anycop.config.js`を設置することで、固有の設定ができます。`errorThrethold`はエラー報告の閾値です。「TypeSafe Coverage」の合計がこの数値を下回った場合、エラーを発生させることができます。インテグレーションツールへの導入などに活用してください。

```javascript
module.exports = {
  targetDir: ".",
  errorThrethold: 0.2
}
```


# 検査項目増加による実装の変更点
`ts.SourceFile`は`ts.Node`のルートノードであり、そこを起点に実行する再帰関数（visit関数）を見直しました。`ts.Node`は`ts.SyntaxKind`を持ち合わせており、この SyntaxKind で処理を選り分けています。

```typescript
function visit(node: ts.Node) {
  switch (node.kind) {
    case ts.SyntaxKind.VariableDeclaration:
      // 変数宣言であれば
      // ....処理
      break
    case ts.SyntaxKind.Parameter:
      // 引数であれば
      // ....処理
      break
    case ts.SyntaxKind.BindingElement:
      // BindingElementであれば
      // ....処理
      break
    case ts.SyntaxKind.FunctionDeclaration:
      // 関数宣言であれば
      // ....処理
      break
    case ts.SyntaxKind.ArrowFunction:
      // アロー関数宣言であれば
      // ....処理
      break
    case ts.SyntaxKind.AsExpression:
      // アサーションであれば
      // ....処理
      break
  }
  ts.forEachChild(node, visit)
}
visit(source)
```


# ts.TypeChecker.getReturnTypeOfSignature

「うっかり関数戻り型がanyに落ちていた…」という検査を追加しました。`ts.SignatureDeclaration`は、関数宣言やアロー関数を指します。`ts.TypeChecker`に備わった`getSignatureFromDeclaration`関数と`getReturnTypeOfSignature`関数を利用することで、`flags` を得ることができます。これをもって`ts.TypeFlags.Any`と一致しているかを判定し、Anyか否かを判定しています。

```typescript
// ts.ArrowFunction / ts.FunctionDeclaration は
// ts.SignatureDeclaration のサブタイプ
// node: ts.SignatureDeclaration
const signature = checker.getSignatureFromDeclaration(node)
if (signature) {
  const { flags } = checker.getReturnTypeOfSignature(signature)
  return checkAny(source, node, flags, name)
}
```