從0到1開(kāi)發(fā)一個(gè)簡(jiǎn)單的 eslint 插件

?前言:eslint 我們常應(yīng)用在代碼靜態(tài)掃描中,通過(guò)設(shè)定的 eslint 的語(yǔ)法規(guī)則,來(lái)對(duì)代碼進(jìn)行檢查,通過(guò)規(guī)則來(lái)約束代碼的風(fēng)格,以此來(lái)提高代碼的健壯性,避免因?yàn)榇a不規(guī)范導(dǎo)致應(yīng)用出現(xiàn) bug 的可能。而規(guī)則是自由的,你可以設(shè)定內(nèi)部自己團(tuán)隊(duì)適用的規(guī)則,也可以直接使用開(kāi)源社區(qū)比較熱門的
?規(guī)則集合, 比如 airbnb、eslint-plugin-vue 等
1.eslint 的配置
?手寫規(guī)則前,讓我們重溫下 eslint 配置,通常我們是使用
?.eslintrc.js來(lái)配置 eslint 的,或者也可以直接package.json中定義 eslintConfig 的屬性

上圖 ?? 是 eslint 主要的配置,我們簡(jiǎn)單回顧下每個(gè)配置的背后包含的意義
1.1 parse
?parse 是用來(lái)定義 eslint 所使用的解析器,默認(rèn)是使用
?Espree??, 解析器的作用是將代碼 code 轉(zhuǎn)化成為一種 AST 抽象語(yǔ)法樹(shù),eslint 中叫ESTree,你可以理解為將 code 翻譯為ESLint能聽(tīng) ?? 懂的話
關(guān)于 Espree 可以參考下面這個(gè)例子

而常用的解析器還有包括以下幾種
Esprima: 上文提到 espree 就是基于 Esprima 改良的
Babel-esLint:一個(gè)對(duì) Babel 解析器的包裝,當(dāng)你項(xiàng)目中使用了 babel,babel 的解析器會(huì)把你的 code 轉(zhuǎn)換為 AST,然后該解析器會(huì)將其轉(zhuǎn)換為 ESLint 能懂的 ESTree。這個(gè)目前我們應(yīng)用的較多,目前也不再維護(hù)和更新,升級(jí)為
@babel/eslint-parser@typescript-eslint/parser: 將 TypeScript 轉(zhuǎn)換成與 estree 兼容的形式,以便在 ESLint 中使用。
對(duì)于 AST 的模擬生成,感興趣的同學(xué)可以使用astexplorer在線嘗試

總結(jié):無(wú)論你使用那種解析器,本質(zhì)是都是為了將 code 轉(zhuǎn)換為 ESLint 能夠閱讀的語(yǔ)言ESTree??
1.2 parseOption
?parserOptions 參數(shù)是用來(lái)控制 parse 解析器, 主要包括以下幾個(gè)屬性 ??,我們挑重點(diǎn)的講講
?

ecmaVersion:用來(lái)指定你想要使用的 ECMAScript 版本,默認(rèn)設(shè)置為 5,舉個(gè)例子:默認(rèn)情況下,ESLint 支持 ECMAScript 5 語(yǔ)法,但如果你想讓 eslint 使用 es6 特征來(lái)支持,就可以通過(guò)修改 parserOptions 中"ecmaVersion": 6
1.3 rules
?rules 就是 eslint 的規(guī)則,你可以在 rules 配置中根據(jù)在不同場(chǎng)景、不同規(guī)范下添加自定義規(guī)則詳情可參考之前 ?? 樹(shù)醬的 前端規(guī)范那些事
?
1.4 extends(擴(kuò)展) 與 plugins(插件)
?extends 和 plugins 很容易混淆,本質(zhì)是為了加強(qiáng) eslint 的擴(kuò)展性,使得我們可以直接使用別人已經(jīng)寫好的 eslint 規(guī)則,方便快捷的應(yīng)用到項(xiàng)目中,有點(diǎn)類似之前文章中Babel 配置傻傻看不懂?提及的 babel 配置中的 present 和 plugin
?
比如你使用 extends 去擴(kuò)展 { "extends": [ "eslint:recommended", "plugin:react/recommended", "eslint-config-standard", ]}
但是如果你想用插件,其實(shí)等價(jià)于 {"plugin": ['react','standard']}
? 提醒:官方規(guī)定 npm 包的擴(kuò)展必須以 eslint-config- 開(kāi)頭,我們使用過(guò)程中可以省略這個(gè)開(kāi)頭,上面案例中 eslint-config-standard 可以直接簡(jiǎn)寫成 standard。同樣,如果要開(kāi)發(fā)一個(gè) eslint 插件,也是需要以這種形式來(lái)命名,下節(jié)會(huì)介紹
我們?cè)倥e個(gè)列子

