使用TypeScript Compiler API获取变量类型时遇到问题
我之前在做内存中TypeScript类型分析的时候也踩过这个坑!你看到的{}...其实是TypeChecker没有完成完整类型解析的信号,大概率是自定义CompilerHost时漏掉了几个关键环节,尤其是内置库的加载和类型格式化的正确方法。
核心问题分析
默认的TypeScript Program依赖磁盘文件,包括内置的lib.d.ts系列文件——这些文件定义了number、Array等基础类型。当你完全在内存中操作时,如果自定义CompilerHost没有正确返回这些内置库内容,TypeChecker就无法识别基础类型,只能输出模糊的{}...结果。另外,直接使用类型对象的toString()方法也会导致格式化不完整。
分步解决方案
1. 确保自定义CompilerHost能返回内置库文件
TypeChecker需要lib.d.ts来解析基础类型,所以你的Host必须能响应对这些文件的读取请求。可以通过ts.getDefaultLibFilePath(options)获取内置库的路径,然后读取其内容(即使是内存模式,也需要先把lib内容加载进来,或者直接调用ts.sys.readFile读取磁盘上的默认lib)。
2. 配置正确的CompilerOptions
至少要指定target和lib选项,告诉TypeScript要加载哪些内置库。开启strict模式能让类型推断更准确:
const compilerOptions: ts.CompilerOptions = { target: ts.ScriptTarget.ESNext, lib: ["ESNext"], // 根据你的场景选择,比如需要DOM的话加"DOM" strict: true, };
3. 使用TypeChecker的typeToString方法格式化类型
不要直接调用type.toString(),这个方法对复杂类型和内置类型的格式化不完整。应该用checker.typeToString(type)来得到和VSCode提示一致的类型字符串。
4. 完整的内存CompilerHost实现示例
下面是一个能正确获取变量类型的完整代码示例:
import * as ts from "typescript"; // 内存中的目标代码 const sourceCode = ` const input: number[] = [1, 2, 3]; const el = input[0]; `; // 创建内存CompilerHost const createMemoryHost = (files: Record<string, string>, options: ts.CompilerOptions): ts.CompilerHost => { const defaultLibPath = ts.getDefaultLibFilePath(options); return { // 关键:返回内存文件或内置lib文件 getSourceFile: (fileName, languageVersion) => { if (files[fileName]) { return ts.createSourceFile(fileName, files[fileName], languageVersion); } // 加载内置lib const libContent = ts.sys.readFile(defaultLibPath); return libContent ? ts.createSourceFile(fileName, libContent, languageVersion) : undefined; }, getDefaultLibFilePath: () => defaultLibPath, writeFile: () => {}, // 内存操作不需要写入磁盘 getCurrentDirectory: () => process.cwd(), getDirectories: () => [], getCanonicalFileName: (fileName) => fileName, useCaseSensitiveFileNames: () => true, getNewLine: () => "\n", fileExists: (fileName) => !!files[fileName] || fileName === defaultLibPath, readFile: (fileName) => files[fileName] || ts.sys.readFile(fileName), }; }; // 初始化内存文件映射 const files = { "/test.ts": sourceCode, }; // 创建Program和TypeChecker const compilerOptions: ts.CompilerOptions = { target: ts.ScriptTarget.ESNext, lib: ["ESNext"], strict: true, }; const host = createMemoryHost(files, compilerOptions); const program = ts.createProgram(Object.keys(files), compilerOptions, host); const checker = program.getTypeChecker(); // 遍历节点获取变量类型 const sourceFile = program.getSourceFile("/test.ts")!; ts.forEachChild(sourceFile, (node) => { if (ts.isVariableStatement(node)) { node.declarationList.declarations.forEach((decl) => { const varName = decl.name.getText(sourceFile); const varType = checker.getTypeAtLocation(decl.name); // 使用typeToString得到正确的类型字符串 const typeStr = checker.typeToString(varType); console.log(`${varName}: ${typeStr}`); }); } });
运行这段代码,你会得到预期的输出:
input: number[] el: number
常见坑点总结
- 忘记加载内置
lib.d.ts:这是最常见的原因,导致基础类型无法被识别。 - 错误使用类型格式化方法:
toString()无法正确处理数组、泛型等复杂类型,必须用typeToString。 - CompilerHost方法实现不全:比如
fileExists或getSourceFile没处理内置库路径,导致Program无法找到依赖文件。
内容的提问来源于stack exchange,提问作者Aipie

