https://github.com/uber-go/gopatch

用于 Go 的重构和代码转换工具。

gopatch 是匹配和转换 Go 代码的工具。它的目的是帮助重构和重新安装。

内容表

介绍

gopatch 的操作方式与 Unix 工具类似:如果将修补程序文件和另一个文件作为输入,它将补丁中指定的更改应用于所提供的文件。patch

  1. .-------. .-------.
  2. /_| |. /_| |.
  3. | ||. +---------+ | ||.
  4. | .go |||>-->| gopatch |>-->| .go |||
  5. | ||| +---------+ | |||
  6. '--------'|| ^ '--------'||
  7. '--------'| | '--------'|
  8. '--------' | '--------'
  9. .-------. |
  10. /_| | |
  11. | +----'
  12. | .patch |
  13. | |
  14. '--------'

具体区别在于,与纯文本转换不同,它可以更智能,因为它理解 Go 语法。patch

开始

安装

安装具有以下命令的抓地程序。

  1. go install github.com/uber-go/gopatch@latest

您的第一个补丁

编写您的第一个修补程序。

  1. $ cat > ~/s1028.patch
  2. @@
  3. @@
  4. -import "errors"
  5. -errors.New(fmt.Sprintf(...))
  6. +fmt.Errorf(...)

此修补程序是静态检查S1028的修复程序。它搜索fmt的使用。冲刺错误。新的,并通过替换它们来简化它们。错误。

例如

  1. return errors.New(fmt.Sprintf("invalid port: %v", err))
  2. // becomes
  3. return fmt.Errorf("invalid port: %v", err)

应用修补程序

要将补丁应用到 Go 项目的目录中。cd

  1. $ cd ~/go/src/example.com/myproject

运行项目,为先前编写的补丁提供带有标志的修补程序。gopatch``-p

  1. $ gopatch -p ~/s1028.patch ./...

这将在项目中的所有 Go 代码上应用修补程序。

通过运行检查代码中是否有此问题实例。git diff

后续步骤

要学习如何编写自己的补丁,请转到补丁部分。要深入到补丁中,请查看深度修补程序

要尝试其他示例补丁,请查看示例部分。

用法

要使用 gopatch 命令行工具,请提供以下参数。

  1. gopatch [options] pattern ...

其中模式指定一个或多个 Go 文件,或包含 Go 文件的目录。对于目录,他们及其后代中的所有 Go 代码都将通过 gopatch 进行考虑。

选项

戈帕奇支持以下命令行选项。

  • -p file,--patch=file
    路径到指定转换的修补程序文件。阅读更多有关补丁文件格式的补丁。
    多次提供此标志以按顺序应用多个修补程序。

    1. $ gopatch -p foo.patch -p bar.patch path/to/my/project
  • 如果省略此标志,则预计在 stdin 上会出现修补程序。

    1. $ gopatch path/to/my/project << EOF
    2. @@
    3. @@
    4. -foo
    5. +bar
    6. EOF

补丁

修补程序文件是指定如何转换代码的 gopatch 的输入。每个修补程序文件包含一个或多个修补程序。本节提供编写补丁的介绍:请深入查看修补程序,了解更详细的解释。

每个修补程序都指定了代码转换。这些格式格式像统一差异:应删除带有指定匹配代码的行,以及预缀的行,并指定应添加新代码。-``+

考虑以下修补程序。

  1. @@
  2. @@
  3. -foo
  4. +bar

