新聞中心
本文轉(zhuǎn)載自微信公眾號「神光的編程秘籍」,作者神說要有光 。轉(zhuǎn)載本文請聯(lián)系神光的編程秘籍公眾號。

作為一家“創(chuàng)意+整合+營銷”的成都網(wǎng)站建設(shè)機(jī)構(gòu),我們在業(yè)內(nèi)良好的客戶口碑。成都創(chuàng)新互聯(lián)公司提供從前期的網(wǎng)站品牌分析策劃、網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、成都做網(wǎng)站、創(chuàng)意表現(xiàn)、網(wǎng)頁制作、系統(tǒng)開發(fā)以及后續(xù)網(wǎng)站營銷運(yùn)營等一系列服務(wù),幫助企業(yè)打造創(chuàng)新的互聯(lián)網(wǎng)品牌經(jīng)營模式與有效的網(wǎng)絡(luò)營銷方法,創(chuàng)造更大的價(jià)值。
前段時(shí)間寫過一篇類型檢查的實(shí)現(xiàn)原理的文章,實(shí)現(xiàn)了簡單的賦值語句和函數(shù)調(diào)用的類型檢查。實(shí)際上類型檢查的情況特別多,一篇文章肯定寫不完,所以我準(zhǔn)備用系列文章來講述各種類型檢查的實(shí)現(xiàn)原理,幫助大家更好的掌握 typescript。
這一篇我們來實(shí)現(xiàn) 4.3 新增的 class 的 override 關(guān)鍵字的類型檢查。(源碼鏈接在后面)
override 修飾符是干嘛的
首先,我們來看下這個(gè)修飾符的作用:被 override 標(biāo)示的方法必須得在父類中存在,否則會(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'.就是說重寫的放在父類不存在,這樣能避免父類重構(gòu)的時(shí)候把一些子類需要重寫的方法給去掉。
如何實(shí)現(xiàn) override 修飾符的類型檢查
其實(shí)所有的修飾符,包括 override、public、static 等,在 parse 成 AST 后都是作為一個(gè)屬性存在的,這個(gè) override 也是,我們通過 astexplorer.net 來查看一下。
可以看到 override 屬性為 true。這樣我們就可以通過這個(gè)屬性把該 class 的所有的需要 override 的 ClassMethod 過濾出來。
然后還可以拿到 superClass 的名字,從作用域中找到對應(yīng)的聲明,然后遍歷 AST 找到它所聲明的所有 ClassMethod。
兩者對比一下,所有不在父類中的 ClassMethod 都需要報(bào)錯(cuò)。
代碼實(shí)現(xiàn)
我們基于 babel 來做 parser 和分析,寫一個(gè)插件來做 override 的類型檢查。關(guān)于 babel 插件的基礎(chǔ)可以看小冊《babel 插件通關(guān)秘籍》。
開啟語法 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,我們先搭一個(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'));
- }
- }
- });
具體的檢查邏輯是拿到父類的所有方法名,拿到當(dāng)前類的所有 override 方法名,然后做下過濾。
我們首先要拿到父類的 ast,通過名字從作用域中查找。
- const superClass = path.node.superClass;
- if (superClass) {
- const superClassPath = path.scope.getBinding(superClass.name).path;
- }
然后封裝一個(gè)方法來拿父類方法名,通過 path.traverse 來遍歷 ast,把收集到的方法名存到 state 中。
- function getAllClassMethodNames(classDeclarationNodePath) {
- const state = {
- allSuperMethodNames: []
- }
- classDeclarationNodePath.traverse({
- ClassMethod(path) {
- state.allSuperMethodNames.push(path.get('key').toString())
- }
- });
- return state.allSuperMethodNames;
- }
這樣就拿到了所有父類方法名。
之后需要拿到當(dāng)前類的所有方法名并過濾出 override 為 true 且不在父類中的進(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 來創(chuàng)建友好的代碼打印格式,通過 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 的類型檢查,整體代碼如下:
- 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';
- }
- }
打印信息為:
正確的識(shí)別出了 bak 在父類不存在的錯(cuò)誤。
至此,我們實(shí)現(xiàn)了 override 的類型檢查!
總結(jié)
類型檢查情況很多,所以需要一個(gè)系列文章去講,這一篇我們來實(shí)現(xiàn) override 的類型檢查。
override 是 ts 4.3 加入的特性,帶有 override 修飾符的方法必須在父類中有對應(yīng)的聲明,否則會(huì)報(bào)錯(cuò)。
我們通過 babel 插件的方式實(shí)現(xiàn)了類型檢查,思路是從作用域取出父類的聲明,然后通過 path.traverse 拿到所有方法名,之后再取當(dāng)前類的所有方法名,對于沒在父類中聲明并且?guī)в?override 修飾符的方法進(jìn)行報(bào)錯(cuò)。
本文是 【typescript 類型檢查原理】系列文章的第二篇,后續(xù)還會(huì)有更多 typescript 類型檢查的實(shí)現(xiàn)原理揭秘的文章。希望能夠幫助大家更好的掌握 typescript。
關(guān)于 babel 插件的知識(shí),可以看我的 babel 小冊《babel 插件通關(guān)秘籍》,其中有詳細(xì)的講解。
本文源碼鏈接 https://github.com/QuarkGluonPlasma/babel-plugin-exercize/tree/master/exercize-type-checker/src
當(dāng)前文章:Typescript類型檢查原理之Override是如何實(shí)現(xiàn)的
鏈接URL:http://m.fisionsoft.com.cn/article/dhodogj.html


咨詢
建站咨詢
