关于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的了解,先到这里。