TypeScript 3.7 の Assertion Functions でコンパイルエラー

エラーメッセージの意味がよくわからず、解決するまで少し時間が掛かってしまったので。

## 再現

```ts
const assertIsString = (val: any): asserts val is string => {
    if (typeof val !== "string") {
        throw new Error("Not a string!");
    }
}

const someValue: unknown = "foo"; // const someValue: unknown

assertIsString(someValue); // const someValue: unknown

console.log(someValue); // const someValue: string
```

[TypeScript Playground](https://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=12&pc=1#code/MYewdgzgLgBAhhCBTATlAkhAylFBLMAcxgF4YAKANzgBsAueMATwEoGFk0IZqaY9u0fEVIA+GAG8AUDFn8AZhShMADkhCLeMAIQkyAIiEFC+lpJlzLUABYoQAdxhgkjgKIo7KcvoByIWHAwRkTapgDcFrIAvlIxUqCQsBAgALZIAGq0AK5IDFlgANZgDmCkMPryICD6YTAA9HUwCdBBqRnZuTD5RSVSUhyoGNi4xuTJaZk0OSy1DU3gLePtU53dxfZgfc0gNEgAdDQghGNtk9Ozjc1Jpx0MwYRSQA)

9 行目でエラー。

```
Assertions require every name in the call target to be declared with an explicit type annotation.(2775)
```

## ~~解決~~

<details><summary>（outdated な解決策のため折りたたみました。これはもう参考にする必要はありません。）</summary><div>

```ts
function assertIsString(val: any): asserts val is string {
    if (typeof val !== "string") {
        throw new Error("Not a string!");
    }
}

const someValue: unknown = "foo"; // const someValue: unknown

assertIsString(someValue); // const someValue: unknown

console.log(someValue); // const someValue: string
```

~~[TypeScript Playground](https://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=12&pc=1#code/GYVwdgxgLglg9mABAQwM6oKYCcoElUDKUWMYA5gBQBuyANgFwpgCeAlI2pjqojbYjB6pipMogDeAKEQyBwRBSjMADhjjy+iAIQBeHYgBEwkuQOsJ02VagALLHADuiMBicBRLPawUDAOThQKIjGolpmANyWMgC+krGSEAjCwXAAthgAanQgGIzgANZgjkj6BsBwcAbhiAD0NYiJYMmoaZnZuYgFRQ5gkpKc2HiEIuQULelZtDms1XUNSYHjbVMdXcV9jS20GAB0tHCUS5PTs-Wbi63HHSHkkkA)~~

~~どうやら asserts function は、 `function` で宣言すべきらしい。~~
</div></details>

## 解決 - 令和最新版

```ts
const assertIsString: (val: any) => asserts val is string = (val) => {
  if (typeof val !== "string") {
    throw new Error("Not a string!")
  }
}

const someValue: unknown = "foo" // const someValue: unknown

assertIsString(someValue) // const someValue: unknown

console.log(someValue) // const someValue: string
```

[TypeScript Playground](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhhCBTATlAkhAylFBLMAcwC4YAKANzgBtS4wBPAShgF4A+eRVKCGK6jDx9o+Im3ICWHGAG8AUDCEAzclAYAHJCFUCYAQlasYAIlEFCJlgqVKoACxQgA7jDBJXAURROUZEwByILBwMOZE+laKMAC+8nHyoJCwECAAtkgAajQArkikOWAA1mAuYBImyiAgJjAA9HUwSdBh6Vm5+TCFJWXy8gjIaJg4YoRkqRnZ1HksDU3gLRPt053dpc5gfc0g1EgAdNQgY0tTM-WNzSltp53hhPJAA)

関数内に型を書くのではなく、変数そのものに対して型宣言すればよい。
もちろん Generics も対応。

```ts
const assertNonNull: <T>(x: T | null | undefined) => asserts x is T = (x) => {
  if (x == null) {
    throw new Error("Should exist value.")
  }
}

const maybe: number | undefined = 0 as any

assertNonNull(maybe) // const maybe: number | undefined

console.log(maybe) // const maybe: number
```

[TypeScript Playground](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhhCBTATlAcudBXANrgLhgB4AVAPgAoAPI0mAHxjD10ZmzABMkAzASzBIuAShgBecvESooEGNRj959cTBpjJMAN4AoGEt7rF4tS3xi9Bg1AAWKEAHdmSZwFEUDlJQBEAZVsQPC4YJGplWAA3OFxsJAA6HxF9GABfXXTdUEhYAFs4AE8AIyQiFlySlHZOHgEhELUABml4MALdXQRkNEwwHHxKfOKkMQB6UZhs6BghkrLsCtRq7j5BYQ6pkFwE3BAAc0HCkrGJqbyj0uYFyqA)

## 結論

以下の問題があるため、JavaScript では「全て Arrow function にすべき」と考えている。

- this の参照先がわかりづらい問題
- function を再宣言（上書き）できてしまう問題

~~しかし、どちらも現代的な JS/TS 開発で遭遇することは滅多になくなってしまった。~~
~~なので、「全て function で宣言すべき」としてもよいのかもしれない。~~

晴れて function を使う理由がなくなったので、 ***全て arrow function にすべき***。

### おまけ: ESLint で function 宣言を禁止する

```js:.eslintrc.js
/**
 * @type {import("eslint").Linter.Config}
 */
const config = {
  rules: {
    // ...
    "no-restricted-syntax": [
      "error",
      {
        selector: "FunctionDeclaration",
        message: "Do not declare function. Use arrow function instead.",
      },
    ],
    // ...
  }
}
```

```
error  Do not declare function. Use arrow function instead  no-restricted-syntax
```

さようなら function

## 参考

https://github.com/microsoft/TypeScript/issues/34523

https://github.com/microsoft/TypeScript/issues/33602#issuecomment-535218482