You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript能否生成脚本类型依赖树?如何通过编译器API获取?

TypeScript类型依赖树生成方案

可以通过TypeScript Compiler API提取类型之间的依赖关系,实现类型依赖树的生成,以下是具体实现思路和示例:

核心原理

TypeScript编译器在类型检查过程中会构建**类型符号(Symbol)与类型(Type)**的关联结构,通过Compiler API提供的TypeChecker等工具,可以遍历这些结构,递归提取自定义类型之间的依赖关系。

实现步骤与示例代码

1. 环境准备

首先安装TypeScript依赖:

npm install typescript

2. 提取类型依赖的代码实现

import * as ts from "typescript";

// 目标类型代码
const targetCode = `
type Point = {
  x: number;
  y: number;
}

type Line = {
  start: Point;
  end: Point;
}

type Shape = {
  lines: Line[];
}
`;

// 初始化编译器配置
const compilerOpts: ts.CompilerOptions = {
  target: ts.ScriptTarget.ESNext,
  module: ts.ModuleKind.CommonJS,
};

// 创建虚拟源文件
const sourceFile = ts.createSourceFile(
  "types.ts",
  targetCode,
  ts.ScriptTarget.ESNext,
  true
);

// 创建程序实例
const program = ts.createProgram(["types.ts"], compilerOpts, {
  getSourceFile: (fileName) => fileName === "types.ts" ? sourceFile : undefined,
  writeFile: () => {},
  getDefaultLibFileName: () => "lib.d.ts",
  useCaseSensitiveFileNames: () => false,
  getCanonicalFileName: (fileName) => fileName,
  getCurrentDirectory: () => "",
  getNewLine: () => "\n",
});

const typeChecker = program.getTypeChecker();
// 存储依赖关系:键为类型名,值为依赖的类型名数组
const typeDeps: Record<string, string[]> = {};

// 递归提取类型依赖
function collectDeps(typeName: string, type: ts.Type) {
  typeDeps[typeName] = [];

  // 处理对象类型(TypeLiteral)
  if (type.getSymbol()?.flags & ts.SymbolFlags.TypeLiteral) {
    for (const prop of type.getProperties()) {
      const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration!);
      const propTypeName = getTypeName(propType);
      if (propTypeName && !typeDeps[typeName].includes(propTypeName)) {
        typeDeps[typeName].push(propTypeName);
        // 递归处理子类型
        if (!(propTypeName in typeDeps)) {
          collectDeps(propTypeName, propType);
        }
      }
    }
  }
  // 处理数组类型
  else if (type.isArray()) {
    const elemType = type.getArrayElementType();
    if (elemType) {
      const elemTypeName = getTypeName(elemType);
      if (elemTypeName && !typeDeps[typeName].includes(elemTypeName)) {
        typeDeps[typeName].push(elemTypeName);
        if (!(elemTypeName in typeDeps)) {
          collectDeps(elemTypeName, elemType);
        }
      }
    }
  }
}

// 获取类型的可读名称
function getTypeName(type: ts.Type): string | undefined {
  const symbol = type.getSymbol();
  if (!symbol) return undefined;

  // 自定义类型别名
  if (symbol.flags & ts.SymbolFlags.TypeAlias) {
    return symbol.name;
  }
  // 原始类型(number、string等)
  if (symbol.flags & ts.SymbolFlags.Primitive) {
    return symbol.name;
  }
  return undefined;
}

// 遍历所有自定义类型别名,收集依赖
const typeAliases = sourceFile.statements.filter(
  (stmt): stmt is ts.TypeAliasDeclaration => stmt.kind === ts.SyntaxKind.TypeAliasDeclaration
);

for (const alias of typeAliases) {
  const typeSymbol = typeChecker.getSymbolAtLocation(alias.name)!;
  const type = typeChecker.getTypeOfSymbolAtLocation(typeSymbol, alias)!;
  collectDeps(alias.name.text, type);
}

// 输出类型依赖树
console.log("类型依赖关系:");
for (const [type, deps] of Object.entries(typeDeps)) {
  console.log(`${type} → ${deps.join(", ")}`);
}

3. 运行结果

执行上述代码后,会输出:

类型依赖关系:
Point → number, number
Line → Point, Point
Shape → Line

扩展说明

  • 示例仅覆盖了对象类型、数组类型和基础原始类型,实际场景中如果需要处理联合类型、交叉类型、泛型等复杂类型,需要扩展collectDeps和getTypeName函数的逻辑。
  • 可以基于收集到的依赖关系,使用图表库生成可视化的依赖树。

内容的提问来源于stack exchange,提问作者visum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 14:25:43