版本

选择器

一些规则和 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 和字符串字面量中进行转义 (\\)。

更改语言