Giter Site home page Giter Site logo

gopatch's Introduction

gopatch Go codecov

gopatch is a tool to match and transform Go code. It is meant to aid in refactoring and restyling.

Table of contents

Introduction

gopatch operates like the Unix patch tool: given a patch file and another file as input, it applies the changes specified in the patch to the provided file.

 .-------.                      .-------.
/_|      |.                    /_|      |.
|        ||.    +---------+    |        ||.
|   .go  |||>-->| gopatch |>-->|   .go  |||
|        |||    +---------+    |        |||
'--------'||      ^            '--------'||
 '--------'|      |             '--------'|
  '--------'      |              '--------'
     .-------.    |
    /_|      |    |
    |        +----'
    | .patch |
    |        |
    '--------'

What specifically differentiates it from patch is that unlike plain text transformations, it can be smarter because it understands Go syntax.

Getting started

Installation

Download a pre-built binary of gopatch from the Releases page or by running the following command in your terminal and place it on your $PATH.

VERSION=0.4.0
URL="https://github.com/uber-go/gopatch/releases/download/v$VERSION/gopatch_${VERSION}_$(uname -s)_$(uname -m).tar.gz"
curl -L "$URL" | tar xzv gopatch

Alternatively, if you have Go installed, build it from source and install it with the following command.

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

Note: If you're using Go < 1.16, use go get github.com/uber-go/gopatch@latest instead.

Your first patch

Write your first patch.

$ cat > ~/s1028.patch
# Replace redundant fmt.Sprintf with fmt.Errorf
@@
@@
-import "errors"

-errors.New(fmt.Sprintf(...))
+fmt.Errorf(...)

This patch is a fix for staticcheck S1028. It searches for uses of fmt.Sprintf with errors.New, and simplifies them by replacing them with fmt.Errorf.

For example,

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

Apply the patch

  • cd to your Go project's directory.
$ cd ~/go/src/example.com/myproject

Run gopatch on the project, supplying the previously written patch with the -p flag.

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

This will apply the patch on all Go code in your project.

