【實(shí)戰(zhàn)】自定義 ESLint Plugin
背景
之前做過(guò)一個(gè)小分享——【優(yōu)化】記一次通過(guò)工具減少 Git 沖突[1]。主要講的是通過(guò)利用 git hooks 在代碼提交之前給相關(guān)的代碼排序,從而減少合代碼時(shí)候的沖突。
上次同事提醒說(shuō),這個(gè) Eslint 就可以做到。我回去查了一下,還真可以,詳情見(jiàn) sort-keys[2]。假如使用了這條規(guī)則,就是要求對(duì)象寫法要遵循一定的順序。比如開(kāi)啟這個(gè)規(guī)則的話,默認(rèn)情況下下面的代碼就會(huì)報(bào)錯(cuò):
let?obj?=?{a:?1,?c:?3,?b:?2};
應(yīng)該為:
let?obj?=?{a:?1,?b:?2,?c:?3};
但是其實(shí)我們的訴求中,還有一種場(chǎng)景,那就是對(duì)象數(shù)組。比如下面這個(gè)場(chǎng)景,我需要根據(jù) label 去決定對(duì)象在數(shù)組中順序(注意:我們這個(gè)場(chǎng)景下數(shù)組的順序?qū)I(yè)務(wù)是沒(méi)有影響的),Eslint 這個(gè)規(guī)則就無(wú)能為力了。
const?FlowList?=?[
??{?value:?'5',?label:?'a'?},
??{?value:?'2',?label:?'C'?},
??{?value:?'1',?label:?'B'?}
];
另外,我們知道 ESLint 規(guī)則可以針對(duì)某個(gè)文件夾或者某個(gè)文件生效,那能不能只針對(duì)于某個(gè)代碼塊呢?
那我們?nèi)绾瓮ㄟ^(guò) Eslint 暴露給我們的能力去實(shí)現(xiàn)這些點(diǎn)呢?
ESLint 是什么?
官方如下:
ESLint 是在 ECMAScript/JavaScript 代碼中識(shí)別和報(bào)告模式匹配的工具,它的目標(biāo)是保證代碼的一致性和避免錯(cuò)誤
ESLint 具有以下特點(diǎn):
- 使用 ?Espree[3] 解析 JavaScript。
- 使用
AST去分析代碼中的模式。 - 完全插件化的。每一個(gè)規(guī)則都是一個(gè)插件,提供了足夠的可拓展能力,讓我們更好的定義使用規(guī)則。
講 ESlint 我們離不開(kāi) AST(抽象語(yǔ)法樹(shù)),我們可以通過(guò) astexplorer[4] 直觀看到 Espree 處理后生成的 AST 的結(jié)構(gòu)。比如 var a = 1; 如下所示:
image-20210812220540504竟然我們知道它的結(jié)構(gòu),我們就可以直接去檢測(cè)它合不合法了。
我們來(lái)講一個(gè)重要的概念——AST Selectors :它是一個(gè)字符串,可用于匹配抽象語(yǔ)法樹(shù)(AST)中的節(jié)點(diǎn)。這對(duì)于在代碼中描述特定的語(yǔ)法模式非常有用。AST 選擇器的語(yǔ)法與 CSS 選擇器的語(yǔ)法類似。如果你以前使用過(guò) CSS 選擇器,那么 AST 選擇器的語(yǔ)法應(yīng)該很容易理解。這個(gè)在我們后面自定義規(guī)則的時(shí)候非常重要。它的語(yǔ)法可以看官方文檔[5]。
ESlint 的原理
在開(kāi)始書寫我們的規(guī)則,我們看看 ESlint 具體的實(shí)現(xiàn)是怎么做的(這里只說(shuō)明單條的 Rule 是怎么書寫的,整體的 ESlint 作用流程這里不展開(kāi))。就以之前提到的 ?sort-keys[6] 為例。
每個(gè)規(guī)則都會(huì)有三個(gè)重要的文件:
lib/rules目錄中的是源文件,具體的校驗(yàn)邏輯可以在這里寫。tests/lib/rules目錄中是測(cè)試文件,寫具體的測(cè)試用例。docs/rules文檔目錄。
在 lib/rules/sort-keys.js 中我們可以找到上面規(guī)則相應(yīng)的源碼。規(guī)則的源文件導(dǎo)出具有以下屬性的對(duì)象。類似如下:
module.exports?=?{
??//?包含規(guī)則的元數(shù)據(jù)
??meta:?{
????//?規(guī)則類型
????type:?"suggestion",
????//?文檔
????docs:?{
??????description:?"require?object?keys?to?be?sorted",
??????category:?"Stylistic?Issues",
??????recommended:?false,
??????url:?"https://eslint.org/docs/rules/sort-keys",
????},
????schema:?[
??????//?可以傳的一些參數(shù)
??????{
????????enum:?["asc",?"desc"],
??????}
????],
????//?提示信息
????messages:?{
??????sortKeys:
????????"Expected?object?keys?to?be?in?{{natural}}{{insensitive}}{{order}}ending?order.?'{{thisName}}'?should?be?before?'{{prevName}}'.",
????},
??},
??create(context)?{
????return?{};
??},
};
meta:代表了這條規(guī)則的元數(shù)據(jù),如其類別,文檔,可接收的參數(shù)的schema等等,官方文檔[7]對(duì)其有詳細(xì)的描述,這里不做贅述。create: ?meta 表達(dá)了我們想做什么,那么create則用表達(dá)了這條 rule 具體會(huì)怎么分析代碼。
create 返回的是一個(gè)對(duì)象,其中 key ?就是上面提到的 AST Selector,在 ?AST Selector 中,我們可以獲取對(duì)應(yīng)選中的內(nèi)容,隨后我們可以針對(duì)選中的內(nèi)容作一定的判斷,看是否滿足我們的規(guī)則,如果不滿足,可用 context.report()拋出問(wèn)題,ESLint ?會(huì)利用我們的配置對(duì)拋出的內(nèi)容做不同的展示。
在 ?AST Selector的末尾添加 :exit 將導(dǎo)致在遍歷過(guò)程中退出匹配節(jié)點(diǎn)時(shí)調(diào)用偵聽(tīng)器,而不是在輸入匹配節(jié)點(diǎn)時(shí)。
自定義 ESlint 插件
基于 `Yeoman generator`[8] (一個(gè)快速幫你搭建工程的腳手架工具),可以快速創(chuàng)建 ESLint plugin 項(xiàng)目。
npm?i?-g?yo
npm?i?-g?generator-eslint
//?創(chuàng)建一個(gè)plugin
yo?eslint:plugin
//?創(chuàng)建一個(gè)規(guī)則
yo?eslint:rule
我創(chuàng)建的目錄結(jié)構(gòu)如下:
├──?README.md
├──?docs?#?文檔
│???└──?rules
│???????├──?array-sort-object.md
│???????└──?sort.md
├──?lib?#?源代碼,規(guī)則文件
│???├──?index.js
│???└──?rules
│???????├──?array-sort-object.js
│???????└──?sort.js
├──?package.json
├──?tests?#?單元測(cè)試文件
│???└──?lib
│???????└──?rules
│???????????├──?array-sort-object.js
│???????????└──?sort.js
└──?yarn.lock
如何做到只檢測(cè)部分代碼?
我們知道 ESlint 的檢測(cè)可以指定到文件維度,但是我們希望只針對(duì)部分的代碼進(jìn)行檢測(cè)。要不然像對(duì)象數(shù)組順序,假如都開(kāi)了檢測(cè),將會(huì)有很多報(bào)錯(cuò)或者警告。
方法是有的,我們發(fā)現(xiàn),ESlint 是可以通過(guò) getCommentsInside ?方法獲取到某個(gè) AST Selector 中的注釋,返回給定節(jié)點(diǎn)內(nèi)所有注釋標(biāo)記的數(shù)組。比如以下:
const?FlowList?=?[
??//?eslint?sortBy:'label'
??{?value:?'5',?label:?'a'?},
??{?value:?'2',?label:?'C'?},
??{?value:?'1',?label:?'B'?}
];
create:?function?(context)?{
????//?獲取到順序的配置,默認(rèn)是升序
????const?order?=?context.options[0]?||?"asc";
????//?variables?should?be?defined?here
??return?{
????ArrayExpression:?(node)?=>?{
??????console.log('getCommentsBefore:',?context.getCommentsInside(node))
????}
??}
打印出來(lái)的結(jié)果如下,我們就可以利用這個(gè)信息進(jìn)行處理。只有評(píng)論命中某個(gè)規(guī)則的時(shí)候,才去處理這段代碼
image-20210812231108912實(shí)現(xiàn)對(duì)象數(shù)組排序
整體的實(shí)現(xiàn)代碼如下,實(shí)現(xiàn)上并不難。整體思路:
是先獲取到要比較的字段(比如上面例子中的
label)。//?獲取到?comment
const?comment?=?context.getCommentsInside(node);
if?(!comment)?return;
//?獲取到排序的字段
const?field?=?comment[0]?&&?comment[0].value?&&?comment[0].value.split("'")[1];
if?(!field)?return;拿到數(shù)組中每一項(xiàng)目標(biāo)字段對(duì)應(yīng)的值([ 'a', 'C', 'B' ])。
//?取每一項(xiàng)排序?qū)ο笾兄?/span>
let?fieldValueArr?=?node.elements.map(item?=>?{
??const?target?=?(item.properties.find((prop)?=>?{
????return?prop.key.name?===?field
??})?||?{?value:?''?});
??return?target.value?&&?target.value.value;
});再對(duì)該數(shù)組進(jìn)行前后順序的檢測(cè),假如不符合我們就報(bào)錯(cuò)。
//?默認(rèn)按照升序排序
for?(let?i?=?1;?i?<?fieldValueArr.length;?i++)?{
??let?reportError?=?false;
??if?(order?===?'asc'?&&?String(fieldValueArr[i]).localeCompare(String(fieldValueArr[i?-?1]))?<?0)?{
????reportError?=?true;
??}?else?if?(order?===?'desc'?&&?String(fieldValueArr[i]).localeCompare(String(fieldValueArr[i?-?1]))?>?0)?{
????reportError?=?true;
??}
??//?判斷是否是降序
??if?(reportError)?{
????context.report({
??????node,
??????message:?`數(shù)組排序不正確。請(qǐng)根據(jù)?${field}?字段排序`,
????});
????break;
??}
}
總結(jié)
Eslint 對(duì)于一個(gè)團(tuán)隊(duì)的代碼規(guī)范是非常重要的,Eslint 自身帶有很多有用的規(guī)則,本文介紹了 ESlint 的基礎(chǔ)原理以及如何自定義 Eslint 插件來(lái)解決對(duì)象數(shù)組排序的問(wèn)題,除此之外,我們可能還有其他的場(chǎng)景可以進(jìn)行嘗試,歡迎大家參與討論~
參考
- ESLint 工作原理探討[9]
- 自定義 ESLint 規(guī)則,讓代碼持續(xù)美麗
參考資料
[1]【優(yōu)化】記一次通過(guò)工具減少 Git 沖突: https://juejin.cn/post/6895534290411454477
[2]sort-keys: https://eslint.org/docs/rules/sort-keys#rule-details
[3]Espree: https://github.com/eslint/espree
[4]astexplorer: https://astexplorer.net/
[5]官方文檔: https://eslint.org/docs/developer-guide/selectors
[6]sort-keys: https://eslint.org/docs/rules/sort-keys#rule-details
[7]官方文檔: https://link.juejin.cn?target=http%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%3A%2F%2Feslint.org%2Fdocs%2Fdeveloper-guide%2Fworking-with-rules%23rule-basics
[8]Yeoman generator: https://yeoman.io/authoring/
ESLint 工作原理探討: https://juejin.cn/post/6844903749886935053#heading-5
