虽然如今已经有不少实用的 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…