Check if there were any instances of this issue in your code by running git diff.

  • Instead, cd to your Go project's directory.

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

    Run gopatch on the project, supplying the previously written patch with the -p flag along with '-d' flag.

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

    This will turn on diff mode and will write the diff to stdout instead of modifying all the Go code in your project. To provide more context on what the patch does, if there were description comments in the patch, they will also get displayed at the top. To learn more about description comments jump to section here

    For example if we applied patch ~/s1028 to our testfile error.go

    $ gopatch -d -p ~/s1028.patch ./testdata/test_files/diff_example/

    Output would be :

    gopatch/testdata/test_files/diff_example/error.go:Replace redundant fmt.Sprintf with fmt.Errorf
    --- gopatch/testdata/test_files/diff_example/error.go
    +++ gopatch/testdata/test_files/diff_example/error.go
    @@ -7,7 +7,7 @@
    
    func foo() error {
            err := errors.New("test")
    -       return errors.New(fmt.Sprintf("error: %v", err))
    +       return fmt.Errorf("error: %v", err)
    }
    
     func main() {
    
    

    Note: Only the description comments of patches that actually apply are displayed.

Next steps

To learn how to write your own patches, move on to the Patches section. To dive deeper into patches, check out Patches in depth.

To experiment with other sample patches, check out the Examples section.

Usage

To use the gopatch command line tool, provide the following arguments.

gopatch [options] pattern ...

Where pattern specifies one or more Go files, or directories containing Go files. For directories, all Go code inside them and their descendants will be considered by gopatch.

Options

gopatch supports the following command line options.

  • -p file, --patch=file

    Path to a patch file specifying a transformation. Read more about the patch file format in Patches.

    Provide this flag multiple times to apply multiple patches in-order.

    $ gopatch -p foo.patch -p bar.patch path/to/my/project

    If this flag is omitted, a patch is expected on stdin.

    $ gopatch path/to/my/project << EOF
    @@
    @@
    -foo
    +bar
    EOF
  • -d, --diff

    Flag to turn on diff mode. Provide this flag to write the diff to stdout instead of modifying the file and display applied patches' description comments if they exist. Use in conjunction with -p to provide patch file.

    Only need to apply the flag once to turn on diff mode

    $ gopatch -d -p foo.patch -p bar.patch path/to/my/project

    If this flag is omitted, normal patching occurs which modifies the file instead.

  • --print-only

    Flag to turn on print-only mode. Provide this flag to write the changed code to stdout instead of modifying the file and display applied patches' description comments to stderr if they exist.

    $ gopatch --print-only -p foo.patch -p bar.patch path/to/my/project
  • --skip-import-processing

    Flag to turn on skip-import-processing mode. Provide this flag to disable import formatting for imports that were not part of the patch changes.

    $ gopatch --skip-import-processing -p foo.patch -p bar.patch path/to/my/project
  • --skip-generated

    Flag to turn on skip-generated code mode. Provide this flag to skip running the tool on generated code. A file is considered containing generated code if it has @generated or ^// Code generated .* DO NOT EDIT\.$ in the comment header.

    $ gopatch --skip-generated -p foo.patch -p bar.patch path/to/my/project

Patches

Patch files are the input to gopatch that specify how to transform code. Each patch file contains one or more patches. This section provides an introduction to writing patches; look at Patches in depth for a more detailed explanation.

Each patch specifies a code transformation. These are formatted like unified diffs: lines prefixed with - specify matching code should be deleted, and lines prefixed with + specify that new code should be added.

Consider the following patch.

@@
@@
-foo
+bar

It specifies that we want to search for references to the identifier foo and replace them with references to bar. (Ignore the lines with @@ for now. We will cover those below.)

A more selective version of this patch will search for uses of foo where it is called as a function with specific arguments.

@@
@@
-foo(42)
+bar(42)

This will search for invocations of foo as a function with the specified argument, and replace only those with bar.

gopatch understands Go syntax, so the above is equivalent to the following.

@@
@@
-foo(
+bar(
  42,
 )

Metavariables

Searching for hard-coded exact parameters is limited. We should be able to generalize our patches.

The previously ignored @@ section of patches is referred to as the metavariable section. That is where we specify metavariables for the patch.

Metavariables will match any code, to be reproduced later. Think of them like holes to be filled by the code we match. For example,

@@
var x expression
@@
# rest of the patch

This specifies that x should match any Go expression and record its match for later reuse.

What is a Go expression?

Expressions usually refer to code that has value. You can pass these as arguments to functions. These include x, foo(), user.Name, etc.

Check the Identifiers vs expressions vs statements section of the appendix for more.

So the following patch will search for invocations of foo with a single argument---any argument---and replace them with invocations of bar with the same argument.

@@
var x expression
@@
-foo(x)
+bar(x)
Input Output
foo(42) bar(42)
foo(answer) bar(answer)
foo(getAnswer()) bar(getAnswer())

Metavariables hold the entire matched value, so we can add code around them without risk of breaking anything.

@@
var x expression
@@
-foo(x)
+bar(x + 3, true)
Input Output
foo(42) bar(42 + 3, true)
foo(answer) bar(answer + 3, true)
foo(getAnswer()) bar(getAnswer() + 3, true)

For more on metavariables see Patches in depth/Metavariables.

Statements

gopatch patches are not limited to transforming basic expressions. You can also transform statements.

What is a Go statements?

Statements are instructions to do things, and do not have value. They cannot be passed as parameters to other functions. These include assignments (foo := bar()), if statements (if foo { bar() }), variable declarations (var foo Bar), and so on.

Check the Identifiers vs expressions vs statements section of the appendix for more.

For example, consider the following patch.

@@
var f expression
var err identifier
@@
-err = f
-if err != nil {
+if err := f; err != nil {
   return err
 }

The patch declares two metavariables:

  • f: This represents an operation that possibly returns an error
  • err: This represents the name of the error variable

The patch will search for code that assigns to an error variable immediately before returning it, and inlines the assignment into the if statement. This effectively reduces the scope of the variable to just the if statement.

InputOutput
err = foo(bar, baz)
if err != nil {
   return err
}
if err := foo(bar, baz); err != nil {
   return err
}
err = comment.Submit(ctx)
if err != nil {
  return err
}
if err := comment.Submit(ctx); err != nil {
  return err
}

For more on transforming statements, see Patches In Depth/Statements.

Elision

Matching a single argument is still too selective and we may want to match a wider criteria.

For this, gopatch supports elision of code by adding ... in many places. For example,

@@
@@
-foo(...)
+bar(...)

The patch above looks for all calls to the function foo and replaces them with calls to the function bar, regardless of the number of arguments they have.

Input Output
foo(42) bar(42)
foo(42, true, 1) bar(42, true, 1)
foo(getAnswer(), x(y())) bar(getAnswer(), x(y()))

Going back to the patch from Statements, we can instead write the following patch.

@@
var f expression
var err identifier
@@
-err = f
-if err != nil {
+if err := f; err != nil {
   return ..., err
 }

This patch is almost exactly the same as before except the return statement was changed to return ..., err. This will allow the patch to operate even on functions that return multiple values.

InputOutput
err = foo()
if err != nil {
   return false, err
}
if err := foo(); err != nil {
   return false, err
}

For more on elision, see Patches in depth/Elision.

Comments

Patches come with comments to give more context about what they do.

Comments are prefixed by '#'

For example:

# Replace time.Now().Sub(x) with time.Since(x)
@@
# var x is in the metavariable section 
var x identifier
@@

-time.Now().Sub(x)
+time.Since(x)
# We replace time.Now().Sub(x)
# with time.Since(x)

Description comments

Description comments are comments that appear directly above a patch's first @@ line. gopatch will record these descriptions and display them to users with use of the --diff or --print-only flags.

For example,

# Replace time.Now().Sub(x) with time.Since(x)
@@
# Not a description comment
var x identifier
@@

-time.Now().Sub(x)
+time.Since(x)
# Not a description comment
# Not a description comment

Patch files with multiple patches can have a separate description for each patch.

# Replace redundant fmt.Sprintf with fmt.Errorf
@@
@@

-import "errors"
-errors.New(fmt.Sprintf(...))
+fmt.Errorf(...)

# Replace time.Now().Sub(x) with time.Since(x)
@@
var x identifier
@@

-time.Now().Sub(x)
+time.Since(x)
# Not a description comment

As these are messages that will be printed to users of the patch, we recommend the following best practices for description comments.

  • Keep them short and on a single-line
  • Use imperative mood ("replace X with Y", not "replaces X with Y")

Usage with --diff

When diff mode is turned on by the -d/--diff flag, gopatch will print description comments for patches that matched different files to stderr.

$ gopatch -d -p ~/s1028.patch testdata/test_files/diff_example/error.go
error.go:Replace redundant fmt.Sprintf with fmt.Errorf
--- error.go
+++ error.go
@@ -7,7 +7,7 @@

func foo() error {
        err := errors.New("test")
-       return errors.New(fmt.Sprintf("error: %v", err))
+       return fmt.Errorf("error: %v", err)
}

 func main() {

Note that gopatch will print only the description comments in diff mode. Other comments will be ignored.

Examples

This section lists various example patches you can try in your code. Note that some of these patches are not perfect and may have false positives.

Project status

The project is currently is in a beta state. It works but significant features are planned that may result in breaking changes to the patch format.

Goals

gopatch aims to be a generic power tool that you can use in lieu of simple search-and-replace.

gopatch will attempt to do 80% of the work for you in a transformation, but it cannot guarantee 100% correctness or completeness. Part of this is owing to the decision that gopatch must be able to operate on code that doesn't yet compile, which can often be the case in the middle of a refactor. We may add features in the future that require compilable code, but we plan to always support transformation of partially-valid Go code.

Known issues

Beyond the known issues highlighted above, there are a handful of other issues with using gopatch today.

  • It's very quiet, so there's no indication of progress. #7
  • Error messages for invalid patch files are hard to decipher. #8
  • Matching elisions between the - and + sections does not always work in a desirable way. We may consider replacing anonymous ... elision with a different named elision syntax to address this issue. #9
  • When elision is used, gopatch stops replacing after the first instance in the given scope which is often not what you want. #10
  • Formatting of output generated by gopatch isn't always perfect.

Upcoming

Besides addressing the various limitations and issues we've already mentioned, we have a number of features planned for gopatch.

  • Contextual matching: match context (like a function declaration), and then run a transformation inside the function body repeatedly, at any depth. #11
  • Collateral changes: Match and capture values in one patch, and use those in a following patch in the same file.
  • Metavariable constraints: Specify constraints on metavariables, e.g. matching a string, or part of another metavariable.
  • Condition elision: An elision should match only if a specified condition is also true.

Contributing

If you'd like to contribute to gopatch, you may find the following documents useful:

  • HACKING documents the architecture, code organization, and other information necessary to contribute to the project.
  • RELEASE documents the process for releasing a new version of gopatch.

Similar Projects

  • rf is a refactoring tool with a custom DSL
  • gofmt rewrite rules support simple transformations on expressions
  • eg supports basic example-based refactoring
  • Coccinelle is a tool for C from which gopatch takes inspiration heavily
  • Semgrep is a cross-language semantic search tool
  • Comby is a language-agnostic search and transformation tool

Credits

gopatch is heavily inspired by Coccinelle.

gopatch's People

Contributors

abhinav avatar alexandear avatar apty avatar breml avatar dependabot[bot] avatar glibsm avatar lverma14 avatar sashamelentyev avatar syriebianco avatar sywhang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gopatch's Issues

Elision in value declarations

We should support elision in value declarations. e.g.,

-var a, ..., c string
+var a, ..., c int
  var (
    ...
-   name = value
+   _name = value
    ...
  )

More accurate elision

Elision correlation between the - and + sections of a patch is very naive right now. It matches based on positions of the ellipses in the patch, so it can easily get things wrong.

We should consider a more sophisticated elision matching implementation; perhaps by diffing the - and + sections.

Alternatively, or in addition to that, we may want to support named elisions or equivalent for cases where even best-effort elision won't behave correctly.

Patch parsing error messages

The machinery for parsing patches is currently convoluted, with multiple temporary files created inside token.FileSets so that when failures do occur, we have no obvious correlation to positions in the patch.

We should simplify that by not introducing these temporary file sets, or mapping positions back to the original patch file. Go syntax error messages should specify exact positions in the patch files.

Metavariables for literals

We should support metavariables to match on literals. That is, to match on "foo" and 42 below.

x := "foo"
const y = 42

This will make it possible to write patches that detect, say, hard-coded strings and turn them into constants or optimize them (moving compilation of a hard-coded regex from a hot path to a top-level variable, for example).

Print diff instead of changing files

We should support a -d flag to print the diff of what would be changed without changing anything.

If the diff was non-empty, the program would exit with a non-zero exit code so that this could be used in scripts like gopatch -d -p foo.patch ./... will return a non-zero exit code and print a diff if foo.patch would change anything.

We'd have to decide how this plays with the -l flag proposed in #19.

Idempotent patching

I am using the following patch to convert unchecked Debug to wrapped by if logging.isDebugEnabled() .
I would like to make this idempotent so it can be repeatedly applied.

How do I do that?

@@
var x expression
@@
-logging.L(x).Debug(...)
+if logging.IsDebugEnabled(x) {
+    logging.L(x).Debug(...)
+}

Multiple top-level declarations

It's currently impossible to have a patch that introduces a new top-level declaration while modifying another one.

For example, given,

func foo() {
  re := regexp.MustCompile("foo")
  // ...
}

We cannot build a patch that would turn it into the following.

var re = regexp.MustCompile("foo")

func foo() {
  // ...
}

That's because we don't yet support patches with multiple top-level declarations in them so the following will fail to parse.

@@
var f, re identifier
var regex expression
@@
+var re = regexp.MustCompile(regex)

 func f() {
-  re := regexp.MustCompile(regex)
   ...
 }

See also #3, #4

Publish binaries

#26 pointed out that our installation instructions only work for Go 1.16+. We need to use go get for older versions.
Rather than having per-version build-from-source instructions, we should just ship binaries with each release.

Chattiness flags

gopatch is completely quiet right now. We should add flags for verbosity.

Initial ideas:

  • -v flag for verbose mode where we'll list whether we changed files or not
  • -l flag to list files that were changed only

Appending new variable declaration in a var(...) block

Hello!

I have a global var block as follows:

var (
	SystemSubsystem                   = "system"
	SystemLabelNames                  = []string{"hostname", "resource", "system_id"}
	SystemMemoryLabelNames            = []string{"hostname", "resource", "memory", "memory_id"}
// and so on
)

Let's say I want to insert a new variable SystemProcessorLabelNames after the SystemMemoryLabelNames

This doesn't work:

@@
@@
SystemMemoryLabelNames            = []string{"hostname", "resource", "memory", "memory_id"}
+SystemProcessorLabelNames         = []string{"hostname", "resource", "processor", "processor_id"}

And this doesn't work:

@@
@@
var (
...
SystemMemoryLabelNames            = []string{"hostname", "resource", "memory", "memory_id"}
+SystemProcessorLabelNames         = []string{"hostname", "resource", "processor", "processor_id"}
)

Although replacing works fine like that:

@@
@@
-[]string{"hostname", "resource", "memory", "memory_id"}
+[]string{"hostname", "resource", "processor", "processor_id"}

But I need to append a new line. Appending works fine inside functions, just like that:

@@
@@
a := 5
+b:=6

But inside var block it either fails or skips the file. Is this a bug or my mistake? Thanks.

imports: named import with metavariable should recognize unnamed import

When trying to match all imports of a path, a patch that specified named imports, even with metavariables will turn unnamed imports into named imports.

Input

Patch:

@@
var foo, x identifier
@@
-import foo "example.com/foo-go.git"
+import foo "example.com/foo.git"

 foo.x

Source:

package whatever

import "example.com/foo-go.git"

func foo() {
  foo.X()
}

Expectation

package whatever

import "example.com/foo-go.git"

func foo() {
  foo.X()
}

Reality

package whatever

import foo "example.com/foo-go.git"

func foo() {
  foo.X()
}

Problem

This is undesirable; the foo metavariable should remember that the import was unnamed and reproduce that. Alternatively, we should provide a different means of matching imports that allows addressing both named and unnamed cases.

Workaround

For now, a workaround is to reproduce the patch as-is twice, once without the named import, once with:

@@
var x identifier
@@
-import "example.com/foo-go.git"
+import "example.com/foo.git"

 foo.x

@@
var foo, x identifier
@@
-import foo "example.com/foo-go.git"
+import foo "example.com/foo.git"

 foo.x

Patches that change packages or imports only

gopatch requires exactly one entity after the package name (if any) and imports.
Patches that affect only imports are not supported.

This fails:

@@
@@
-import "example.com/foo-go"
+import "example.com/foo"

We can work around this by "using" foo in the patch without changing it.

@@
@@
-import "example.com/foo-go"
+import "example.com/foo"

 foo

We should not have this requirement, and instead support patches that don't affect code beyond the imports and packages.

This is likely related to #4 where we plan to add support for multiple entities in a single diff.

Contextual matching and transformations

Patches currently apply at matching "levels" in the AST.

For example, consider the following patch.

@@
@@
-func foo(...) {
+func foo(...) error {
    ...
    if err != nil {
-       log.Fatal(err)
+       return err
    }
    ...
 }

In addition to the issue with replacing only the first instance of the matching statement (described in #10), this has the problem that the statement is only matched at the "top level" of the function. It will not affect the following versions, for example:

func foo() {
    for x := range y {
        err := bar(x)
        if err != nil {
            log.Fatal(err)
        }
    }
}

We need a means of specifying that given some previously matched context, we want to apply a "sub transformation" to the entire tree inside that context.

I've been referring to this concept as contextual matching but there may be a better term.

Borrowing from Coccinelle, one possible syntax here is <..., ...>. For example,

@@
@@
-func foo(...) {
+func foo(...) error {
    <...
      if err != nil {
-       log.Fatal(err)
+       return err
      }
    ...>
 }

Which I'm roughly translating to "given that matched context, apply this match/transformation to everything inside it." The "match all" nature of this syntax might also help address #10.

Elision: Support "[...]" in function calls

[...] in function calls should match any and all generic function instantiations.

@@
@@
-foo[...](42)
+bar[...](42)

Should match:

foo(42)
foo[int](42)
foo[MyInt](42)

Elision: match 1 or more returned types

(Edit: the below is using 10bff91)

This patch

@@
@@
 func f() (...) {
- fmt.Println("")
+ fmt.Println("hello")
  ...
 }

will match against

package foo

func f() (string, error) {
  fmt.Println("")
  return "", nil
}

but not

package foo

func f() string {
  fmt.Println("")
  return ""
}

I thought the parentheses were forcing more than one return type, so I tried patching without them

@@
@@
 func f() ... {
- return ""
+ return "hello"
 }

however this is not a valid patch:

load patch "repro": parse: repro/0.minus:1:11: found unexpected "..." inside *ast.FieldList

This isn't a big deal -- I can repeat the patch for 1 return type and N return types.

Intuitively, I would have thought "(...)" already represents "zero to N" return types, although I could also be totally misunderstanding the syntax!

"Replace all" elision

Currently, patches with elisions only replace the first instance of the matched contents.

For example,

 f(..., 
-  "GET",
+  constants.Get,
  ...,
 )

This will only replace the first instance in a matching function call.

// Input
foo(a, b, "GET", c, "GET", d)

// Expected
foo(a, b, constants.Get, c, constants.Get, d)

// Actual
foo(a, b, constants.Get, c, "GET", d)

gopatch should either treat ..., foo, ... transformations as "replace all", or we should add support for contextual transformations sooner (#11).

-e flag for short patches

For simple, short patches, we should support a -e flag like sed with some automatic means of defining metavariables similar to gofmt -r.

Expose api for embed in user program.

All apis in gopatch is internal, user must invoke this by external command, pre-requirements is gopatch installed in user machine.

So expose api for embed is convenient

Changing imports removes comment at the top of a file

Given a Go source file with a comment at the top of the file (not a package comment):

// Copyright 2022 ...

package main

import (
	"cmd/internal/edit"
)

func main() {
	_ = edit.Buffer
}

and a patch to modify the imports:

@@
var randomIdentifier identifier
@@
-import "cmd/internal/edit"
+import "github.com/foo/bar/internal/edit"

edit.randomIdentifier

the comment at the top is removed by gopatch, resulting with the following file:

package main

import "github.com/foo/bar/internal/edit"

func main() {
	_ = edit.Buffer
}

Understand decl groupings

For gopatch, currently the following are different:

var foo = 42

var (
  foo = 42
)

gopatch should understand decl groupings for types, consts, vars, and funcs so that these are treated as equivalent.

Ideally it should also understand which of those groupings are unordered so the following are considered equivalent by a patch.

type (
  Foo struct{}
  Bar struct{}
)

type (
  Bar struct{}
  Foo struct{}
)

Way to handle duplicate imports of one package?

I have some files that import the same library package multiple times with different names:

import log "github.com/sirupsen/logrus"
import "github.com/sirupsen/logrus"

I was hoping to write a gopatch to clean these up but I can't seem to do it. This patch on its own

@@
var logrus identifier
@@
-import logrus "github.com/sirupsen/logrus"
+import logrusBar "github.com/sirupsen/logrus"
-logrus
+logrusBar

only matches one of the two imports and I can't seem to control which one it matches.

Elision and variadic parameters

Elision does not seem to match variadic parameters - which also use the ... notation.
Below is a simple repro.

Patch file (goal is to swap order of first 2 parameters only):

@@
var first expression
var second expression
@@
-bar(second, first,...)
+bar(first, second,...)

Input:

func foo() {
	bar("b", "a")
	bar("b", "a", "c")
	bar("b", "a", []string{"c"}...)
}

Output:

func foo() {
	bar("a", "b")
	bar("a", "b", "c")
        // Expected: bar("a", "b", []string{"c"}...)
	bar("b", "a", []string{"c"}...)
}

[Installation error] server response: not found

I tried to download in Go and got an error:

root@cubmaster01:~/workspces/tiktok/workspaces/poc# go install github.com/uber-go/gopatch@latest
go: downloading github.com/uber-go/gopatch v0.1.1
go: github.com/uber-go/gopatch@latest: github.com/uber-go/[email protected]: verifying module: github.com/uber-go/[email protected]: checking tree#15366661 against tree#15491733: reading https://goproxy.io/sumdb/sum.golang.org/tile/8/1/234: 404 Not Found
        server response: not found

I tried to install with CLI and got an error:

root@cubmaster01:~/workspces/tiktok/workspaces/poc# curl --proxy 127.0.0.1:7890 -L "$URL" | tar xzv gopatch
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:--  0:00:05 --:--:--     0
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to github.com:443 

gzip: stdin: unexpected end of file
tar: Child returned status 1
tar: Error is not recoverable: exiting now

about ENV

root@cubmaster01:~/workspces/tiktok/workspaces/poc# go version && uname -a 
go version go1.19.3 linux/amd64
Linux cubmaster01 5.4.0-137-generic #154-Ubuntu SMP Thu Jan 5 17:03:22 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

Additional parts:

I tried to install a brand new server and got an error:

[root@iZuf68xky083mr0yy6q37lZ ~]# go install github.com/uber-go/gopatch@latest
go: github.com/uber-go/gopatch@latest: module github.com/uber-go/gopatch: Get "https://proxy.golang.org/github.com/uber-go/gopatch/@v/list": dial tcp 142.251.43.17:443: i/o timeout

PATH:

[root@iZuf68xky083mr0yy6q37lZ ~]# go version && uname -a 
go version go1.18.9 linux/amd64
Linux iZuf68xky083mr0yy6q37lZ 3.10.0-1160.81.1.el7.x86_64 #1 SMP Fri Dec 16 17:29:43 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Support multiple transformations in one diff

Diffs in a patch can currently transform only one entity at a time.

That is,

@@
@@
 type Foo struct {
-  bar string
 }
 
@@
@@
 type Bar struct {
-  foo string
 }

We special case statements by allowing statement blocks, but nothing else. Where appropriate -- specially for type and func declarations, we should support multiple entities in the same patch.

@@
@@
 type Foo struct {
-  bar string
 }
 
 type Bar struct {
-  foo string
 }

Disable import group ordering

GoPatch currently has a call to Imports.Process with the option FormatOnly set to true.
This ends up modifying code that wasn't a part of the patch changes.
For example, in this example if we're applying a patch to replaceto.StrPtr(...) to ptr.String(...)

-- remove_some.in.go --
package main

import (
	"fmt"

	"conversion/to.git"
)

func foo(s string) *bool {
	fmt.Println("Hello World")
	if to.StrPtr(s) == nil {
		return to.BoolPtr(false)
	}

	if to.DecimalPtr(s) == nil {
		return to.BoolPtr(false)
	}

	return to.BoolPtr(true)
}

Our group import ordering also gets changed along with the actual patch change

-- remove_some.out.go --
package main

import (
	"fmt"

	"conversion/to.git"

	"go.uber.org/thriftrw/ptr" // gets added with a space
)

func foo(s string) *bool {
	fmt.Println("Hello World")
	if ptr.String(s) == nil {
		return ptr.Bool(false)
	}

	if to.DecimalPtr(s) == nil {
		return ptr.Bool(false)
	}

	return ptr.Bool(true)
}

Ref: Internal GO-1995

Patching ranged slice of struct literals

I'm trying to write a patch to transform a ranged slice of (anonymous) struct literals. For example, I want to turn this:

for _, foo := range []struct {
  bar string
  baz string
} {
  {
    bar: "bar",
    baz: "baz",
  },
  // ...
} {
  // do something with foo
}

into this:

foos := []struct {
  bar string
  baz string
} {
  {
    bar: "bar",
    baz: "baz",
  },
  // ...
}

for _, foo := range foos {
  // do something with foo
}

I've tried minor variants of the following patch:

@@
var x identifier
@@
- for _, x := range []struct{ ... } {
  ...
- }
+ foos := []struct { ... }
+ for _, x := range foos {
  ...
+ }

which returns the following error:

0.minus.augmented:4:6: missing ',' before newline in composite literal (and 2 more errors)

@abhinav suggested adding a trailing ; to the elision, but that resulted in the same error.

I'm not sure if this is technically the same thing as #4, but I think for this case it doesn't seem practical to do it in two stages (e.g., a second match on the trailing } { would be extremely ambiguous).

Is this type of patch possible currently?

Release v0.2.0

There have been a few improvements on main since the last release.
Would you please tag a new release?

Thanks!

Support multiple patches in a same file

As title, is it possible to have multiple patches in a same file, using txtar format, like:

-- p1.patch --
@@
var x expression
@@
-foo(x)
+bar(x)

-- p2.patch --
@@
var f expression
var err identifier
@@
-err = f
-if err != nil {
+if err := f; err != nil {
   return ..., err
 }

Add an option to disable automatic code formatting

Currently when you use the following patch:

@@
var x expression
@@
-x.Enabled(true)
+x.Enabled()

This automatically reformats the expression matched to x, and that could make a lot of unwanted changes. For example, if the original code looks like this:

a.Foo().Bar(bar)
    .Baz(baz)
    .Enabled(true)

The resulting code after applying the patch becomes a one-liner:

a.Foo().Bar(bar).Baz(baz).Enabled()

Adding an option to disable the automatic formatting of code when applying patches could be nice for cases like this. For example, with that option, the resulting code would look like this:

a.Foo().Bar(bar)
    .Baz(baz)
    .Enabled()

List matches

We should support a -l flag to list matches of a patch without changing anything.

This could be used to implement simple linters.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.