本文转载自微信公众号「神光的类型编程秘籍」,作者神说要有光 。检查转载本文请联系神光的原理编程秘籍公众号。
前段时间写过一篇类型检查的实现实现原理的文章,实现了简单的类型赋值语句和函数调用的类型检查。实际上类型检查的检查情况特别多,一篇文章肯定写不完,原理所以我准备用系列文章来讲述各种类型检查的实现实现原理,帮助大家更好的类型掌握 typescript。
这一篇我们来实现 4.3 新增的检查 class 的 override 关键字的类型检查。(源码链接在后面)
首先,我们来看下这个修饰符的实现作用:被 override 标示的方法必须得在父类中存在,否则会报错。类型
class Animal { getName() { return ; } } class Dog extends Animal { override bak() { return wang; } override getName() { return wang; } }上面这段代码会报错:This member cannot have 检查an override modifier because it is not declared in the base class Animal.就是说重写的放在父类不存在,这样能避免父类重构的原理时候把一些子类需要重写的服务器租用方法给去掉。
其实所有的修饰符,包括 override、public、static 等,在 parse 成 AST 后都是作为一个属性存在的,这个 override 也是,我们通过 astexplorer.net 来查看一下。
可以看到 override 属性为 true。这样我们就可以通过这个属性把该 class 的所有的需要 override 的 ClassMethod 过滤出来。
然后还可以拿到 superClass 的名字,从作用域中找到对应的声明,然后遍历 AST 找到它所声明的所有 ClassMethod。
两者对比一下,所有不在父类中的 ClassMethod 都需要报错。
我们基于 babel 来做 parser 和分析,写一个插件来做 override 的类型检查。关于 babel 插件的基础可以看小册《babel 插件通关秘籍》。
开启语法 typescript 插件来解析 ts 语法。服务器托管
const { transformFromAstSync } = require(@babel/core); const parser = require(@babel/parser); const ast = parser.parse(sourceCode, { sourceType: unambiguous, plugins: [typescript] }); const { code } = transformFromAstSync(ast, sourceCode, { plugins: [overrideCheckerPlugin] });插件要处理的是 ClassDeclaration,我们先搭一个基本的结构:
const { declare } = require(@babel/helper-plugin-utils); const overrideCheckerPlugin = declare((api, options, dirname) => { api.assertVersion(7); return { pre(file) { file.set(errors, []); }, visitor: { ClassDeclaration(path, state) { const semanticErrors = state.file.get(errors); //... state.file.set(errors, semanticErrors); } }, post(file) { console.log(file.get(errors)); } } });具体的检查逻辑是拿到父类的所有方法名,拿到当前类的所有 override 方法名,然后做下过滤。
我们首先要拿到父类的 ast,通过名字从作用域中查找。
const superClass = path.node.superClass; if (superClass) { const superClassPath = path.scope.getBinding(superClass.name).path; }然后封装一个方法来拿父类方法名,通过 path.traverse 来遍历 ast,把收集到的方法名存到 state 中。
function getAllClassMethodNames(classDeclarationNodePath) { const state = { allSuperMethodNames: [] } classDeclarationNodePath.traverse({ ClassMethod(path) { state.allSuperMethodNames.push(path.get(key).toString()) } }); return state.allSuperMethodNames; }这样就拿到了所有父类方法名。
之后需要拿到当前类的所有方法名并过滤出 override 为 true 且不在父类中的进行报错。
const superClass = path.node.superClass; if (superClass) { const superClassPath = path.scope.getBinding(superClass.name).path; const allMethodNames = getAllClassMethodNames(superClassPath); path.traverse({ ClassMethod(path) { if (path.node.override){ const methodName = path.get(key).toString(); const superClassName = superClassPath.get(id).toString(); if (!allMethodNames.includes(methodName)) { // 报错 } } } }); }报错的部分使用 code frame 来创建友好的代码打印格式,通过 Error.stackTraceLimit 设置为 0 去掉调用栈信息。
const tmp = Error.stackTraceLimit; Error.stackTraceLimit = 0; let errorMessage = `this member cannot have an override modifier because it is not declared in the base class ${ superClassName}`; semanticErrors.push(path.get(key).buildCodeFrameError(errorMessage, Error)); Error.stackTraceLimit = tmp;这样,我们就完成了 override 的类型检查,整体代码如下:
const { declare } = require(@babel/helper-plugin-utils); function getAllClassMethodNames(classDeclarationNodePath) { const state = { allSuperMethodNames: [] } classDeclarationNodePath.traverse({ ClassMethod(path) { state.allSuperMethodNames.push(path.get(key).toString()) } }); return state.allSuperMethodNames; } const overrideCheckerPlugin = declare((api, options, dirname) => { api.assertVersion(7); return { pre(file) { file.set(errors, []); }, visitor: { ClassDeclaration(path, state) { const semanticErrors = state.file.get(errors); const superClass = path.node.superClass; if (superClass) { const superClassPath = path.scope.getBinding(superClass.name).path; const allMethodNames = getAllClassMethodNames(superClassPath); path.traverse({ ClassMethod(path) { if (path.node.override){ const methodName = path.get(key).toString(); const superClassName = superClassPath.get(id).toString(); if (!allMethodNames.includes(methodName)) { const tmp = Error.stackTraceLimit; Error.stackTraceLimit = 0; let errorMessage = `this member cannot have an override modifier because it is not declared in the base class ${ superClassName}`; semanticErrors.push(path.get(key).buildCodeFrameError(errorMessage, Error)); Error.stackTraceLimit = tmp; } } } }); } state.file.set(errors, semanticErrors); } }, post(file) { console.log(file.get(errors)); } } }); module.exports = overrideCheckerPlugin;github 链接
我们用最开始的代码来测试一下:
class Animal { getName() { return ; } } class Dog extends Animal { override bak() { return wang; } override getName() { return wang; } }打印信息为:
正确的识别出了 bak 在父类不存在的错误。亿华云计算
至此,我们实现了 override 的类型检查!
类型检查情况很多,所以需要一个系列文章去讲,这一篇我们来实现 override 的类型检查。
override 是 ts 4.3 加入的特性,带有 override 修饰符的方法必须在父类中有对应的声明,否则会报错。
我们通过 babel 插件的方式实现了类型检查,思路是从作用域取出父类的声明,然后通过 path.traverse 拿到所有方法名,之后再取当前类的所有方法名,对于没在父类中声明并且带有 override 修饰符的方法进行报错。
本文是 【typescript 类型检查原理】系列文章的第二篇,后续还会有更多 typescript 类型检查的实现原理揭秘的文章。希望能够帮助大家更好的掌握 typescript。
关于 babel 插件的知识,可以看我的 babel 小册《babel 插件通关秘籍》,其中有详细的讲解。
本文源码链接 https://github.com/QuarkGluonPlasma/babel-plugin-exercize/tree/master/exercize-type-checker/src