我可以使用 ast-grep 重现 eslint 的“prefer-object-spread”规则吗?

问题描述 投票:0回答:1

我正在尝试将不同的 linter 及其性能与自定义规则进行比较。 Ast-grep 很有前途,并且可能比 eslint 运行得更快,因为它是用 Rust 而不是 javascript 编写的。然而,eslint 有大量可用的既定规则,而 ast-grep ……没有那么多。因此,我试图通过编写 eslint 目录中的典型规则“prefer-object-spread”来获得一些经验并在两个工具之间创建一些共同点。

一般来说,“prefer-object-spread”会转换

Object.assign({},defaultConfig,customizedConfig)
{...defaultConfig,...customizedConfig}

我主要在 ast-grep 中实现了这条规则,但我正在努力匹配顶级参数(

defaultConfig
customizedConfig
)而不匹配它们的所有后代。上面的简单示例中没有任何后代节点,但请考虑这个示例:

Object.assign({},defaultConfigGenerator(16))

或更糟

Object.assign({},Object.assign(a,b))

我得到了类似

{...defaultConfigGenerator(16),...16}
的结果,因为我匹配了 16 的后代节点及其祖先。我不知道如何使用元变量来仅引用
arguments
节点的直接子节点,而不是它们的后代节点。

我尝试按照 https://dev.to/herrington_darkholme/find-patch-a-novel-function-programming-like-code-rewrite-scheme-3964 中的示例进行操作,我们可以将元变量与多个 AST 节点并转换它们中的每一个。

示例展示了如何进行此转换

// from
import {a, b, c} from './barrel';
// to
import a from './barrel/a';
import b from './barrel/b';
import c from './barrel/c';

使用这个 ast-grep 规则:

# Example barrel import rewrite rule
rule:
  pattern: import {$$$IDENTS} from './barrel'
rewriters:
- id: rewrite-identifer
  rule:
    pattern: $IDENT
    kind: identifier
  fix: import $IDENT from './barrel/$IDENT'
transform:
  IMPORTS:
    rewrite:
      rewriters: [rewrite-identifer]
      source: $$$IDENTS
      joinBy: "\n"
fix: $IMPORTS

因此,在多次阅读此示例后,我尝试应用相同的原则来使用元变量来匹配

Object.assign
的参数并转换它们。

# First part of my rule
rule:
  pattern: Object.assign($FIRST_ARG_OBJ_LITERAL,$$$REMAINING_ARGS)
  has:
    kind: object
    stopBy: end
    pattern: $FIRST_ARG_OBJ_LITERAL

到目前为止,这匹配了正确的节点,甚至正确地强制第一个参数必须是对象文字。代码的其余部分都是关于尝试正确修改匹配的节点。在这里:

# Second part of my rule
rewriters:
- id: remove_empty_obj
  rule:
    kind: object
    not:
      has: 
        pattern: $ANYCONTENTS
  fix:
    ""

- id: add_ellipsis
  rule:
    pattern: $ANYCONTENTS
  fix:
    ...$ANYCONTENTS,

- id: argument_spreader
  rule:
    pattern: $ANY
    inside:
      kind: arguments
      inside:
        kind: call_expression
        pattern: Object.assign($$$REMAINING_ARGS)
  fix:
    "...$ANY"


transform:
  OPTIONAL_FIRST_OBJ:
    rewrite:
      rewriters: [remove_empty_obj,add_ellipsis]
      source: $FIRST_ARG_OBJ_LITERAL
  SPREAD_REMAINDERS:
    rewrite:
      rewriters: [argument_spreader]
      source: $$$REMAINING_ARGS
      joinBy: ","
  

fix:
  "{$OPTIONAL_FIRST_OBJ$SPREAD_REMAINDERS}"

remove_empty_object
add_ellipsis
重写器只是帮助处理第一个参数。我的问题是输入到
$$$REMAINING_ARGS
重写器中的
argument_spreader
。我预计
$$$REMAINING_ARGS
仅包含第一个参数的同级节点,但这些节点的后代也包含在内。

我可以通过坚持只匹配

arguments
甚至
arguments
调用中的
Object.assign
的节点来减少一些多余的节点,但如果我们有嵌套的情况,它不会处理这种情况
Object.assign({},Object.assign(a,b))
$$$REMAINING_ARGS
内的
argument_spreader
变量与规则的其余部分之间似乎没有任何联系。否则,我认为它会起作用。

您可以在 ast-grep 游乐场 尝试使用此规则来测试您的任何想法。

eslint abstract-syntax-tree static-analysis automated-refactoring ast-grep
1个回答
0
投票

ast-grep 的 rewriter 是一个新的实验性功能!感谢您的尝试,如果您无法弄清楚,这是很自然的。让我们分解一下这个问题,看看如何解决。

首先,让我们详细阐述一下我们的目标。如果 JavaScript 有一个使用对象字面量作为第一个参数的

Object.assign
调用,我们希望将其更改为对象扩展语法。

下面的模式可以实现这个目标。

rule:
  pattern: Object.assign({$$$FIELDS}, $$$REMAINING_ARGS)

上面的模式利用了

{$$$FIELDS}
是一个可以被树看护者轻松解析的表达式。它仅匹配对象文字,内部字段由元变量
$FIELDS
捕获。


现在让我们尝试添加

add_ellipsis

rewriters:
- id: add_ellipsis
  rule:
    pattern: $ANYCONTENTS
  fix:
    ...$ANYCONTENTS,

然后将其应用到

$$$REMAINING_ARGS

transform:
  SPREAD_REMAINDERS:
    rewrite:
      rewriters: [argument_spreader]
      source: $$$REMAINING_ARGS

我希望 $$$REMAINING_ARGS 仅包含第一个参数的同级节点,但这些节点的后代也包含在内。

重写器将应用于所有后代。但是,重写器应该只匹配树中的第一个节点并停止重写其子节点。存在一个错误,即后代节点被重复重写。并且已修复此处


现在让我们将这些部件组装在一起!最简单的方法就是散布所有对象。

fix: '{...{$$$FIELDS}, $SPREAD_REMAINDERS}'

参观游乐场


© www.soinside.com 2019 - 2024. All rights reserved.