开发一个eslint插件

先创建一个 eslint 插件使用的空项目,ESlint 推荐使用的是Yeoman Generator,首先我们来全局安装一下 Yeoman,安装命令如下:

1
npm install -g yo

Yeoman是一款通用的初始化工具,想要初始化 ESlint 插件,我们还要安装 ESlint 模板,安装命令如下:

1
npm install -g generator-eslint

接下来,我们新建一个目录,命令如下:

1
mkdir eslint-plugin-utils

稍等片刻就可以完成初始化工作,lib/rules 目录下放着我们的自定义规则,而 testt/lib/rules 目录下存放规则对应的单元测试代码。

type-typeof-limit

我们知道使用 typeof 操作符来判断一个变量为对象时可能存在问题,那么我们接下来就要开发这样一个 eslint 插件,在发现“typeof * ===’object’”的时候给出报错提示。首先我们使用yo eslint:rule命令来创建一个规则。

1
yo eslint:rule

输入上面的命令以后我们会进行一点点的交互询问,然后就会在我们的目录下生成两个文件,那么我们打开lib/rules/type-typeof-limit.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
meta: {
type: null, // `problem`, `suggestion`, or `layout`
docs: {
description: 'typeof不能用于对象和数组',
recommended: false,
url: null // URL to the documentation page for this rule
},
fixable: null, // Or `code` or `whitespace`
schema: [] // Add a schema if the rule has options
},

create(context) {
return {
// visitor functions for different types of nodes
}
}
}

其中 meta 字段是规则的元数据,这里我们需要关注的字段如下几个:

  • type: 规则的类型,Problem 代表报错,这里需要将 type 的值修改为 problem

  • docs: 存放规则文档信息

    description: 指定规则的简短描述,需要填写。

    category: 指定规则的分类信息。

  • fixable: 表示规则是否提供自动修复功能,当其值被设置为 true 时,需要提供自动修复的代码。

create 函数里面是具体的逻辑,其返回一个对象,该对象的属性名表示节点类型,在向下遍历树时,当遍历到属性名匹配的节点时,ESlint 会调用属性名对应的函数,我们要写的这个规则的函数如下,其含义是每次遇到 BinaryExpression 节点,都会调用传递给 BinaryExpression 属性的函数。

1
2
3
4
5
6
7
module.exports = {
create(context) {
return {
BinaryExpression: (node) => {}
}
}
}

ESlint 的原理

ESlint 会将每个 javascript 文件解析为抽象语法树(AST)。ESlint 官网也提供了一款工具,可以查看指定代码解析后的 AST,例如下面的代码:

1
typeof a === 'object'

ESlint 会将上述代码解析后返回一个嵌套的 AST,每个节点中的 type 属性表示当前节点的类型,上述代码的判断表达式可以用下面的逻辑来判断:

  • BinaryExpression 节点
  • left.operator 为 typeof
  • operator 为==或===
  • right 为 Literal,且 value 为 object

前面我们说过了 ESlint 遍历到 BinaryExpression 节点后会执行传递给 BinaryExpression 属性的函数,并将 BinaryExpression 节点传递给这个函数,然后进行上面的逻辑判断,如果为 true,那么 ESlint 调用 context.report 报告错误,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
create(context) {
return {
BinaryExpression(node) => {
const operator = node.operator
const left = node.left
const right = node.right
if ((operator === '==' || operator === '===')
&& left.operator === 'typeof'
&& left.type==='UnaryExpression'
&& right.type === 'Literal'
&& right.value === 'object') {
context.report({
node,
message: 'typeof不能用于对象和数组,请使用@erdanlib/type'
})
}
}
}
}
}

ESlint 推荐使用测试驱动开发,上面的代码可以通过写单元测试来快速验证结果。修改 tests/lib/rules/type-typeof-limit.js 文件中的内容如下,其中包括三个单元测试,一个合法的单元测试和两个非法的单元测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const rule = require('../../../lib/rules/type-typeof-limit'),
RuleTester = require('eslint').RuleTester

const msg = 'typeof不能用于对象和数组,请使用 @erdanlib/type'
const ruleTester = new RuleTester({})
ruleTester.run('type-typeof-limit', rule, {
valid: [
// give me some code that won't trigger a warning
{
code: 'typeof a == "number"'
},
{
code: 'a == "object"'
}
],

invalid: [
{
code: 'typeof a == "object"',
errors: [{ message: msg }]
},
{
code: 'typeof a === "object"',
errors: [{ message: msg }]
}
]
})

写好单元测试以后可以执行npm run test命令来检验我们的测试结果,如果用例全部通过的话,我们就可以在真实环境下使用一下我们刚刚写好的插件了。

首先在插件目录下执行下面命令,这会将本地的插件通过软连接的方式连接到本地的 npm 全局目录

1
npm link

接下来我们新建一个目录并初始化 eslint 配置

1
2
3
4
5
6
7
mkdir eslint-plugin-utils-demo

npm init -y

npm install eslint -D

touch .eslintrc.js

然后在 eslint-plugin-utils-demo 这个目录下执行以下命令,这样会在 node_modules 目录下创建一个软连接

1
npm link @erdanlib/eslint-plugin-utils

接下来,修改根目录下的.eslinttc.js 文件,内容如下:

1
2
3
4
5
6
module.exports = {
plugins: ['@erdanlib/utils'],
rules: {
'@erdanlib/utils/type-typeof-limit': 2
}
}

我们创建一个新的 js 文件,在该文件中输入以下代码:

1
typeof a === 'object'

如果看到红色波浪线的提示,那么说明我们的插件就可以正常应用到项目中啦,至此,一个 eslint 插件开发完毕。

发布 eslint 插件

开发完毕后,想要让更多的人使用,那就要发布到 npm 上面,输入以下命令

1
2
3
4
npm login  // 登录

npm publish // 发布

这里需要注意的是记得更改eslint-plugin-utils这个目录的 package.json 文件:

1
2
3
4
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},