上圖我們通過(guò)上面這個(gè)配置例子,我們可以看到要么是plugins:[]要么是extends:[],通過(guò)上圖所示的配置二相對(duì)于配置一少了parser, parserOptions 和 plugins 等的信息配置,但其實(shí)這兩個(gè)配置最終實(shí)現(xiàn)的結(jié)果是一致的,這是因?yàn)榕渲枚卸x的 extends:plugin:@typescript-eslint/recommended 會(huì)自動(dòng)加載上述提到的其他幾個(gè)配置信息
2 開(kāi)發(fā) eslint 插件
?通過(guò)上一節(jié)對(duì) eslint 的配置的了解,接下來(lái)看看如何從 0 到 1 開(kāi)發(fā)一個(gè) eslint 插件。
?
2.1 eslint 插件初始化
?ESLint 官方為了方便開(kāi)發(fā)者,提供了使用 Yeoman 腳手架的模板(generator-eslint??)。以此方便我們通過(guò)該腳手架拉取 eslint 插件模版,對(duì) Yeoman 進(jìn)一步了解可以閱讀 ?? 樹(shù)醬的前端工程化那些事 - yeoman
?

第一步:安裝 npm install -g yo generator-eslint第二步:創(chuàng)建一個(gè)文件夾并然后通過(guò)命令行初始化 ESLint 插件的項(xiàng)目結(jié)構(gòu) yo eslint:plugin第三步:完成插件初始化創(chuàng)建
2.2 創(chuàng)建 rule 規(guī)則
?完成插件項(xiàng)目結(jié)構(gòu)初始化創(chuàng)建后,開(kāi)始生成 ESLint 插件中具體規(guī)則,在 ESLint 插件的項(xiàng)目中執(zhí)行命令行
?yo eslint:rule,來(lái)生成 eslint 規(guī)則的模版,實(shí)際效果如下所示

創(chuàng)建成功后,我們看下最終的目錄結(jié)構(gòu)

docs: 使用文檔,描述你編寫的規(guī)則 lib/rules 目錄:規(guī)則開(kāi)發(fā)源碼文件 (例如,no-extra-semi.js) tests/lib/rules 目錄:?jiǎn)卧獪y(cè)試文件 (例如,no-extra-semi.js)
2.3 編寫規(guī)則
?當(dāng)完成上面一系列操作之后,eslint 插件模版初步完成,接下來(lái)我們找到目錄中
?lib/rules中對(duì)剛剛創(chuàng)建的 rule 進(jìn)行開(kāi)發(fā)
假設(shè)我們有個(gè)場(chǎng)景,我們想創(chuàng)建一個(gè)規(guī)則,用來(lái)判讀代碼中是否存在console方法的調(diào)用,首先回到第一節(jié)提到的parse解析器,本質(zhì)上 rule 的邏輯判斷是通過(guò)識(shí)別 Espree 返回的抽象語(yǔ)法 ?? 去判斷,分別針對(duì)各種類型定義檢查方法。寫代碼之前,我們先看下 console 返回的 AST 是長(zhǎng)啥樣?

