之前写过一篇 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',
},
});
}
},
};
},
};