版本

选择器

某些规则和 API 允许使用选择器来查询 AST。此页面旨在

  1. 解释什么是选择器
  2. 描述创建选择器的语法
  3. 描述选择器可以用于什么

什么是选择器?

选择器是一个字符串,可用于匹配抽象语法树 (AST) 中的节点。这对于描述代码中的特定语法模式很有用。

AST 选择器的语法类似于 CSS 选择器 的语法。如果您以前使用过 CSS 选择器,那么 AST 选择器的语法应该很容易理解。

最简单的选择器只是一个节点类型。节点类型选择器将匹配具有给定类型的所有节点。例如,考虑以下程序

var foo = 1;
bar.baz();

选择器“Identifier”将匹配程序中所有 Identifier 节点。在这种情况下,选择器将匹配 foobarbaz 的节点。

选择器不限于匹配单个节点类型。例如,选择器 VariableDeclarator > Identifier 将匹配所有具有 VariableDeclarator 作为直接父节点的 Identifier 节点。在上面的程序中,这将匹配 foo 的节点,但不匹配 barbaz 的节点。

选择器可以具有什么语法?

支持以下选择器

  • 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 和字符串文字中进行转义 (\\)。

更改语言