本文转载请联系神光编程秘籍公众号。前段时间写了一篇类型检查实现原理的文章,实现了简单赋值语句和函数调用的类型检查。事实上,检查的类型非常多,一篇文章是写不完的,所以我打算用系列文章来讲述各种类型检查的实现原理,帮助大家更好地掌握typescript。本篇我们将实现4.3新增类的override关键字的类型检查。(源码链接在后面)override修饰符有什么作用?首先我们看一下这个修饰符的作用:override标记的方法必须存在于父类中,否则会报错。classAnimal{getName(){return'';}}classDogeextendsAnimal{overridebak(){return'wang';}overridegetName(){return'wang';}}上面的代码会报错:Thismembercannothavean'override'修饰符,因为它没有在基类'Animal'中声明。也就是说重写的在父类中是不存在的,避免在重构父类的时候去掉一些需要重写的子类方法。如何实现override修饰符的类型检查其实所有的修饰符,包括override、public、static等,在解析成AST之后都是作为属性存在的,而这个override也是,我们通过astexplorer.net来查看一下。可以看到override属性为真。这样我们就可以通过这个属性来过滤掉这个类中所有需要重写的ClassMethods。那么也可以得到superClass的名称,从作用域中找到对应的声明,然后遍历AST,找到它声明的所有ClassMethods。两者比较,所有不在父类中的ClassMethods都需要报错。代码实现我们基于babel做parser和分析,写一个插件做override类型检查。babel插件的基础知识可以在小册子《babel 插件通关秘籍》中找到。启用syntaxtypescript插件来解析ts语法。const{transformFromAstSync}=require('@babel/core');constparser=require('@babel/parser');constast=parser.parse(sourceCode,{sourceType:'unambiguous',plugins:['typescript']});const{code}=transformFromAstSync(ast,sourceCode,{plugins:[overrideCheckerPlugin]});插件需要处理的是ClassDeclaration,我们先搭建一个基本结构:const{declare}=require('@babel/helper-plugin-utils');constoverrideCheckerPlugin=declare((api,options,dirname)=>{api.assertVersion(7);return{pre(file){file.set('errors',[]);},visitor:{ClassDeclaration(path,state){constsemanticErrors=state.file.get('errors');//...state.file.set('errors',semanticErrors);}},post(file){console.log(file.get('errors'));}}});具体的检查逻辑是获取父类的所有方法名,获取当前类的所有override方法名,然后进行过滤。我们首先需要获取父类的ast并通过名称从作用域中查找它。constsuperClass=path.node.superClass;if(superClass){constsuperClassPath=path.scope.getBinding(superClass.name).path;}然后封装一个方法获取父类方法名,通过path.traverse遍历ast,收集接收到的方法名称存储在状态中。函数getAllClassMethodNames(classDeclarationNodePath){conststate={allSuperMethodNames:[]}classDeclarationNodePath.traverse({ClassMethod(path){state.allSuperMethodNames.push(path.get('key').toString())}});returnstate.allSuperMethodNames;这样就得到了所有的父类方法名。之后需要获取当前类的所有方法名,过滤掉override为true且不在父类中报错的方法名。constsuperClass=path.node.superClass;if(superClass){constsuperClassPath=path.scope.getBinding(superClass.name).path;constallMethodNames=getAllClassMethodNames(superClassPath);path.traverse({ClassMethod(path){if(path.node.override){constmethodName=path.get('key').toString();constsuperClassName=superClassPath.get('id').toString();if(!allMethodNames.includes(methodName)){//报错}}}});}错误报告部分使用代码框架创建友好的代码打印格式,并通过将Error.stackTraceLimit设置为0来去除调用堆栈信息。consttmp=Error.stackTraceLimit;Error.stackTraceLimit=0;leterrorMessage=`thismembercannothavean'override'修饰符因为它未在基类中声明'${superClassName}'`;semanticErrors.push(path.get('key').buildCodeFrameError(errorMessage,Error));Error...]}classDeclarationNodePath.traverse({ClassMethod(path){state.allSuperMethodNames.push(path.get('key').toString())}});returnstate.allSuperMethodNames;}constoverrideCheckerPlugin=declare((api,options,dirname)=>{api.assertVersion(7);return{pre(file){file.set('errors',[]);},visitor:{ClassDeclaration(path,state){constsemanticErrors=state.file.get('错误');constsuperClass=path.node.superClass;if(superClass){constsuperClassPath=path.scope.getBinding(superClass.name).path;constallMethodNames=getAllClassMethodNames(superClassPath);path.traverse({ClassMethod(path){if(path.node.override){constmethodName=path.get('key').toString();constsuperClassName=superClassPath.get('id').toString();if(!allMethodNames.includes(methodName)){consttmp=Error.stackTraceLimit;Error.stackTraceLimit=0;leterrorMessage=`thismembercannothavean'override'modifierbecauseitisnotdeclaredinthebaseclass'${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链接测试效果我们用初始代码测试一下:classAnimal{getName(){return'';}}classDogeextendsAnimal{overridebak(){return'wang';}overridegetName(){return'wang';}}打印信息为:正确识别父类不存在bak的错误至此,我们实现了对的类型检查覆盖!总结类型检查的案例很多,所以需要出一个系列的文章来讲一下。在本文中,我们将实现override的类型检查。Override是ts4.3中添加的功能。带有override修饰符的方法必须在父类中有相应的声明,否则会报错。我们通过babel插件实现了类型检查。思路是从作用域中取出父类的声明,然后通过path.traverse获取所有的方法名,进而获取当前类的所有方法名。对于那些没有在父类中声明并且带有override修饰符的方法会报错。本文是【TypeScript类型检查原理】系列文章的第二篇,后续会有更多文章揭示TypeScript类型检查的实现原理。希望能帮助大家更好的掌握打字稿。关于babel插件的知识可以看我的babel小册子《babel 插件通关秘籍》,里面有详细的讲解。本文来源链接https://github.com/QuarkGluonPlasma/babel-plugin-exercize/tree/master/exercize-type-checker/src