它规定,我们要搜索标识符的引用,并替换为引用。(暂时忽略行。我们将涵盖以下内容。foo``bar``@@

此修补程序的更选择性版本将搜索其称为具有特定参数的函数的用途。foo

  1. @@
  2. @@
  3. -foo(42)
  4. +bar(42)

这将搜索调用作为一个函数与指定的参数,并仅替换那些与。foo``bar

戈帕奇理解 Go 语法, 所以上面相当于以下内容。

  1. @@
  2. @@
  3. -foo(
  4. +bar(
  5. 42,
  6. )

可变性

搜索硬编码的确切参数是有限的。我们应该能够概括我们的补丁。

以前忽略的补丁部分称为可变量部分。这就是我们指定补丁的可变性的位置。@@

可变性将匹配任何代码,稍后将复制。把它们想象像我们匹配的代码要填充的孔。例如

  1. @@
  2. var x expression
  3. @@
  4. # rest of the patch

这规定,应匹配任何 Go 表达式并记录其匹配,以便以后重用。x

什么是 Go 表达式? 表达式通常指具有价值的代码。您可以将这些参数传递给函数。其中包括 ,等。x``foo()``user.Name 请查看附录中的标识符与表达式与语句部分,了解更多。

因此,以下补丁将搜索调用与一个单一的论点—-任何论点—-并以相同的参数来代替它们。foo``bar

  1. @@
  2. var x expression
  3. @@
  4. -foo(x)
  5. +bar(x)
输入 输出
foo(42) bar(42)
foo(answer) bar(answer)
foo(getAnswer()) bar(getAnswer())

可变性具有整个匹配值,因此我们可以在它们周围添加代码,而不会破坏任何内容。

  1. @@
  2. var x expression
  3. @@
  4. -foo(x)
  5. +bar(x + 3, true)
输入 输出
foo(42) bar(42 + 3, true)
foo(answer) bar(answer + 3, true)
foo(getAnswer()) bar(getAnswer() + 3, true)

有关可变性方面的更多,请参阅深度修补程序/可变性

语句

哥帕奇补丁不仅限于转换基本表达式。您还可以转换语句。

什么是 Go 语句? 陈述是做事的指示,没有价值。它们不能作为参数传递到其他函数。这些包括分配()、如果语句()、可变声明()等。foo := bar()``if foo { bar() }``var foo Bar 请查看附录中的标识符与表达式与语句部分,了解更多。

例如,考虑以下修补程序。

  1. @@
  2. var f expression
  3. var err identifier
  4. @@
  5. -err = f
  6. -if err != nil {
  7. +if err := f; err != nil {
  8. return err
  9. }

补丁声明两个可变性:

  • f:这表示可能返回的操作error
  • err:这表示变量的名称error

补丁将在返回之前立即搜索分配给错误变量的代码,并将分配内联到语句中。这有效地将变量的范围缩小到仅声明。if``if

输入 输出
  1. err = foo(bar, baz)
  2. if err != nil {
  3. return err
  4. }

|

  1. if err := foo(bar, baz); err != nil {
  2. return err
  3. }

|
|

  1. err = comment.Submit(ctx)
  2. if err != nil {
  3. return err
  4. }

|

  1. if err := comment.Submit(ctx); err != nil {
  2. return err
  3. }

|

有关转换语句的更多,请参阅“深度修补程序”/“语句“。

元音省略

匹配单个参数仍然过于选择性,我们可能希望匹配更广泛的标准。

为此,gopatch 通过在许多地方添加代码来支持代码的删除。例如...

  1. @@
  2. @@
  3. -foo(...)
  4. +bar(...)

上述修补程序查找所有对函数的调用,并以对函数的调用替换它们,无论它们的参数数量如何。foo``bar

输入 输出
foo(42) bar(42)
foo(42, true, 1) bar(42, true, 1)
foo(getAnswer(), x(y())) bar(getAnswer(), x(y()))

“语句”返回补丁,我们可以改为编写以下修补程序。

  1. @@
  2. var f expression
  3. var err identifier
  4. @@
  5. -err = f
  6. -if err != nil {
  7. +if err := f; err != nil {
  8. return ..., err
  9. }

此修补程序几乎与以前完全相同,但语句已更改为。这将允许修补程序即使在返回多个值的函数上运行。return``return ..., err

输入 输出
  1. err = foo()
  2. if err != nil {
  3. return false, err
  4. }

|

  1. if err := foo(); err != nil {
  2. return false, err
  3. }

|

有关去位的更多,请参阅深度修补程序/埃利西翁

例子

本节列出了您可以在代码中尝试的各种示例修补程序。请注意,其中一些补丁并不完美,可能有误报。

项目状态

该项目目前处于测试状态。它工作,但计划了重要的功能,可能会导致修补程序格式的中断更改。

目标

gopatch 的目标是成为一个通用的电动工具,您可以使用它来代替简单的搜索和替换。

gopatch 将尝试在转换中为您完成 80% 的工作,但它不能保证 100% 的正确性或完整性。部分原因在于,Gopatch 必须能够对尚未编译的代码进行操作,而重构器中通常会出现这种情况。将来,我们可能会添加需要可编译代码的功能,但我们计划始终支持部分有效的 Go 代码的转换。

已知问题

除了上述已强调的已知问题外,今天使用 gopatch 还有其他一些问题。

  • 很安静,所以没有进步的迹象。#7
  • 无效补丁文件的错误消息很难破译。#8
  • 匹配各部分之间的差异并不总是以理想的方式工作。我们可能会考虑用不同的名称提取语法替换匿名的 elision 来解决这个问题。#9-``+``...
  • 使用 elision 时,gopatch 在给定范围内的第一个实例后停止更换,这通常不是您想要的。#10
  • 通过 gopatch 生成的输出格式并不总是完美的。

即将 到来

除了解决我们已经提到的各种限制和问题外,我们还计划提供一些功能。

  • 上下文匹配:匹配上下文(如函数声明),然后在任何深度上反复在函数内运行转换。#11
  • 附带更改:在一个补丁中匹配和捕获值,并在同一文件中使用以下修补程序中的值。
  • 可变约束:指定对可变性的限制,例如匹配字符串或其他可变量的一部分。
  • 条件消除:只有在指定条件也属实时,才能匹配的即发。

类似项目

  • rf是一个具有自定义DSL的重构工具
  • gofmt重写规则支持对表达式进行简单转换
  • 例如支持基于基本示例的重构
  • 科奇内尔是 C 的工具, 戈帕奇从中获得灵感
  • 森格雷普是一种跨语言语义搜索工具
  • 康比是一种语言不可知的搜索和转换工具

学分

戈帕奇深受科奇内尔的启发。