选择器
一些规则和 API 允许使用选择器来查询 AST。本页的目的是:
- 解释什么是选择器
- 描述创建选择器的语法
- 描述选择器可以用来做什么
什么是选择器?
选择器是可以用来匹配抽象语法树(AST)中节点的字符串。这对于描述你的代码中的特定语法模式很有用。
AST 选择器的语法与 CSS 选择器的语法类似。如果你以前使用过 CSS 选择器,AST 选择器的语法应该很容易理解。
最简单的选择器只有节点类型。一个节点类型选择器将匹配所有具有给定类型的节点。比如思考下面这个程序:
var foo = 1;
bar.baz();
选择器 Identifier 将匹配程序中所有的 Identifier 节点。在这种情况下,选择器将匹配 foo、bar 和 baz 的节点。
选择器并不局限于对单一节点类型的匹配。例如,选择器 VariableDeclarator > Identifier 将匹配所有以 VariableDeclarator 为直接父节点的 Identifier 节点。在上面的程序中,这将匹配 foo 的节点,但不匹配 bar 和 baz 的节点。
选择器支持哪些语法?
支持以下选择器:
- 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
这种语法非常强大,可以用来在你的代码中精确选择许多语法模式。
本节中的例子改编自 esquery 文档
选择器可以拿来干嘛?
如果你正在编写自定义的 ESLint 规则,你可能对使用选择器来检查 AST 的特定部分感兴趣。如果你正在为你的代码库配置 ESLint,你可能对用选择器限制特定的语法模式感兴趣。
监听规则中的选择器
当编写一个自定义的 ESLint 规则时,你可以在 AST 被遍历时监听符合特定选择器的节点。
module.exports = {
create(context) {
// ...
return {
// 这个监听器将为所有有块的 IfStatement 节点被调用。
"IfStatement > BlockStatement": function(blockStatementNode) {
// ...你的逻辑在此
},
// 这个监听器会调用所有有 3个 以上参数的函数声明。
"FunctionDeclaration[params.length>3]": function(functionDeclarationNode) {
// ... 你的逻辑在此
}
};
}
};
在选择器的末尾添加 :exit 将导致监听器在遍历过程中退出匹配节点时被调用,而不是在进入节点时被调用。
如果两个或更多的选择器匹配同一个节点,它们的监听器将按照特定性增加的顺序被调用。AST 选择器的特异性类似于 CSS 选择器的特异性。
- 当比较两个选择器时,包含更多的类选择器、属性选择器和伪类选择器(不包括
:not())的选择器的特异性更高。 - 如果类/属性/伪类数量相同,包含更多节点类型选择器的选择器具有更高的特异性。
如果多个选择器的特异性相同,它们的监听器将按字母顺序对该节点进行调用。
使用选择器限制语法
通过 no-restricted-syntax 规则,你可以限制代码中特定语法的使用。例如,你可以使用以下配置来禁止使用没有块状语句作为主体的 if 语句:
{
"rules": {
"no-restricted-syntax": ["error", "IfStatement > :not(BlockStatement).consequent"]
}
}
…或者你也可以使用下面这种等效的配置:
{
"rules": {
"no-restricted-syntax": ["error", "IfStatement[consequent.type!='BlockStatement']"]
}
}
另一个可以禁止调用 require() 的示例:
{
"rules": {
"no-restricted-syntax": ["error", "CallExpression[callee.name='require']"]
}
}
或者你可以强制要求对 setTimeout 的调用总是要有两个参数:
{
"rules": {
"no-restricted-syntax": ["error", "CallExpression[callee.name='setTimeout'][arguments.length!=2]"]
}
}
在 no-restricted-syntax 规则中使用选择器可以让你对代码库中的问题模式有很大的控制力,而不需要编写自定义规则来检测每个模式。
已知问题
由于 esquery 中的一个 bug,无法正确解析包含正斜杠字符 / 的正则表达式,所以 [value=/some\/path/] 会出现语法错误。你可以将 / 字符替换成其对应的 unicode 字符,像这样 [value=/some\u002Fpath/],来解决这个问题。
比如下列配置禁止从 some/path 导入:
{
"rules": {
"no-restricted-syntax": ["error", "ImportDeclaration[source.value=/^some\\u002Fpath$/]"]
}
}
注意 JSON 和字符串字面量中的 \ 需要转义(\\)。