Typescript 類(lèi)型檢查原理之Override 是如何實(shí)現(xiàn)的
本文轉(zhuǎn)載自微信公眾號(hào)「神光的編程秘籍」,作者神說(shuō)要有光 。轉(zhuǎn)載本文請(qǐng)聯(lián)系神光的編程秘籍公眾號(hào)。
前段時(shí)間寫(xiě)過(guò)一篇類(lèi)型檢查的實(shí)現(xiàn)原理的文章,實(shí)現(xiàn)了簡(jiǎn)單的賦值語(yǔ)句和函數(shù)調(diào)用的類(lèi)型檢查。實(shí)際上類(lèi)型檢查的情況特別多,一篇文章肯定寫(xiě)不完,所以我準(zhǔn)備用系列文章來(lái)講述各種類(lèi)型檢查的實(shí)現(xiàn)原理,幫助大家更好的掌握 typescript。
這一篇我們來(lái)實(shí)現(xiàn) 4.3 新增的 class 的 override 關(guān)鍵字的類(lèi)型檢查。(源碼鏈接在后面)
override 修飾符是干嘛的
首先,我們來(lái)看下這個(gè)修飾符的作用:被 override 標(biāo)示的方法必須得在父類(lèi)中存在,否則會(huì)報(bào)錯(cuò)。
- class Animal {
- getName() { return ''; }
- }
- class Dog extends Animal {
- override bak() {
- return 'wang';
- }
- override getName() {
- return 'wang';
- }
- }
上面這段代碼會(huì)報(bào)錯(cuò):This member cannot have an 'override' modifier because it is not declared in the base class 'Animal'.就是說(shuō)重寫(xiě)的放在父類(lèi)不存在,這樣能避免父類(lèi)重構(gòu)的時(shí)候把一些子類(lèi)需要重寫(xiě)的方法給去掉。
如何實(shí)現(xiàn) override 修飾符的類(lèi)型檢查
其實(shí)所有的修飾符,包括 override、public、static 等,在 parse 成 AST 后都是作為一個(gè)屬性存在的,這個(gè) override 也是,我們通過(guò) astexplorer.net 來(lái)查看一下。
可以看到 override 屬性為 true。這樣我們就可以通過(guò)這個(gè)屬性把該 class 的所有的需要 override 的 ClassMethod 過(guò)濾出來(lái)。
然后還可以拿到 superClass 的名字,從作用域中找到對(duì)應(yīng)的聲明,然后遍歷 AST 找到它所聲明的所有 ClassMethod。
兩者對(duì)比一下,所有不在父類(lèi)中的 ClassMethod 都需要報(bào)錯(cuò)。
代碼實(shí)現(xiàn)
我們基于 babel 來(lái)做 parser 和分析,寫(xiě)一個(gè)插件來(lái)做 override 的類(lèi)型檢查。關(guān)于 babel 插件的基礎(chǔ)可以看小冊(cè)《babel 插件通關(guān)秘籍》。
開(kāi)啟語(yǔ)法 typescript 插件來(lái)解析 ts 語(yǔ)法。
- 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,我們先搭一個(gè)基本的結(jié)構(gòu):
- 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'));
- }
- }
- });
具體的檢查邏輯是拿到父類(lèi)的所有方法名,拿到當(dāng)前類(lèi)的所有 override 方法名,然后做下過(guò)濾。
我們首先要拿到父類(lèi)的 ast,通過(guò)名字從作用域中查找。
- const superClass = path.node.superClass;
- if (superClass) {
- const superClassPath = path.scope.getBinding(superClass.name).path;
- }
然后封裝一個(gè)方法來(lái)拿父類(lèi)方法名,通過(guò) path.traverse 來(lái)遍歷 ast,把收集到的方法名存到 state 中。
- function getAllClassMethodNames(classDeclarationNodePath) {
- const state = {
- allSuperMethodNames: []
- }
- classDeclarationNodePath.traverse({
- ClassMethod(path) {
- state.allSuperMethodNames.push(path.get('key').toString())
- }
- });
- return state.allSuperMethodNames;
- }
這樣就拿到了所有父類(lèi)方法名。
之后需要拿到當(dāng)前類(lèi)的所有方法名并過(guò)濾出 override 為 true 且不在父類(lèi)中的進(jìn)行報(bào)錯(cuò)。
- 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)) {
- // 報(bào)錯(cuò)
- }
- }
- }
- });
- }
報(bào)錯(cuò)的部分使用 code frame 來(lái)創(chuàng)建友好的代碼打印格式,通過(guò) Error.stackTraceLimit 設(shè)置為 0 去掉調(diào)用棧信息。
- 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 的類(lèi)型檢查,整體代碼如下:
- 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 鏈接
測(cè)試效果
我們用最開(kāi)始的代碼來(lái)測(cè)試一下:
- class Animal {
- getName() { return ''; }
- }
- class Dog extends Animal {
- override bak() {
- return 'wang';
- }
- override getName() {
- return 'wang';
- }
- }
打印信息為:
正確的識(shí)別出了 bak 在父類(lèi)不存在的錯(cuò)誤。
至此,我們實(shí)現(xiàn)了 override 的類(lèi)型檢查!
總結(jié)
類(lèi)型檢查情況很多,所以需要一個(gè)系列文章去講,這一篇我們來(lái)實(shí)現(xiàn) override 的類(lèi)型檢查。
override 是 ts 4.3 加入的特性,帶有 override 修飾符的方法必須在父類(lèi)中有對(duì)應(yīng)的聲明,否則會(huì)報(bào)錯(cuò)。
我們通過(guò) babel 插件的方式實(shí)現(xiàn)了類(lèi)型檢查,思路是從作用域取出父類(lèi)的聲明,然后通過(guò) path.traverse 拿到所有方法名,之后再取當(dāng)前類(lèi)的所有方法名,對(duì)于沒(méi)在父類(lèi)中聲明并且?guī)в?override 修飾符的方法進(jìn)行報(bào)錯(cuò)。
本文是 【typescript 類(lèi)型檢查原理】系列文章的第二篇,后續(xù)還會(huì)有更多 typescript 類(lèi)型檢查的實(shí)現(xiàn)原理揭秘的文章。希望能夠幫助大家更好的掌握 typescript。
關(guān)于 babel 插件的知識(shí),可以看我的 babel 小冊(cè)《babel 插件通關(guān)秘籍》,其中有詳細(xì)的講解。
本文源碼鏈接 https://github.com/QuarkGluonPlasma/babel-plugin-exercize/tree/master/exercize-type-checker/src