通過(guò)上圖我們可以清晰的看到 console.log()是屬于 ExpressionStatement(表達(dá)式語(yǔ)句)中的 CallExpression(調(diào)用語(yǔ)句),可以通過(guò) callee 屬性中的 object 來(lái)判斷是否為console, 同時(shí)也可以利用其 property 屬性來(lái)判斷是 console 的哪種方法,比如log、info等
so~ 我們開(kāi)始造玩具,我們通過(guò)在 create 返回的對(duì)象中,定義一個(gè) CallExpression 方法,當(dāng) ESLint 開(kāi)始對(duì) esTree 遍歷時(shí),通過(guò)對(duì)調(diào)用語(yǔ)句的監(jiān)聽(tīng),來(lái)檢查該調(diào)用語(yǔ)句是否為 console 調(diào)用,代碼如下 ??

每條 rule 就是一個(gè) node 模塊,其主要由 meta 和 create 兩部分組成,重點(diǎn)講下下面兩個(gè) ??
meta: 代表了這條規(guī)則的元數(shù)據(jù),包含類別,文檔,可接收的參數(shù)的 schema 等, 其中主要提下 schema,如果指定該選項(xiàng),ESLint 可以通過(guò)識(shí)別的傳參,避免無(wú)效的規(guī)則配置(排除校驗(yàn)),可參考下節(jié)介紹的單元測(cè)試的中傳遞的 options context.report():它用來(lái)發(fā)布警告或錯(cuò)誤(取決于你所使用的配置)
?? 推薦閱讀:
Eslint - Working with Rules
2.4 單元測(cè)試
?當(dāng)完成 eslit 插件開(kāi)發(fā)后,我們需要對(duì)開(kāi)發(fā)完的插件進(jìn)行驗(yàn)證,以此來(lái)保證規(guī)則校驗(yàn)功能的正常使用。eslint 插件開(kāi)發(fā)項(xiàng)目結(jié)構(gòu)中默認(rèn)使用了
?mocha作為單元測(cè)試框架
我們對(duì)tests/rules/treegogo.js單元測(cè)試文件進(jìn)行修改,定義 invalid 與 valid 的不同例子

最后執(zhí)行

2.5 關(guān)于發(fā)布
?在發(fā)布之前,還需要對(duì) packjson 中 main 定義入口文件即
?lib/index.js,暴露出 rules 與 configs

????? 啊寬同學(xué):那我如何定義一個(gè)包含配置的集合呢?
?
是的,官方文檔描述:你可以在一個(gè)插件中在 configs 鍵下指定打包的配置。當(dāng)你想提供不止代碼風(fēng)格,而且希望提供一些自定義規(guī)則來(lái)支持它時(shí),會(huì)非常有用。每個(gè)插件支持多配置,然后當(dāng)你使用的時(shí)候,可以通過(guò)這樣使用 { "extends": ["plugin:tree-eslint/myConfig"] },這就包含預(yù)設(shè)好的規(guī)則配置
最后是 npm 發(fā)布 npm pulish
2.6 如何使用
?通過(guò)第一節(jié)的配置的介紹,我們需要有個(gè)
?.eslintrc文件,如果目錄沒(méi)用可以通過(guò)命令行eslint -init初始化,配置好后,安裝剛剛開(kāi)放好的 eslint 插件
配置一可以對(duì)我們開(kāi)發(fā)的那個(gè) rule 進(jìn)行配置:error,warn,off,如果需要對(duì)部分做排除就加上 option,也可以像配置二引用預(yù)設(shè)好的擴(kuò)展 extends
請(qǐng)你喝杯?? 記得三連哦~
1.閱讀完記得給?? 醬點(diǎn)個(gè)贊哦,有?? 有動(dòng)力
2.關(guān)注公眾號(hào)前端那些趣事,陪你聊聊前端的趣事
3.文章收錄在Github frontendThings 感謝Star?
