TypeScript のコンパイラ内部の調査

TypeScript のコンパイラは、多くの場合 tsc と呼ばれ、TypeScript エコシステムのコア コンポーネントの 1 つです。静的型付けルールを適用しながら、TypeScript コードを JavaScript に変換します。この記事では、TypeScript コンパイラの内部動作を詳しく調べて、TypeScript コードの処理と変換の仕組みを理解します。

1. TypeScript のコンパイルプロセス

TypeScript コンパイラは一連の手順を実行して TypeScript を JavaScript に変換します。プロセスの概要は次のとおりです。

  1. ソース ファイルを抽象構文ツリー (AST) に解析します。
  2. AST のバインディングと型チェック。
  3. 出力 JavaScript コードと宣言を出力します。

これらの手順をさらに詳しく見てみましょう。

2. TypeScript コードの解析

コンパイル プロセスの最初のステップは、TypeScript コードの解析です。コンパイラはソース ファイルを受け取り、それを AST に解析し、字句解析を実行します。

TypeScript の内部 API を使用して AST にアクセスし、操作する方法の簡略化された図を以下に示します。

import * as ts from 'typescript';

const sourceCode = 'let x: number = 10;';
const sourceFile = ts.createSourceFile('example.ts', sourceCode, ts.ScriptTarget.Latest);

console.log(sourceFile);

createSourceFile 関数は、生の TypeScript コードを AST に変換するために使用されます。 sourceFile オブジェクトには、解析されたコードの構造が含まれます。

3. バインディングと型チェック

解析後、次のステップは AST 内のシンボルをバインドし、型チェックを実行することです。このフェーズでは、すべての識別子がそれぞれの宣言にリンクされていることを確認し、コードが TypeScript の型ルールに従っているかどうかを確認します。

型チェックは TypeChecker クラスを使用して実行されます。プログラムを作成し、型情報を取得する方法の例を次に示します。

const program = ts.createProgram(['example.ts'], {});
const checker = program.getTypeChecker();

// Get type information for a specific node in the AST
sourceFile.forEachChild(node => {
    if (ts.isVariableStatement(node)) {
        const type = checker.getTypeAtLocation(node.declarationList.declarations[0]);
        console.log(checker.typeToString(type));
    }
});

この例では、TypeChecker は変数宣言の型をチェックし、AST から型情報を取得します。

4. コード発行

型チェックが完了すると、コンパイラは出力フェーズに進みます。ここで、TypeScript コードが JavaScript に変換されます。構成に応じて、出力には宣言ファイルとソース マップも含まれる場合があります。

コンパイラを使用して JavaScript コードを生成する方法の簡単な例を次に示します。

const { emitSkipped, diagnostics } = program.emit();

if (emitSkipped) {
    console.error('Emission failed:');
    diagnostics.forEach(diagnostic => {
        const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
        console.error(message);
    });
} else {
    console.log('Emission successful.');
}

program.emit 関数は JavaScript 出力を生成します。出力中にエラーが発生した場合は、エラーがキャプチャされて表示されます。

5. 診断メッセージ

TypeScript コンパイラーの主な役割の 1 つは、開発者に意味のある診断メッセージを提供することです。これらのメッセージは、型チェックとコード出力の両方のフェーズで生成されます。診断には警告やエラーが含まれる場合があり、開発者が問題を迅速に特定して解決するのに役立ちます。

コンパイラから診断を取得して表示する方法は次のとおりです。

const diagnostics = ts.getPreEmitDiagnostics(program);

diagnostics.forEach(diagnostic => {
    const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
    console.log(`Error ${diagnostic.code}: ${message}`);
});

この例では、診断がプログラムから抽出され、コンソールに出力されます。

6. コンパイラ API を使用した TypeScript の変換

TypeScript コンパイラ API を使用すると、開発者はカスタム変換を作成できます。コードを発行する前に AST を変更できるため、強力なカスタマイズとコード生成ツールが有効になります。

すべての変数の名前を newVar に変更する単純な変換の例を次に示します。

const transformer = (context: ts.TransformationContext) => {
    return (rootNode: T) => {
        function visit(node: ts.Node): ts.Node {
            if (ts.isVariableDeclaration(node)) {
                return ts.factory.updateVariableDeclaration(
                    node,
                    ts.factory.createIdentifier('newVar'),
                    node.type,
                    node.initializer
                );
            }
            return ts.visitEachChild(node, visit, context);
        }
        return ts.visitNode(rootNode, visit);
    };
};

const result = ts.transform(sourceFile, [transformer]);
console.log(result.transformed[0]);

この変換は AST 内の各ノードにアクセスし、必要に応じて変数の名前を変更します。

結論

TypeScript のコンパイラ内部を調べると、TypeScript コードがどのように処理され、変換されるかについてより深く理解できます。カスタム ツールを構築しようとしている場合でも、TypeScript の動作に関する知識を深めようとしている場合でも、コンパイラ内部を詳しく調べることは啓発的な体験になる可能性があります。