TypeScript のコンパイラーの処理の流れ

## はじめに

最近、typescript のコンパイラーのコードをサラサラーっと読んでいたのですが、なんとなく区切りがついたので、簡単にまとめておこうと思います。いつも通りではありますが、この記事で間違ったことを書く可能性は大いにあるため、正確な情報が必要であれば、参考に記載している参照元 URL を確認してください。

## 参考

typescript のコンパイルに関するソースコードがあるディレクトリです。
https://github.com/microsoft/TypeScript/tree/main/src/compiler

typescript の型チェックに関するソースコードです。超巨大なので typescript のリポジトリをクローンしてから、ローカルのエディターで開きましょう（汗）。
https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts

typescript のメンテナーがコンパイラーに関してまとめてくれています。
https://github.com/microsoft/TypeScript-Compiler-Notes

typescript のメンテナーがミニマムの Typescript を実装しています。
https://github.com/sandersn/mini-typescript


## コンパイラーの流れ

ものすごくざっくりとコンパイラーの流れを分割すると、以下の3つになります。

- テキスト解析（parsing text）
- 型チェック（Type Checking）
- コード出力（Emit）

各工程について記載していきます。


## テキスト解析（parsing text）

テキスト解析の最終的な目的はシンタックスツリーを作成することです（シンタックスツリーについては後述）。
シンタックスツリーを作成するために、まずは、コンパイル対象のコードをテキストとして読み取り、キーワード毎に分割していき、分割されたキーワードに対して名称を与えます。
例えば、以下のコードをキーワード毎に分割し、名称を与えると...

```javascript
function hello() {
  console.log("Hi");
}
```


以下になります。

```javascript
// 分割対象`名称`
function`FunctionKeyword`
 `WhitespaceTrivia`
hello`Identifier`
(`OpenParenToken`
)`CloseParenToken`
 `WhitespaceTrivia`
{`OpenBraceToken`
`NewLineTrivia`
 `WhitespaceTrivia`
console`Identifier`
.`DotToken`
log`Identifier`
(`OpenParenToken`
"Hi"`StringLiteral`
)`CloseParenToken`
;`SemicolonToken`
`NewLineTrivia`
}`CloseBraceToken`
`NewLineTrivia`
`EndOfFileToken`
```

上記の例を見ていくと、関数の宣言は `FunctionKeyword` と言う名称が与えられ、関数名の hello は `Identifier` になっています。また、console も `Identifier` です。javascript の構文にない hello や console は固有の名称ということで　`Identifier`　になります。

これらの処理は、typescript の playground に Ts Scanner というプラグインを入れることでも確認できます。
https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABACwKYBt1wBQEpEDeAUIohAgM5zqoB0WA5tgEQASMzuA3EQL5A

![スクリーンショット 2022-11-13 18.17.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606419/0bf58a38-4b0d-9fab-65c3-500e51b4ca01.png)


次は、これらに階層の情報を付与され、シンタックスツリーが作成されます（ここで作成されるシンタックスツリーが超重要で、この後の処理でもずっと使われます）。
シンタックスツリーの全量を書くのはしんどいので、省略しますが、ざっくり以下になります。

```
SourceFile:
  - statements: [
    Function Declaration
      - name: Identifier
      - body: Block
        statements: [
          ExpressionStatement
            expression: CallExpress
              ...
        ]
  ]
```

ソースファイルがルートにあり、その一階層下に関数宣言(`Function Declaration`)があり、関数宣言の一階層下に、関数名を表す name(`Identifier`)があります。こんな感じでシンタックスツリーが作成されています。

※ 本当は、関数宣言(`Function Declaration`)に name 以外にも色々な属性が付与されていますが、上記のざっくりしたシンタックスツリーでは、属性は省略して記載しています。属性とは、例えば、関数宣言の開始位置、終了位置があり、これらは vscode 等のエディターでエラー箇所をハイライトするために使われたり...他にも色々と属性はありますが、これらは後続の処理で使用されます。

これらの処理は、Typescript の playground の設定から AST Viewer を有効することでも確認できます。

![スクリーンショット 2022-11-13 18.40.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606419/2986cc90-8f70-9532-1716-4050bfb41664.png)



## 型チェック（Type Checking）

型チェックは、宣言された "もの" に対して実行されていきます。"もの" は、変数や関数、インターフェイスなどです。
"もの" はテキスト解析でシンタックスツリーとして表現されており、シンタックスツリーの頭からつま先まで、型チェックは行われます。

```
<!-- これらを頭からつま先まで型チェック -->
SourceFile:
  - statements: [
    Function Declaration
      - name: Identifier
      - body: Block
        statements: [
          ExpressionStatement
            expression: CallExpress
              ...
        ]
  ]
```

例えば、以下のコードがあります。

```javascript
function hello() {
  console.log("Hi");
}
```

上記に宣言された関数は、シンタックスツリーでは、hello(`Identifier`)と言う名前の関数(`Function Declaration`)で表現されており、型チェックでは、hello(`Identifier`)と言う名前の関数(`Function Declaration`)が重複して存在しないかがチェックされます。もし、存在すれば `Duplicate function implementation.` と言うエラーが得られます。


次に、以下のコードがあります。

```typescript
const num: number = 'A'
// error message : Type 'string' is not assignable to type 'number'.
```

このコードは、`Type 'string' is not assignable to type 'number'.` というエラーが得られます。
このコードのシンタックスツリーを見ると以下のようになっています（関連のある属性のみ表示）。

```
VariableDeclaration:
    - initializer: StringLiteral
    - type: NumberKeyword
```

`VariableDeclaration` は `num` という名前の変数を宣言していることを意味しており、この変数の初期値(`initializer`)は `A` という `StringLiteral` になっていますが、型(`type`)は `NumberKeyword` となっています。
型チェックにて、初期値(`initializer`)と型(`type`)が整合していないため、先の `Type 'string' is not assignable to type 'number'.`というエラーメッセージを作成します。


## コード出力（Emit）

`.js`, `.d.ts`, `.map` ファイルが出力されます。コード出力では、テキスト解析で得られたシンタックスツリーを元に、`tsconfig` に合わせて新しいシンタックスツリーを作成します。例えば、`tsconfig` のターゲットが `es2015` であれば、`ESNext`, `ES2020`, `ES2019`, `ES2018`, `ES2017`, `ES2016`, `ES2015` のシンタックスは変換される必要があります。


## まとめ

かなり粒度は荒くしていますが、簡単にコンパイラーの処理の流れのメモとして置いておきます。以上終わり。