TypeScriptコンパイラ入門 (1) エラーメッセージを改善しよう

皆さん、TypeScript使ってますか？　TypeScriptはオープンソースで開発されているプログラミング言語であり、JavaScriptプログラミングに静的型による安全性をもたらしてくれます……という説明は、おそらくこの記事を開いたみなさんには今さらでしょう。

TypeScriptはOSSであり、我々ユーザーがPull Requestを送って開発に貢献することができます。筆者はこの前のゴールデンウィークに[初めてのPRをTypeScriptに送って数時間くらいでマージされました](https://github.com/microsoft/TypeScript/pull/31194)。これは先日リリースされたTypeScript 3.5に含まれています。

このとき初めてTypeScriptコンパイラのソースコードをじっくり読んでおり、それが筆者が最近書いた別記事[TypeScriptの型推論詳説](https://qiita.com/uhyo/items/6acb7f4ee73287d5dac0)の執筆にも役立っています。

皆さんにもぜひTypeScriptへの貢献をおすすめしたく、その手引として**TypeScriptコンパイラ入門**を書くこととしました。第1回では、恐らく最もとっつきやすい部分である**エラーメッセージの改善**について解説します。今回筆者が送ったPRもエラーメッセージの改善です。(2)以降は筆者が次のPRを送ったら書きます。

この記事では筆者がどのような改善を行ったのか紹介し、修正を完了するまでの思考ステップを全て解説します。なお、記事中ではTypeScriptコンパイラのソースコードを必要に応じて掲載しながら解説します。ソースコードの著作権表記は記事末尾を参照してください。

## 改善の内容

今回筆者は次のissueをクローズしました。

- [Related error spans on non-async functions containing 'await'](https://github.com/microsoft/TypeScript/issues/26586)

これは、`async`関数の中以外の場所で`await`を使ったときのエラーメッセージに関するissueです。例として次のコードを見てください。このコードは`async`ではない普通の関数の中で`await`を使っています。

```ts:test.ts
function foo() {
  await null;
}
```

これに対する従来のエラーメッセージ（TypeScript 3.4以前）は以下のスクリーンショットのように「'await'式は、非同期関数内でのみ使用できます。」とだけ表示していました。

[![Screenshot from Gyazo](https://gyazo.com/54f30e48f892b7aa7563052eceafe97a/raw)](https://gyazo.com/54f30e48f892b7aa7563052eceafe97a)

今回のPRによって、TypeScript 3.5では次のスクリーンショットのようにメッセージが追加されています。

[![Screenshot from Gyazo](https://gyazo.com/1583aa48543a2252e1f4a75f1a20c8e4/raw)](https://gyazo.com/1583aa48543a2252e1f4a75f1a20c8e4)

すなわち、従来のメッセージに加えて「Did you mean to mark this function as 'async'?」というメッセージが追加されています（これは新しいメッセージなのでまだ翻訳されていないようです。いつ翻訳されるのかはよく分かりませんが）。さらに、このメッセージは`test.ts(1, 10)`という位置を示しています。この位置は`function foo() {`の`foo`の部分に相当します。

つまるところ、`await`を含んでいる関数がどこなのかを示すメッセージを従来のエラーメッセージに加えて表示したということです。関数が大きいときに便利かもしれませんね。

このissueは`good first issue`ラベルが付いているissueの中から目ぼしい物を選びました。ただ、TypeScriptへの貢献を狙っている人は意外と多く、そのようなissueには大抵未経験っぽい人が「やります😆」的なコメントを付けています。誰にも取られずに残っている`good first issue`は滅多にありません。

それにも関わらず残っている`good first issue`というのはいくつかのパターンに分かれます。だいたいは次の3つです。

1. やりますと言った人がそのまま音沙汰が無くなって放置されている。
2. やりますと言った人が「やり方教えて😉」と言って無視された状態で放置されている。
3. ちゃんとPRが出たがPRが放置されている。

3はだめですが、1と2の場合は残念ながら彼らにはまだ早かったのでしょう。数カ月も放置されていれば奪って大丈夫です。筆者が取ったissueも過去2人の人が挑戦して撃沈していました。

## 開発環境

TypeScriptへの貢献に際しては開発環境を整える必要があります。詳しくは[CONTRIBUTING.md](https://github.com/Microsoft/TypeScript/blob/master/CONTRIBUTING.md)に書いてあるのでそちらを参照しましょう。

あとでまた説明しますが、とりあえずソースを変更したら`gulp`を実行すれば（`gulp`をグローバルインストールしていない場合は`npx gulp`でOK）ビルドされます。ビルドすると`built/local/tsc.js`という場所にソースからビルドされた`tsc`ができるので、これを使って動作を確かめられます。

TypeScriptコンパイラはそれ自身がTypeScriptで書かれています。いわゆるセルフホスティングというやつですね。TypeScriptということで筆者はVSCodeを使ってコードを編集しようとしましたが、コードベースが大きいためメモリがたった8GBのPCでは重くて使い物にならず、結局vimを使いました。

では、いよいよ修正に入っていきましょう。

## エラーメッセージはどこで定義されているのか

今回はエラーメッセージに関する変更ということで、そもそもTypeScriptがどのようにエラーメッセージを定義しているのかを理解しておく必要があります。筆者は事前にいくつかのPRを見てエラーメッセージがどこで定義されているのか理解しました。

答えを言ってしまうと、`src/compiler/diagnosticMessages.json`に定義されています。ここに全てのエラーメッセージが定義されています。例えば上で例示したエラーメッセージは次のように書かれています。

```json
    "'await' expression is only allowed within an async function.": {
        "category": "Error",
        "code": 1308
    },
```

このように、エラーメッセージをキーとして`category`と`code`の情報が書かれています。エラーコードについては[wikiで説明されています](https://github.com/microsoft/TypeScript/wiki/Coding-guidelines#diagnostic-message-codes)。このエラーメッセージは`await`が`async`関数の外では使えないという文法的な制約なので1000番台が割り当てられます。

今回のPRでは`Did you mean to mark this function as 'async'?`という新しいメッセージを追加するので、このファイルに新しいメッセージを追加しました。

```json
    "Did you mean to mark this function as 'async'?": {
        "category": "Error",
        "code": 1356
    },
```

元のエラーメッセージが1000番台（構文に関するエラーメッセージ）なので、同じく1000番台の一番最後にこのメッセージを追加します。

これでPRはもうできたも同然ですね。あとはこのエラーメッセージを表示する処理を追加するコードを書けば完成です。

なお、`diagnosticMessages.json`を変更したら`gulp generate-diagnostics`を実行しておきましょう。実は`diagnosticMessages.json`からはTypeScriptの型定義などのコードが自動生成されており、これを実行しておかないとコンパイラのソースコード中から当該エラーメッセージを利用できません。

## 変更すべき箇所を探す

ではいよいよコンパイラ本体を変更していきます。しかし、どこを編集すればいいのでしょうか。

まず前提として、TypeScriptの型検査をはじめとするメインの処理（構文解析をして抽象構文木 (AST) [^ast]ができた状態でそれに対してエラーチェックを行う部分）は`src/compiler/checker.ts`に書かれています。よって、今回いじるのはこのファイルです。厳密には`await`のチェックは型検査とかではなく構文上のエラーなのですが、構文解析の時点でこれをハンドルするのは面倒なので、構文解析の段階では一旦受け入れて抽象構文木を作り、その後でエラーを出すという戦略になっています。

[^ast]: **抽象構文木** (AST) とは、プログラムのソースコードを意味的に表す木構造です。木構造とかよく分からないという方はとりあえずプログラムのソースコードを表すオブジェクトだと思っておきましょう。プログラムの各単位（文とか式とか）に対して対応するオブジェクト（**ノード**と呼ばれる）が存在します。例えば`x + 1`という式全体に対応するノードは、自身が`+`式であるという情報、左の式（子ノード）と右の式（子ノード）がそれぞれ何かという情報を持つオブジェクトです。この場合左の子ノードは`x`という式に対応するノードであり、右の子ノードは`1`という式に対応するノードです。

型検査などを全部このファイル1つに詰め込んでいるため3万行くらいあってとてもでかいですが、全部を把握する必要はありません。今回はエラーメッセージを変えることになるため、まず当該のエラーメッセージを出している箇所を探しました。

ちょっと検索すると、以下の`checkAwaitExpression`関数でこのエラーメッセージが使われていることが分かります（ソースコードはPR反映前のコミット`e8161ef`のもの）。

```ts
        function checkAwaitExpression(node: AwaitExpression): Type {
            // Grammar checking
            if (produceDiagnostics) {
                if (!(node.flags & NodeFlags.AwaitContext)) {
                    grammarErrorOnFirstToken(node, Diagnostics.await_expression_is_only_allowed_within_an_async_function);
                }

                if (isInParameterInitializerBeforeContainingFunction(node)) {
                    error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer);
                }
            }

            const operandType = checkExpression(node.expression);
            return checkAwaitedType(operandType, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
        }
```

`checkAwaitExpression`という関数名から察するに、これは`await`式に対する型チェックなどを行う関数でしょう。返り値が`Type`ですから、`await`式の型推論の結果が返されると思われます。前述のように、そのついでに`await`が使用可能な場所かどうかのチェックを行っています。

引数`node`の型が`AwaitExpression`となっていることから、これは`await expr`という式を表すASTのノードであることが分かります。最初の`// Grammar checking`というコメントがついている`if`文の中が、`await`が正しい場所で使われているかどうかのチェックのようですね。

当該のエラーメッセージは次の文で使われています。

```ts
grammarErrorOnFirstToken(node, Diagnostics.await_expression_is_only_allowed_within_an_async_function);
```

引数の`Diagnostics.await_expression_is_only_allowed_within_an_async_function`という定数っぽいものがエラーメッセージです。この`Diagnostics`というオブジェクトは前述の`diagnosticMessages.json`から自動でコード生成されたものです。元々の`Did you mean to mark this function as 'async'?`というメッセージ（プロパティ名）から空白を`_`に変換したり記号を取り除いたりしたものがエラーメッセージを表すプロパティ名となります。

`grammarErrorOnFirstToken`というのは、名前から察するに`node`の位置（`FirstToken`とあるので最初のトークン、すなわち`await`の部分でしょう）で第2引数で指定したメッセージを発生させるという処理をする関数でしょう。これは先ほどお見せしたスクリーンショットと合致していますね。

[![Screenshot from Gyazo](https://gyazo.com/54f30e48f892b7aa7563052eceafe97a/raw)](https://gyazo.com/54f30e48f892b7aa7563052eceafe97a)

ちなみに、エラーの発生条件である`!(node.flags & NodeFlags.AwaitContext)`が「`await`式が`async`関数内にない」という条件を表していることになります。`node.flags`は多分構文解析器がASTを生成するときに付与する情報でしょう。構文解析の時点で「そのノードの文脈が`await`使用可能な文脈化どうか」という情報が付加されていることになります。まあ自然ですね。

なお、この部分を発見する方法ですが、`await_expression_is`とかでソースコード内をすぐ検索するとすぐに見つかりました。`'await' expression is only allowed within an async function.`というエラーメッセージが`Diagnostics.await_expression_is_only_allowed_within_an_async_function`という名前でソースコード内に現れることを知っていれば見つけるのは簡単です。

もちろん、これを違うエラーに変更したりするとちゃんと反映されます。まずTypeScriptのソースコードをいじってちょっと挙動を壊してみるのも面白いですね。変更が反映済みの`tsc`は、`gulp`でビルドしたあと`built/local/tsc.js`に存在しています。これを使ってTypeScriptファイルをコンパイルすることで挙動を確かめましょう。

## どう変更するか検討する

では、変更すべき箇所が分かったので次はどう変更すればいいのか検討しましょう。

今回の目的は「既存のエラーメッセージにサブメッセージとして`Did you mean to mark this function as 'async'?`を追加する」ということですから、エラー生成部分に手を入れる必要があります。まあ、さすがにサブメッセージ的な機構は既にあるはずです。そうでないとさすがに`good first issue`ではありません。

ということで、まず`grammarErrorOnFirstToken`の中身を見てみる必要があります。

```ts
        function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean {
            const sourceFile = getSourceFileOfNode(node);
            if (!hasParseDiagnostics(sourceFile)) {
                const span = getSpanOfTokenAtPosition(sourceFile, node.pos);
                diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2));
                return true;
            }
            return false;
        }
```

今回使われている2つの引数の他に3つほどオプショナルな引数がありますが、これはプレースホルダーを持つエラーメッセージ用であることはまあ分かりますね。例えば`Cannot find name '{0}'.`のようなエラーメッセージが該当します。

`hasParseDiagnostics(sourceFile)`は、関数名から察するに「このファイルで既に構文エラーのメッセージが発生しているか」を判定していると思われます。すでに発生している場合は追加でエラーを発生させるのを避けているわけですね。一般に、パースエラーが1度発生した時点で正常にソースを読み込むのは不可能でありいくらでも構文エラーが発生しますから、複数の構文エラーメッセージを出す意味がそんなに無いわけです。

その中でやっていることは、`createFileDiagnostic`関数でエラーメッセージを表すオブジェクトを作成し、それを`diagnostics.add`で発生したエラーメッセージの一覧に追加しているように見えます。`span`というのはエラーの範囲（画像の赤線が引かれている部分）を表すオブジェクトです。今回は`getSpanOfTokenAtPosition`という関数名から察するに、一つのトークン（今回は`await`式の最初のトークンなので`await`キーワードでしょう）に相当する範囲を表すオブジェクトとなります。

今回のミッションは`createFileDiagnostic`で作られたエラーメッセージ（型を見ると`DiagnosticWithLocation`型のオブジェクトのようです）に対して、付加的な情報、すなわち“サブメッセージ"のようなものを追加することです。ということで、この`DiagnosticWithLocation`オブジェクトをいじる必要があります。どういじればいいのか調べるために、`DiagnosticWithLocation`型の定義を見てみましょう。

```ts
    export interface DiagnosticWithLocation extends Diagnostic {
        file: SourceFile;
        start: number;
        length: number;
    }
```

なるほど、これは`Diagnostic`オブジェクトにファイル中の位置を指し示すプロパティを追加したオブジェクトですね。名前から察するに、この`Diagnostic`がメインの型のようです。

```ts
    export interface Diagnostic extends DiagnosticRelatedInformation {
        /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
        reportsUnnecessary?: {};
        source?: string;
        relatedInformation?: DiagnosticRelatedInformation[];
    }
    export interface DiagnosticRelatedInformation {
        category: DiagnosticCategory;
        code: number;
        file: SourceFile | undefined;
        start: number | undefined;
        length: number | undefined;
        messageText: string | DiagnosticMessageChain;
    }
```

`code`などのいかにもそれっぽい情報がありますが、目に付くのは`relatedInformation`プロパティです。これは`DiagnosticRelatedInformation[]`型を持ち、ある`Diagnostic`型に対して0個以上の`DiagnosticRElatedInformation`オブジェクトを紐付けられるようになっています。これはまさに「エラーメッセージが子メッセージを持つ」という状況を表しており、今回の目的に激しく合致しています。

ということで、ここまでのコードリーディングでやるべきことがはっきりしました。`Did you mean to mark this function as 'async'?`というメッセージを表す`DiagnosticRelatedInformation`オブジェクトを作成し、それを既存のエラーメッセージの`relatedInformation`に追加してやればいいのです。

## どう実装するか検討する

何をすればいいかは分かりましたので、それをどう実装するかを次に考えます。やりたいことの通りにコードを書けばそれでいいのですが、あまりに汚い、というか既存のコードと合致しないコードは書きたくありません。特に自分は、今回やりたいことをなるべく簡単にやってくれる既存の関数があるのかどうか気になりました。

ということで、まずやったことは「ソースコードを`relatedInformation`で検索する」ということです。`relatedInformation`を扱う便利関数を探すわけですね。

そうすると次の`addRelatedInfo`関数が随所で呼ばれていることが分かります。まあ見れば分かる通り、与えられた`Diagnostic`オブジェクトの`relatedInformation`に`DiagnosticRelatedInformation`オブジェクトたちを追加してくれる便利関数ですね。

```ts
    export function addRelatedInfo<T extends Diagnostic>(diagnostic: T, ...relatedInformation: DiagnosticRelatedInformation[]): T {
        if (!diagnostic.relatedInformation) {
            diagnostic.relatedInformation = [];
        }
        diagnostic.relatedInformation.push(...relatedInformation);
        return diagnostic;
    }
```

これがあちこちで呼ばれているということは、これをさらにラップしてくれるハイレベルな（`grammarErrorOnFirstToken`のような）便利関数は無さそうということが分かります。

これにより、実装方針が固まりました。具体的には「新しいエラーメッセージを表す`Diagnostic`オブジェクトを作り、`addRelatedInfo`を使って既存の処理により作られた`Diagnostic`オブジェクトにそれを追加し、最後に`diagnostics.add`する」という処理を`checkAwaitExpression`内にベタ書きすることにしました。

## いざ実装

ここまで決めればあとは書くだけです。実装の中身を順番に見ていきましょう。

### サブメッセージを作る

まず、今回追加したいサブメッセージを表す`Diagnostic`オブジェクトを作る必要があります。これもエラーメッセージの一種なわけですが、ご存知のようにエラーメッセージは主に「エラーの場所」と「エラーメッセージ」から構成されます（前者は、`DiagnosticRelatedInformation`の定義を見れば分かるように、無いこともあるようですが）。

今回のメッセージ、すなわち`Did you mean to mark this function as 'async'?`がどこを指し示していればいいか考えてみましょう。これは「この関数を`async`関数にするんじゃないですか？」的な意味なので、「この関数」を指し示している必要があります。この関数とは、問題の`await`式を含む関数です。

ここで、「与えられたノード（今回は`AwaitExpression`ノード）を含む関数を取得する」という処理が必要になりました。さすがにこの処理を行ってくれる関数はすでにどこかにあるだろうということでソースを検索します。関数名に`contain`あたりが入っているだろう、のようなアタリを付けて適当に検索していくと`getContaingFunction`というそれっぽい関数が見つかります。

```ts
    export function getContainingFunction(node: Node): SignatureDeclaration | undefined {
        return findAncestor(node.parent, isFunctionLike);
    }
```

引数は`Node`で返り値は`SignatureDeclaration | undefined`ですね。`undefined`の可能性があることは「`node`を含む関数」が存在しないこともあるからでしょう。`SignatureDeclaration`というのが関数を表すオブジェクトのようなので調べてみましょう。

```ts
    export type SignatureDeclaration =
        | CallSignatureDeclaration
        | ConstructSignatureDeclaration
        | MethodSignature
        | IndexSignatureDeclaration
        | FunctionTypeNode
        | ConstructorTypeNode
        | JSDocFunctionType
        | FunctionDeclaration
        | MethodDeclaration
        | ConstructorDeclaration
        | AccessorDeclaration
        | FunctionExpression
        | ArrowFunction;
```

関数の種類ごとに型が細分化されていますね。ただ、あまり細かく見ていかなくても大丈夫です。ちょっと見ていくと、これらの個別の型は全て`SignatureDeclarationBase`の部分型であることが分かります。

```ts
    export interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer {
        kind: SignatureDeclaration["kind"];
        name?: PropertyName;
        typeParameters?: NodeArray<TypeParameterDeclaration>;
        parameters: NodeArray<ParameterDeclaration>;
        type?: TypeNode;
        /* @internal */ typeArguments?: NodeArray<TypeNode>; // Used for quick info, replaces typeParameters for instantiated signatures
    }
```

さらに継承関係を辿って行くと、これらは結局`Node`であることが分かります。このことが今回重要です。

```ts
    export interface Declaration extends Node {
        _declarationBrand: any;
    }

    export interface NamedDeclaration extends Declaration {
        name?: DeclarationName;
    }
```

すなわち、`getContainingFunction`の返り値は（`undefined`でなければ）関数を表す`Node`オブジェクトだったのです。

特定のノードに対してエラーを発生させるという処理はとてもよくありそうですから、`Node`があればそれに対するエラーメッセージ（`Diagnostic`）を生成してくれる関数はすでにありそうです。ということで適当にそれっぽい名前で再び検索します。

すると、`createDiagnosticForNode`という関数が見つかります。とりあえずこの部分のコードを書いてみるとこんな感じになります。

```ts
const func = getContainingFunction(node);
if (func && func.kind !== SyntaxKind.Constructor) {
    const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async);
}
```

`node`が`AwaitExpression`オブジェクトだったので、それを含む関数を取得し、それがあったら`createDiagnosticForNode`でエラーメッセージを生成します。メッセージが指し示す対象はもちろん当該の関数ノード、すなわち`func`です。

### 元々のエラーメッセージに追加する

こうしてできたサブメッセージを`addRelatedInfo`で元々のエラーメッセージに追加したいですね。元々のエラーメッセージを作る処理は、`grammarErrorOnFirstToken`を呼び出す代わりにその中身をベタ書きすることにしましょう。そして、`diagnostics.add`の前にサブメッセージを追加する処理を挟みます。するとこうなります。

```ts
const sourceFile = getSourceFileOfNode(node);
if (!hasParseDiagnostics(sourceFile)) {
    const span = getSpanOfTokenAtPosition(sourceFile, node.pos);
    const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expression_is_only_allowed_within_an_async_function);
    const func = getContainingFunction(node);
    if (func) {
        const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async);
        addRelatedInfo(diagnostic, relatedInfo);
    }
    diagnostics.add(diagnostic);
}
```

元々のエラーメッセージである`diagnostic`を作るところまでを`grammarErrorOnFirstToken`から複製してきました。そして、`addRelatedInfo`を用いて`diagnostic`に`relatedInfo`を追加していることが分かります。

以上で実装はほとんど終了です。

### 微調整

最初は以上のような実装にしていたのですが、ひとつ気づいた点がありました。それは、**外の関数がコンストラクタだった場合はサブメッセージを付けてはいけない**ということです。このサブメッセージは関数を`async`にすることを提案するものですが、コンストラクタは`async`にできないからです。

というわけで、`func`がコンストラクタの場合はサブメッセージを追加する処理を書くことにしました。そのためには`func`がコンストラクタかどうか判定できる必要がありますね。ここで`func`は`SignatureDeclaration`だったことを思い出します。

```ts
    export type SignatureDeclaration =
        | CallSignatureDeclaration
        | ConstructSignatureDeclaration
        | MethodSignature
        | IndexSignatureDeclaration
        | FunctionTypeNode
        | ConstructorTypeNode
        | JSDocFunctionType
        | FunctionDeclaration
        | MethodDeclaration
        | ConstructorDeclaration
        | AccessorDeclaration
        | FunctionExpression
        | ArrowFunction;
```

この中に`ConstructorDeclaration`というのがあり、これがいかにもそれっぽいですね。

```ts
    export interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer {
        kind: SyntaxKind.Constructor;
        parent: ClassLikeDeclaration;
        body?: FunctionBody;
        /* @internal */ returnFlowNode?: FlowNode;
    }
```

他に`ConstructorTypeNode`というのもありますが、こちらは型を表すノードの話なので今回は関係ありません。

`ConstructorDeclaration`は`kind: SyntaxKind.Constructor;`とあり、`SyntaxKind`というのはノードの種類を表すenum型です。つまり、これは[筆者の既存記事](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a#%E4%BB%A3%E6%95%B0%E7%9A%84%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B%E3%81%A3%E3%81%BD%E3%81%84%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)でも解説しているタグ付きunionのパターンになっていることが分かります。よって、`func`がコンストラクタかどうか判定するには`func.kind`が`SyntaxKind.Constructor`かどうかを見ればいいことが分かります。

これにより、コードは以下のようになりました。これで今度こそOKですね。

```ts
    if (func && func.kind !== SyntaxKind.Constructor) {
        const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async);
        addRelatedInfo(diagnostic, relatedInfo);
    }
```

実際のPRでは親切にもアサーションを追加したりしています。

## `for-await-of`文も忘れずに対処する

実は、`await`というのは`await expr`のような`await`式の他に、`for-await-of`文という構文でも登場します。こちらもやはり`async`関数の中でしか使えません。

ということで、こちらにも同様に変更を加えました。やっていることはほとんど同じなので実際のコードを見たい方はPRのdiffを参照してください。

似たことをしているので関数に纏めようかとも思いましたが、微妙にやっていることが違っていたのでそのままにしておきました。

## 既存のテストの修正

以上で実装は終わりましたが、これだけではPRを送ることはできません。**テスト**をちゃんと整備しないといけないのです。

TypeScriptコンパイラのテストは基本的に、テスト用のTypeScriptソースコードに対して実際にコンパイラを走らせ、発生するエラーメッセージや推論される型などが正しいかどうかチェックするというものです。各ソースコードに対してそれに対して期待される結果が書かれたファイルが存在しており（これをbaselineといいます）、テストでは結果がこのファイルの中身と一致しているかどうかが試されます。いわゆるスナップショットテストというやつですね。

`CONTRIBUTING.md`に書いてある通り、テストを走らせるには以下のコマンドを使います（全部のテストを走らせるとすごく時間がかかります）。

```sh
gulp runtests-parallel
```

実は、ここまでの変更を加えた段階ではテストが落ちるようになっています。エラーメッセージに変更を加えたので、そのエラーメッセージをチェックするテストが落ちてしまっているのです。

落ちているテストで何がどう変わったのかは`gulp diff`を実行すると分かります。

今回の場合、テストの側を書き換える必要がありますね。エラーメッセージが変わったのは今回の改善の正しい結果だからです。その場合はbaselineを新しい結果に合わせて書き換えます。以下のコマンドを実行すればOKです。

```sh
gulp baseline-accept
```

## テストを書く

また、今回の変更をより網羅的にテストするために、自分でも新しいテストを追加しなければいけません。

具体的には、`tests/cases/compiler/awaitInNonAsyncFunction.ts`というファイルを作成し以下のようなコードを書きました。

```ts
// @target: esnext
// https://github.com/Microsoft/TypeScript/issues/26586

function normalFunc(p: Promise<number>) {
  for await (const _ of []);
  return await p;
}

export function exportedFunc(p: Promise<number>) {
  for await (const _ of []);
  return await p;
}

const functionExpression = function(p: Promise<number>) {
  for await (const _ of []);
  await p;
}

const arrowFunc = (p: Promise<number>) => {
  for await (const _ of []);
  return await p;
};

function* generatorFunc(p: Promise<number>) {
  for await (const _ of []);
  yield await p;
}

class clazz {
  constructor(p: Promise<number>) {
    for await (const _ of []);
    await p;
  }
  method(p: Promise<number>) {
  for await (const _ of []);
    await p;
  }
}

for await (const _ of []);
await null;
```

今回変更したエラーが発生する`await`式や`for-await-of`文をひたすら書いたソースコードですね。いろいろな関数タイプを試していたり、関数の中ではない場合もちゃんと試していたりします。

このファイルだけ追加してテストを走らせると、`tests/baselines/reference/`以下に4つほどファイルが自動生成されます（中身はPRを参照）。これらが新しく追加されたテストに対するbaselineとなります。

ということで、生成されたbaselineファイルたちを目視で確認し、内容に問題が無ければ無事にテストが書けたということになります。

なお、テストを走らせるときは再び全テストを走らせるとものすごく時間がかかるので絞り込みを行う必要があります。ファイル名で絞り込めるのでこんな感じで適当に絞り込みましょう。

```sh
gulp runtests --tests=awaitInNonAsyncFunction
```

## まとめ

というわけで、以上で今回筆者が作成したPRの中身と、そこに至るまでの思考の流れを解説しました。TypeScriptに対する初めての貢献でしたが、レビューもたいへんスムーズに進み（余計な改行が入っていたのを指摘されましたがそれだけ）、PRを出してから数時間でマージされました。`good first issue`に対するPRがいくつも放置されている現状を見るに、コードに対して必要十分かつ的確な変更をすること（あとPRの説明文もいい感じに）が重要であると思われます。

繰り返しになりますが、エラーメッセージの変更は比較的やりやすい部類です。全く新しいエラー判定を追加するのが`good first issue`になることはあまり無いでしょうが、既存のエラーメッセージがわかりにくいので改善するというようなタスクは結構あります。

その際はただエラーメッセージの文面を変更するだけでなく、（今回外側の関数を探す処理を追加したよおうに）何らかの追加情報を集める処理を書かなければいけないことが多いでしょう。それがエラーメッセージ改善の肝であるといえます。そのような部分は場合に応じてアドリブで進めていく必要があります。この記事で事細かに述べた思考過程がその参考になれば幸いです。ソースコードを全部理解する必要はなく、必要な部分を調べれば大抵は大丈夫です。

とはいえ、ある程度コンパイラの全体像を把握していたほうがコードの理解・予測がしやすいのは間違いありません。例えば、記事中に出てきた`checkAwaitExpression`はベーシックな型検査アルゴリズム（抽象構文木に沿って再帰的に制約を作ったり型を生成したりする）のイメージが頭の中にあればその意味が自然に理解できます[^inference]。興味が出てきたらその方面を調べてみるのもよいかもしれません。

[^inference]: [TypeScriptの型推論詳説](https://qiita.com/uhyo/items/6acb7f4ee73287d5dac0)で解説したように、TypeScriptの型推論はこの方式にそってワンパスで行なわれるものです。関数型言語などでは、木に沿って制約を生成したあと制約を解消するというものになったりして少し複雑です。

とにかく、皆さんもぜひTypeScriptに貢献しましょう。たまに`good first issue`が発生するので、いけそうだなと思ったら他の人に取られる前にissueにやりますと書きましょう（筆者が取ったissueはしばらく放置されていたのでいきなりPRを出しましたが）。

やり方をTypeScriptチームの人に聞くとたまに教えてもらえますが、返事が返ってこないことも多いです。まあ、いちいち教えてあげなくても（筆者のように😤）勝手にソースを読み解いてPRを送ってくる人もいるわけですから、手取り足取り教えてあげる時間が勿体無いのかもしれません。この記事を参考に自力でTypeScriptコンパイラのコードが読めるようになるといいですね。

この記事を参考に皆さんもぜひやってみてください。

## 著作権表示

記事中に掲載したTypeScriptコンパイラのソースコードはApache 2.0 ライセンスのもと公開されており、著作権表示は以下の通りです。

```
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved. 
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0  
 
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
MERCHANTABLITY OR NON-INFRINGEMENT. 
 
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
```