我们知道,babel 能够解析 typescript 语法,那么能不能基于 babel 实现类型检查呢?
我们经常用 tsc 来做类型检查,有没有想过,类型检查具体做了什么?
这一节,我们来学习一下类型、类型检查、怎么实现 ts 的类型检查。
# 什么是类型
类型代表了变量存储的内容,也就是规定了这块内容占据多大的内存空间,可以对它做什么操作。比如 number 和 boolean 就会分配不同字节数的内存,Date 和 String 可以调用的方法也不同。这就是类型的作用。它代表了一种可能性,你可以在这块内存放多少内容,可能对它进行什么操作。
类型分为动态类型和静态类型。
动态类型是指类型是在运行时才确定的,而静态类型是指编译期间就知道了变量的类型信息。有了类型信息自然就知道了对它而言什么操作是合法的,什么操作是不合法的,什么变量能够赋值给他。
静态类型会在代码中保留类型信息,这个类型信息可能是显式声明的,也可能是自动推导出来的。想做一个大的项目,没有静态类型来约束和提前检查代码的话,太容易出 bug 了,会很难维护。这也是随着前端项目逐渐变得复杂,出现了 typescript 以及 typescript 越来越火的原因。
typescript 就是给 javascript 增加了静态类型的语法和相应的语义。
# 如何检查类型
我们知道了什么是类型以及为什么要做静态的类型检查,那么怎么检查呢?
检查类型就是检查变量的内容,而理解代码的话需要把代码 parse 成 AST,所以类型检查也就变成了对 AST 结构的检查。
比如一个变量声明为了 number,那么给它赋值的是一个 string 就是有类型错误。
再复杂一点,如果类型有泛型,也就是有类型参数,那么需要传入具体的参数来确定类型,确定了类型之后再去和实际的 AST 对比。
typescript 还支持高级类型,也就是类型可以做各种运算,这种就需要传入类型参数求出具体的类型再去和 AST 对比。
我们来写代码实现一下:
# 代码实现
# 实现简单类型的类型检查
# 赋值语句的类型检查
比如这样一段代码,声明的值是一个 string,但是赋值为了 number,明显是有类型错误的,我们怎么检查出它的错误的。
let name: string;
name = 111;
首先我们使用 babel 把这段代码 parse 成 AST:
const parser = require('@babel/parser');
const sourceCode = `
let name: string;
name = 111;
`;
const ast = parser.parse(sourceCode, {
plugins: ['typescript']
});
使用 babel parser 来 parse,启用 typescript 语法插件。
可以使用 astexplerer.net 来查看它的 AST:

# 实现类型检查
我们需要检查的是这个赋值语句 AssignmentExpression,左右两边的类型是否匹配。
右边是一个数字字面量 NumericLiteral,很容易拿到类型,而左边则是一个引用,要从作用域中拿到它声明的类型,之后才能做类型对比。
babel 提供了 scope 的 api 可以用于查找作用域中的类型声明(binding),并且还可以通过 path.getTypeAnnotation 获得声明时的类型。
AssignmentExpression(path, state) {
const leftBinding = path.scope.getBinding(path.get('left'));
co