选择器
某些规则和 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 {
// This listener will be called for all IfStatement nodes with blocks.
"IfStatement > BlockStatement": function(blockStatementNode) {
// ...your logic here
},
// This listener will be called for all function declarations with more than 3 parameters.
"FunctionDeclaration[params.length>3]": function(functionDeclarationNode) {
// ...your logic here
}
};
}
};
在选择器的末尾添加 :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 和字符串文字中进行转义 (\\
)。