之前写过一篇 eslint入门教程,只是针对一些 eslint rule 搭建以及实现一个简单的 demo,在实际应用中需要更多的 api 以及一些技巧来实现复杂的需求场景,本文针对 eslint rule 的一些开发技巧进行整理,希望对你有所帮助。
基础格式
一个 eslint 规则的基础结构如下,包含 meta 对象和 create 方法。meta 对象包含规则的元数据,create 函数返回一个对象,该对象具有 eslint 调用的方法,可以用来遍历 AST visit 节点。
// customRule.js
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "Description of the rule",
},
fixable: "code",
schema: [], // 没有提供选项
},
create: function (context) {
return {
// 回调函数
};
},
};
meta元数据
meta 对象包含以下属性,分别是:
- type:(string)表示规则的类型,枚举值:"problem"、"suggestion" 或 "layout" 之一。
- problem:该规则识别将导致的错误或者可能导致的混乱行为代码,开发人员应该高度优先解决。
- suggestion:该规则建议一些更好的代码实现方式,开发人员就算不适用该规则也不会有影响。
- layout:该规则关心代码的格式化,比如空格,逗号,分号等。
- docs:(object)核心规则必须要声明,自定义规则不需要太注重这个配置。
- description:(string)规则的描述。
- recommended:(boolean)表示在配置文件中,是否使用
"extends": "eslint:recommended"
启用该规则。 - url:(string)在编辑器报错里,会提供一个链接给开发人员查看具体的规则文档。
- fixable:(stirng)是否可修复,枚举值: "code" 或 "whitespace"。如果规则不可以通过命令
--fix
修复,则不需要声明。 - hasSuggestions:(boolean)规则是否可以返回建议,不配置则默认是 false。
- schema:(object | array)规则配置,在
.eslintrc
配置的配置格式,通过 schema 来限制。 - deprecated:(boolean)表示规则是否被废弃。
- replaceBy:(array) 如果规则被废弃,使用替代的规则。
create方法
create
方法返回一个对象,可以方便我们遍历 AST 节点。遍历的方式可以是节点类型或者选择器 。返回的对象的可以是以下属性:
- 节点或者选择器,在 down tree 调用。
ReturnStatement: function(node) {
// 向下遍历调用
}
- 节点或者选择器加
:exit
,在 up tree 调用。
"FunctionExpression:exit": function(node) {
// 向上遍历调用
}
- 事件名称,调用 handle 函数进行代码链路分析
onCodePathStart: function (codePath, node) {
// 分析代码路径开始
}
选择器遍历
eslint 提供了类似css选择器的语法来方便我们匹配对应的节点类型,使得我们遍历AST节点更加方便,目前支持的选择器有:
- AST 节点类型:
ForStatement
- 通配符(匹配所有节点):
*
- 存在属性:
[attr]
- 属性值:
[attr="foo"]
或[attr=123]
- 属性正则:
[attr=/foo.*/]
(有一些已知问题) - 属性条件:
[attr!="foo"]
、[attr>2]
、[attr<3]
、[attr>=2]
或[attr<=3]
- 嵌套属性:
[attr.level2="foo”]
- 字段:
FunctionDeclaration > Identifier.id
- 第一个或最后一个子项:
:first-child
或:last-child
- 第 n 个子项(不支持 ax+b):
:nth-child(2)
- 最后 n 个子项(不支持 ax+b):
:nth-last-child(1)
- 后裔:
FunctionExpression ReturnStatement
- 子项:
UnaryExpression > Literal
- 随同兄弟:
VariableDeclaration ~ VariableDeclaration
- 相邻兄弟:
ArrayExpression > Literal + SpreadElement
- 否定:
:not(ForStatement)
- 任意匹配:
:matches([attr] > :first-child, :last-child)
- AST 节点类:
:statement
、:expression
、:declaration
、:function
或:pattern
例如我想实现一个禁止使用 window.open ,有两种写法:
不用选择器
create(context) {
return {
MemberExpression(node) {
if (
node.object.type === "Identifier" &&
node.object.name === "window" &&
node.property.type === "Identifier" &&
node.property.name === "open"
) {
context.report({
node,
messageId: "tip",
});
}
},
};
}
使用选择器
create(context) {
return {
"MemberExpression[property.name='open'][object.name='window']": (node) => {
context.report({
node,
messageId: "tip",
});
},
};
},
可以看出使用选择器的语法会更清晰,方便开发。
上下文对象
create 方法提供了一个参数 context 对象,可以让我们获取到当前上下文一些信息,具体可以查阅官方文档,上下文对象。
我们重点关注 context.report()
方法,eslint 就是通过该方法给开发人员报告错误信息。
context.report({
node: node,
message: "Unexpected identifier"
});
message 可以使用占位符的方式定义,在 meta 配置 messages 对象,就可以方便 message 复用了。
module.exports = {
meta: {
messages: {
avoidName: "Avoid using variables named '{{ name }}'",
},
},
create(context) {
return {
Identifier(node) {
if (node.name === "foo") {
context.report({
node,
messageId: "avoidName",
data: {
name: "foo",
},
});
}
},
};
},
};