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
相关产品推荐
相关产品推荐

