🥋

TypeScript の型エイリアスが指す型を文字列で取得する

2023/02/23に公開

はじめに

必要になるケースは限られると思うが、 TypeScript で記述したコードに含まれる、ある型エイリアスが具体的にどのような型であるのかを文字列として取得したいことがある。

例えば、以下のような型エイリアス Piyo を具体的なリテラルのユニオン型 (1 | 2 | 3 | 4 | 5) として取得したいということもあるし、

type Hoge = 1 | 2 | 3
type Bar = 3 | 4 | 5
type Piyo = Hoge | Bar

以下のように実装などから与えられた型 ("a" | "b" | "c" | "d") を取得したいということがある。

import * as z from "zod";
const data = z.union([
    z.literal("a"),
    z.literal("b"),
    z.literal("c"),
    z.literal("d"),
])

type Data = z.infer<typeof data>

動作確認をしたリポジトリは以下

https://github.com/sterashima78/ts-type-test

ソース・実行例

ソースは以下

forEachChild が AST をトラバースするAPIで、ノードが型エイリアスの時のみ、そのエイリアスが指す型結果を出力させている。typeToString というまさにな感じの名前の API があるが、それの第三引数によってどのような形で型の文字列を受け取るかを指定できる。今回のケースでは InTypeAlias が適当そうだった。

index.ts
import ts from "typescript";

function printInferredType(filePath: string) {
  const program = ts.createProgram([filePath], {});
  const sourceFile = program.getSourceFile(filePath)!;
  const checker = program.getTypeChecker();

  function visit(node: ts.Node) {
    if (ts.isTypeAliasDeclaration(node)) {
      const inferredType = checker.typeToString(
        checker.getTypeAtLocation(node), 
        node, 
        ts.TypeFormatFlags.InTypeAlias
      );
      console.log(`Inferred type for ${node.name.text}: ${inferredType}`);
    }
    ts.forEachChild(node, visit);
  }
  visit(sourceFile);
}

printInferredType(process.argv[2]);

以下のソースに対して

example/test.ts
type Hoge = 1 | 2 | 3
type Bar = 3 | 4 | 5

export type Piyo = Hoge | Bar
type Foo = Hoge & Bar

以下のように実行結果が得られる。

$ node dist/index.js example/test.ts 
Inferred type for Hoge: 1 | 2 | 3
Inferred type for Bar: 3 | 4 | 5
Inferred type for Piyo: 1 | 2 | 3 | 4 | 5
Inferred type for Foo: 3

外部ファイルに依存していても大丈夫

example/external.ts
import { Piyo } from "./test.js";

type A = Piyo & (1 | 3 | 10)

以下のような結果になる。

$ node dist/index.js example/external.ts
Inferred type for A: 1 | 3

実装をもとにしたものの結果もでる。

example/zod.ts
import * as z from "zod";
const data = z.union([
    z.literal("a"),
    z.literal("b"),
    z.literal("c"),
    z.literal("d"),
])

type Data = z.infer<typeof data>
type Hoge = Array<Promise<Data>>
$ node dist/index.js example/zod.ts 
Inferred type for Data: "a" | "b" | "c" | "d"
Inferred type for Hoge: Promise<"a" | "b" | "c" | "d">[]

おわりに

もともとの目的を満たすにあたって TypeScript Compiler API を使えば良さそうであることは想像できたが、 TypeScript Compiler API の情報はあまり充実しているとは言えず、どこから手を付けようかと思っていた。

そこで、ChatGPT に質問をしてサンプルコードを作ってもらうことが手がかりになった。
作ってもらったコード自体はそのままでは動作するものではなかったが、具体的な API がコード中に提示されたことで、それを手がかりに Issue などを探すことができた。

というわけで、TypeScript の記事と見せかけて、ChatGPT などの大規模言語モデルを使ったサービスをうまく活用できるといいねという話だった。

Discussion