第十章 类型检查器

105 阅读3分钟

第十章 类型检查器

使用TypeScript类型检查器获取更深入的元数据

TypeScript编译器API还有一个强大功能:类型检查器。它扩展了编译器API的能力,可以深入检查AST节点,获取节点额外的元数据。

你是否好奇,代码编辑器或IDE为什么能在鼠标悬停时显示提示信息?比如变量的类型,或是函数的签名,即使这些信息并没有显式写出来。这就是类型检查器的功劳!

本章我们将通过示例脚本,演示如何识别未显式声明返回类型的函数。这一切都基于类型检查器的类型推断能力。

我们将解析下面这段代码的AST:

export function hello(text?: string) {
  console.log(`Hello, ${text ?? "World!"}`)
  return 6
}

通过类型检查器,我们能识别出该函数的签名为:(text?: string) => number

返回类型

打开我们一直使用的测试文件fun.ts,粘贴以下测试代码:

export function hello(text?: string) {
  console.log(`Hello, ${text ?? "World!"}`)
  return 6
}

注意这个函数没有声明返回类型。虽然从代码中能明显看出返回的是number类型(因为返回了值6),但没有显式声明时,必须阅读函数体才能知道返回类型。

我们的目标是编写脚本,识别文件中所有函数的返回类型。

打开index.mjs文件,替换为以下基础代码:

import ts from "typescript";

const program = ts.createProgram(["fun.ts"], {
  module: ts.ModuleKind.ESNext,
});

for (const rootFileName of program.getRootFileNames()) {
  const sourceFile = program.getSourceFile(rootFileName);

  if (sourceFile && !sourceFile.isDeclarationFile) {
    typeCheckSourceFile(sourceFile);
  }
}

这和前一章的示例类似。我们只需要定义一个typeCheckSourceFile函数来处理类型检查。

// 为程序初始化类型检查器
const checker = program.getTypeChecker()

/**
 * @param {ts.SourceFile} sourceFile 
 */
function typeCheckSourceFile(sourceFile) {  
  sourceFile.forEachChild((node) => {
    // 在这里添加类型检查逻辑
  })
}

在编写逻辑前,我们先通过program.getTypeChecker()初始化类型检查器,并将其赋值给变量备用。

基础逻辑是:遍历文件中的每个根节点,检查是否是函数声明。如果是,就获取其类型签名,再提取返回类型并打印到控制台。

注意:和前面几章一样,本例仅处理根节点。要覆盖文件中所有节点,需要递归遍历AST,这超出了本章范围。

简单的函数检查可以用ts.isFunctionDeclaration(node)实现,它会将类型收窄为FunctionDeclaration。确认是函数后,就可以安全地获取其类型签名了。签名是描述函数参数类型和返回类型的复合类型。获取函数签名的代码如下:const signature = checker.getSignatureFromDeclaration(node);getSignatureFromDeclaration会返回ts.Signature类型,如果无法确定类型则返回undefined

获取签名后,可以用const returnType = signature.getReturnType()提取返回类型。注意要先检查签名是否为undefined,否则可能抛出异常。拿到返回类型(ts.Type类型)后,我们可以用const returnTypeStr = checker.typeToString(returnType);将其转为字符串输出。

完整代码如下:

const checker = program.getTypeChecker()

/**
 * @param {ts.SourceFile} sourceFile 
 */
function typeCheckSourceFile(sourceFile) {  
  sourceFile.forEachChild((node) => {
    if (ts.isFunctionDeclaration(node)) {
      const signature = checker.getSignatureFromDeclaration(node)
      const returnType = signature.getReturnType()
      const returnTypeStr = checker.typeToString(returnType)
      console.log(`函数'${node.name.escapedText}'的返回类型是: ${returnTypeStr}!`)
    }
  })
}

运行脚本后,会输出:

函数'hello'的返回类型是: number!

太棒了!我们成功识别了未显式声明返回类型的函数。

类型与符号

类型检查器提供了数十个工具函数,用于检查和获取节点的符号与类型。通常基础AST节点就能提供大部分数据,但如果需要更深入的元数据,类型检查器很可能能满足需求。

要查看所有可用工具,请访问GitHub上的TypeScript仓库。类型检查器的所有方法接口定义在这里

总结

本章我们学习了TypeScript类型检查器,以及如何在脚本中使用它。类型检查器能提供强大工具,帮助我们识别被检查代码中的推断类型。