雖然如今已經有不少實用的 ESLint 插件了,但隨着項目不斷迭代發展,你可能會遇到已有 ESLint 插件不能知足如今團隊開發的狀況。這時候,你須要本身來建立一個 ESLint 插件。javascript
本文我將帶你瞭解各類Lint工具的大體歷史,而後一步一步地建立一個屬於你本身的 ESLint 插件,以及教你如何利用AST
抽象語法樹來制定這個插件的規則。前端
以此來帶你瞭解 ESLint 的實現原理。java
Lint 是爲了解決代碼不嚴謹而致使各類問題的一種工具。好比 ==
和 ===
的混合使用會致使一些奇怪的問題。node
2002年,Douglas Crockford 開發了多是第一款針對 JavaScript 的語法檢測工具 —— JSLint,並於 2010 年開源。react
JSLint 面市後,確實幫助許多 JavaScript 開發者節省了很多排查代碼錯誤的時間。可是 JSLint 的問題也很明顯—— 幾乎不可配置,全部的代碼風格和規則都是內置好的;再加上 Douglas Crockford 推崇道系「愛用不用」的優良傳統,不會向開發者妥協開放配置或者修改他以爲是對的規則。因而 Anton Kovalyov 吐槽:「JSLint 是讓你的代碼風格更像 Douglas Crockford 的而已」,而且在 2011 年 Fork 原項目開發了 JSHint。《Why I forked JSLint to JSHint》git
JSHint 的特色就是可配置,同時文檔也相對完善,並且對開發者友好。很快你們就從 JSLint 轉向了 JSHint。github
後來幾年你們都將 JSHint 做爲代碼檢測工具的首選,但轉折點在2013年,Zakas 發現 JSHint 沒法知足本身制定規則需求,而且和 Anton 討論後發現這根本不可能在JShint上實現,同時 Zakas 還設想發明一個基於 AST
的 lint。因而 2013年6月份,Zakas 發佈了全新 lint 工具——ESLint。《Introducing ESLint》npm
ESLint早期源碼:json
var ast = esprima.parse(text, { loc: true, range: true }),
walk = astw(ast);
walk(function(node) {
api.emit(node.type, node);
});
return messages;
複製代碼
ESLint 的出現並無撼動 JSHint 的霸主地位。因爲前者是利用 AST 處理規則,用 Esprima 解析代碼,執行速度要比只須要一步搞定的 JSHint 慢不少;其次當時已經有許多編輯器對 JSHint 支持完善,生態足夠強大。真正讓 ESLint 逆襲的是 ECMAScript 6
的出現。api
2015 年 6 月,ES2015 規範正式發佈。可是發佈後,市面上瀏覽器對最新標準的支持狀況極其有限。若是想要提早體驗最新標準的語法,就得靠 Babel
之類的工具將代碼編譯成 ES5 甚至更低的版本,同時一些實驗性的特性也能靠 Babel 轉換。 但這時候的 JSHint 短時間內沒法提供支持,而 ESLint 卻只須要有合適的解析器就能繼續去 lint 檢查。Babel 團隊就爲 ESLint 開發了一款替代默認解析器的工具,也就是如今咱們所見到的 babel-eslint
,它讓 ESLint 成爲率先支持 ES6 語法的 lint 工具。
也是在 2015 年,React 的應用愈來愈普遍,誕生不久的 JSX 也越發流行。ESLint 自己也不支持 JSX 語法。可是由於可擴展性,eslint-plugin-react
的出現讓 ESLint 也能支持當時 React 特有的規則。
2016 年,JSCS 開發團隊認爲 ESLint 和 JSCS 實現原理太過類似,並且須要解決的問題也都一致,最終選擇合併到 ESLint,並中止 JSCS 的維護。
當前市場上主流的 lint 工具以及趨勢圖:
今後 ESLint 一統江湖,成爲替代 JSHint 的前端主流工具。
本文 ESLint
插件目標是在項目開發中禁用:console.time()
。
這裏咱們利用 yeoman 和 generator-eslint 來構建插件的腳手架代碼。安裝:
npm install -g yo generator-eslint
複製代碼
本地新建文件夾eslint-plugin-demofortutorial:
mkdir eslint-plugin-demofortutorial
cd eslint-plugin-demofortutorial
複製代碼
初始化 ESLint 插件的項目結構:
yo eslint:plugin // 搭建一個初始化的目錄結構
複製代碼
此時文件的目錄結構爲:
.
├── README.md
├── lib
│ ├── index.js
│ └── rules
├── package.json
└── tests
└── lib
└── rules
複製代碼
安裝依賴:
npm install
複製代碼
至此,環境搭建完畢。
終端執行:
yo eslint:rule // 生成默認 eslint rule 模版文件
複製代碼
此時項目結構爲:
.
├── README.md
├── docs // 使用文檔
│ └── rules
│ └── no-console-time.md
├── lib // eslint 規則開發
│ ├── index.js
│ └── rules // 此目錄下能夠構建多個規則,本文只拿一個規則來說解
│ └── no-console-time.js
├── package.json
└── tests // 單元測試
└── lib
└── rules
└── no-console-time.js
複製代碼
上面結構中,咱們須要在 ./lib/ 目錄下去開發 Eslint 插件,這裏是定義它的規則的位置。
在正式寫 ESLint
插件前,你須要瞭解下 ESLint
的工做原理。其中 ESLint
使用方法你們應該都比較熟悉,這裏不作講解,不瞭解的能夠點擊官方文檔如何在項目中配置 ESLint。
在公司團隊項目開發中,不一樣開發者書寫的源碼是各不相同的,那麼在 ESLint
中,如何去分析每一個人寫的源碼呢?
做爲開發者,面對這類問題,咱們必須懂得要使用 抽象的手段 !那麼 Javascript
的抽象性如何體現呢?
沒錯,就是 AST
(Abstract Syntax Tree(抽象語法樹)),再祭上那張看了幾百遍的圖。
在 ESLint
中,默認使用 esprima 來解析咱們書寫的 Javascript
語句,讓其生成抽象語法樹,而後去 攔截 檢測是否符合咱們規定的書寫方式,最後讓其展現報錯、警告或正常經過。 ESLint
的核心就是規則(rules
),而定義規則的核心就是利用 AST
來作校驗。每條規則相互獨立,能夠設置禁用off
、警告warn
⚠️和報錯error
❌,固然還有正常經過不用給任何提示。
上面講完了 ESLint
和 AST
的關係以後,咱們能夠正式進入開發具體規則。先來看以前生成的 lib/rules/no-console-time.js
:
/**
* @fileoverview no console.time()
* @author Allan91
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "no console.time()",
category: "Fill me in",
recommended: false
},
fixable: null, // or "code" or "whitespace"
schema: [
// fill in your schema
]
},
create: function(context) {
// variables should be defined here
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
// any helper functions should go here or else delete this section
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
// give me methods
};
}
};
複製代碼
這個文件給出了書寫規則的模版,一個規則對應一個可導出的 node
模塊,它由 meta
和 create
兩部分組成。
Create 返回一個對象,其中最多見的鍵名是AST
抽象語法樹中的選擇器,在該選擇器中,咱們能夠獲取對應選中的內容,隨後咱們能夠針對選中的內容做必定的判斷,看是否知足咱們的規則。若是不知足,可用 context.report
拋出問題,ESLint
會利用咱們的配置對拋出的內容作不一樣的展現。
具體參數配置詳情見官方文檔
本文建立的 ESLint
插件是爲了避免讓開發者在項目中使用 console.time()
,先看看這段代碼在抽象語法樹中的展示:
其中,咱們將會利用如下內容做爲判斷代碼中是否含有 console.time
:
那麼咱們根據上面的AST
(抽象語法書)在 lib/rules/no-console-time.js
中這樣書寫規則:
/** * @fileoverview no console.time() * @author Allan91 */
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "no console.time()",
category: "Fill me in",
recommended: false
},
fixable: null, // or "code" or "whitespace"
schema: [
// fill in your schema
],
// 報錯信息描述
messages: {
avoidMethod: "console method '{{name}}' is forbidden.",
},
},
create: function(context) {
return {
// 鍵名爲ast中選擇器名
'CallExpression MemberExpression': (node) => {
// 若是在ast中知足如下條件,就用 context.report() 進行對外警告⚠️
if (node.property.name === 'time' && node.object.name === 'console') {
context.report({
node,
messageId: 'avoidMethod',
data: {
name: 'time',
},
});
}
},
};
}
};
複製代碼
再修改 lib/index.js
:
"use strict";
module.exports = {
rules: {
'no-console-time': require('./rules/no-console-time'),
},
configs: {
recommended: {
rules: {
'demofortutorial/no-console-time': 2, // 能夠省略 eslint-plugin 前綴
},
},
},
};
複製代碼
至此,Eslint
插件建立完成。接下去你須要作的就是將此項目發佈到 npm平臺。 根目錄執行:
npm publish
複製代碼
打開npm平臺,能夠搜索到上面發佈的 eslint-plugin-demofortutorial
這個 Node 包。
發佈完以後在你須要的項目中安裝這個包:
npm install eslint-plugin-demofortutorial -D
複製代碼
而後在 .eslintrc.js
中配置:
"extends": [
"eslint:recommended",
"plugin:eslint-plugin-demofortutorial/recommended",
],
"plugins": [
'demofortutorial'
],
複製代碼
若是以前沒有.eslintrc.js
文件,能夠執行下面命令生成:
npm install -g eslint
eslint --init
複製代碼
此時,若是在當前項目的 JS
文件中書寫 console.time
,會出現以下效果:
對於完整的 npm
包來講,上面還只算是個「半成品」,咱們須要寫單元測試來保證它的完整性和安全性。
下面來完成單元測試,在 ./tests/lib/rules/no-console-time.js
中編寫以下代碼:
'use strict';
// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------
let rule = require('../../../lib/rules/no-console-time');
let RuleTester = require('eslint').RuleTester;
// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------
let ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 10,
},
});
ruleTester.run('no-console-time', rule, {
valid: [ // 合法示例
'_.time({a:1});',
"_.time('abc');",
"_.time(['a', 'b', 'c']);",
"lodash.time('abc');",
'lodash.time({a:1});',
'abc.time',
"lodash.time(['a', 'b', 'c']);",
],
invalid: [ // 不合法示例
{
code: 'console.time()',
errors: [
{
messageId: 'avoidMethod',
},
],
},
{
code: "console.time.call({}, 'hello')",
errors: [
{
messageId: 'avoidMethod',
},
],
},
{
code: "console.time.apply({}, ['hello'])",
errors: [
{
messageId: 'avoidMethod',
},
],
},
{
code: 'console.time.call(new Int32Array([1, 2, 3, 4, 5]));',
errors: 1,
},
],
});
複製代碼
上面測試代碼詳細介紹見官方文檔。
根目錄執行:
npm run test
複製代碼
至此,這個包的開發完成。其它規則開發也是相似,好比您能夠繼續制定其它規範,好比 ️console.log()
、debugger
警告等等。
因爲自動生成ESLint
的項目中依賴的 eslint
版本還在 3.x 階段,會對單元測試語法解析形成以下報錯:
'Parsing error: Invalid ecmaVersion.'
複製代碼
建議將該包升級到 "eslint": "^5.16.0"
。
以上。
查看Npm上發佈的包
zhuanlan.zhihu.com/p/32297243 en.wikipedia.org/wiki/Lint_(… octoverse.github.com/ jslint.com medium.com/@anton/why-… www.nczonline.net/blog/2013/0… eslint.org jscs.info github.com/babel/babel… github.com/yannickcr/e… www.nczonline.net/blog/2016/0… medium.com/@markelog/j…