我們一起Eslint 探索之 Plugin
關于Eslint-plugin的研究,今天主要談兩點:
實現一個簡單的plugin插件。
eslint如何處理plugin插件。
接著上一篇《Eslint源碼探索》,今天探索一下plugin。今天是研究課。
實現一個簡單的plugin插件
關于Plugin官方文檔說的比較詳細,但是內容太多,這里羅列了幾條關鍵的步驟:
- 生成plugin模版
- 生成rule規則
- rule規則對象配置屬性說明
- 單元測試
這里以生成一個單行注釋不允許出現的字段為例。展開對plugin的探索。
官方提供了模版生成的工具Yeoman。這里一樣:
// 下載
pnpm install -g yo generator-eslint
// 生產模版
yo eslint:plugin
// 生成rule
yo eslint:rule
模版的結構也比較簡單,提供了三個功能,這三個功能都可以通過yo eslint:rule自動生成。
- docs文檔說明。每一個rule都有各自的md。
- lib核心庫,包含每一個rule。
- tests測試用例。每一個rule都是單獨的用例。
依賴第三方包如下:eslint相關和測試相關的。
requireindex這個包,會把lib下的所有rules打包成一個對象。比較實用,以后在其他項目中也會用到。
// requireIndex 把多文件包裝成對象
const requireIndex = require("requireindex");
module.exports.rules = requireIndex(__dirname + "/rules");
// 打印一下,包裝的rules
{
// ...
'notes-keys': {
meta: {
type: 'suggestion',
docs: [Object],
fixable: 'code',
schema: [Array]
},
create: [Function: create]
}
}
mocha主要用于測試。
接下來進入rules,了解一下rules詳細的配置:
在meta中,type較為關鍵,表示此規則的必要性:
- problem,表示此代碼會導致錯誤和異常行為,是優先要處理的。
- suggestion,表示此代碼只是建議,可以有更好的方式來完成。
- layout,表示代碼的美觀性,比如空格、逗號、封號等。
meta: {
type: 'suggestion', // `problem`, `suggestion`, or `layout`
docs: {
description: "校驗注釋中是否包含指定關鍵詞",
category: "Stylistic Issues", // 規則分類
recommended: true, // 配置文件中的 "extends":"eslint:recommended"屬性是否啟用該規則,當創建的規則push到eslint核心規則才生效
url: null, // URL to the documentation page for this rule
},
fixable: 'code', // Or `code` or `whitespace`
// 如fixable主要用于fix功能,如果不設置fixable,eslint將默認此規則不可修復
schema: [
{
"keyWords": {
"type": "array",
"items": {
"type": "string"
}
}
}
], // Add a schema if the rule has options
},
fixable主要用于修復規則,在eslint-plugin包中此處為必填,官方推薦此處必須配置。與之相對應的在create中context.report中fix函數相對應,修復函數的回調。
在schema中定義的字段,主要用于傳入的配置,例如上面的例子,要想指定某些字段在注釋中禁用,這些自定義禁用的字段需要傳入到context中。此處用于限定傳入字段的屬性。當我使用此rule的時候,可以這樣處理:
rules: {
// foo表示在注釋中禁用的字段
"foo/notes-keys": [2, { keyWords: ['foo'] }]
}
create返回一個對象,包含eslint在遍歷語法樹時,用來訪問節點的方法。這些訪問節點的方法很多。這也是生成插件比較難的地方,因為走到這里你需要了解AST的節點類型相關的知識。
上圖是針對AST節點類型的一些簡單的總結,在eslint源碼中定義的訪問NodeType的如下圖。除了源碼中定義的,還有第三方定義,比如在react中就有JSXAttribute和JSXElement等自定義AST節點。
這里先把create的代碼放在這里,圖中的Program就是AST節點訪問的方法。
接下來,開始講講context,context顧名思義,在這里幾乎是無所不能的,它主要能幫我們做的事情,有以下幾點:
- 獲取配置中的信息,如settings、parser相關的屬性。
- 獲取遍歷節點的數組,比如聲明的變量、參數的變量、函數的變量、說明符的變量等。
- 獲取與源文件關聯的文件名。
- 獲取源碼。
- 獲取作用域中所有的變量和引用。
- 報告有問題的代碼。
create中的邏輯比較簡單,獲取到單行注釋,然后判斷是否有配置中的字段,如果有就report。此插件已發布到npm上,供大家學習參考。在npm上直接搜eslint-plugin-foo,只有一個rule。也可點擊文章底部原文鏈接查看github上源碼。
使用也簡單,
// .eslintrc.js
plugins: ['foo'],
rules: {
"foo/notes-keys": [2, { keyWords: ['foo'] }]
}
在發布之前,我們還得寫一下單元測試,這個也簡單,eslint提供了RuleTester方法,我們只需寫幾個簡單的實例就行。
const rule = require("../../../lib/rules/notes-keys"),
RuleTester = require("eslint").RuleTester;
const ruleTester = new RuleTester();
const errMsg = (msg) => `注釋中含有不被允許的字符${msg}`;
ruleTester.run("notes-keys", rule, {
valid: ['// sssss', '// attr'], //生效的, 失效的
invalid: [
{
code: `// xxx:測試內容`,
errors: [{ message: errMsg('xxx') }],
options: [{ // 通過options 配置自定義參數
keyWords: ['xxx']
}],
// 添加fix修復必須添加output,output輸出的內容要和rule中fix返回的結果一致
output: `// :測試內容`,
},
{
code: `// str11: const s='測試內容'`,
errors: [{ message: errMsg('str') }],
options: [{ // 通過options 配置自定義參數
keyWords: ['str']
}],
// 添加fix修復必須添加output,output輸出的內容要和rule中fix返回的結果一致
output: `// 11: const s='測試內容'`,
}
],
});
運行一下
eslint如何加載plugin插件
關于plugin的處理沒在eslint庫里,而是引用了另一個庫@eslint/eslintrc中,這里需要注意一點node在v14版本之后就支持了ES模塊的書寫,簡而言之,我們可以在node文件中使用import/export。然后通過命令行執行node命令即可。
接著上一篇,我們聊到關于plugin、extends和parser的處理在ConfigArrayFactory中。
這里的探索過程也簡單,下載@eslint/eslintrc的源碼,npm install就緒之后,我們創建一個demo.js的文件。順便下載我們發布的插件eslint-plugin-foo,在配置文件中加入。
demo中的代碼如下,我們固定一個path來讓它向下流轉,通過node命令執行,方便我們查看它執行的步驟。
plugin的大致加載流程,和在源碼中的位置,如下:
在loadInDirectory中,我們可查看當前的配置文件對象,也就是我們配置foo的地方。這個時候,還沒有加載plugin。
在_loadPlugin中,能獲取到foo配置的數據。
最后在configArray中收集生效的規則。
最后在demo.js中打印config。已經把foo插件變成一個對象,把rule也放在了rules中。
關于plugin的了解,先到這里。