|
| 1 | +--- |
| 2 | +name: golang-refactor-tools |
| 3 | +description: > |
| 4 | + Go code refactoring with automated tools: gofmt -r, gopatch, and rsc.io/rf. |
| 5 | + Use this skill when performing mechanical rewrites across a codebase — renaming |
| 6 | + identifiers, migrating API calls, moving types between packages, rewriting |
| 7 | + expressions, or replacing deprecated patterns. Triggers when the task involves |
| 8 | + bulk code transformation, API migration, structural rewrite, or the user mentions |
| 9 | + gofmt -r, rf, gopatch, coccinelle-style patching, or large-scale rename/move in Go. |
| 10 | +--- |
| 11 | + |
| 12 | +# Go Refactoring Tools |
| 13 | + |
| 14 | +## Decision table |
| 15 | + |
| 16 | +| Situation | Tool | |
| 17 | +|---|---| |
| 18 | +| Swap one expression for another | `gofmt -r` | |
| 19 | +| Structural multi-line or multi-statement rewrite | `gopatch` | |
| 20 | +| Type-sensitive expression rewrite | `rf ex` | |
| 21 | +| Rename / move identifier, type, function, package | `rf mv` | |
| 22 | +| Delete declarations | `rf rm` | |
| 23 | + |
| 24 | +> **Workflow**: dry-run first (`gopatch -d` / `rf -diff`), review the diff, then apply. |
| 25 | +> After any tool run: `goimports -w . && go build ./... && go test ./...`. Commit before bulk rewrites. |
| 26 | +
|
| 27 | +--- |
| 28 | + |
| 29 | +## `gofmt -r` — quickest, ships with Go |
| 30 | + |
| 31 | +Syntax-aware but **not type-aware**. Pattern variables must be **single lowercase letters**; everything else is matched literally. `-w .` recurses naturally — `./...` is a Go package pattern and will error. |
| 32 | + |
| 33 | +```bash |
| 34 | +gofmt -r 'a.OldMethod(b) -> a.NewMethod(b)' -w . |
| 35 | +gofmt -r 'ioutil.ReadAll(a) -> io.ReadAll(a)' -w . # ioutil/io are literal; a is wildcard |
| 36 | +``` |
| 37 | + |
| 38 | +After running: fix imports with `goimports -w .` (gofmt -r never touches imports). |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## `gopatch` — structural patch files |
| 43 | + |
| 44 | +Operates on syntax, understands statements and imports. Can work on partially-invalid code. |
| 45 | + |
| 46 | +```bash |
| 47 | +go install github.com/uber-go/gopatch@latest |
| 48 | +gopatch -p my.patch ./... # rewrite in place |
| 49 | +gopatch -d -p my.patch ./... # dry-run: print diff |
| 50 | +gopatch ./... <<'EOF' # read patch from stdin (omit -p) |
| 51 | +... |
| 52 | +EOF |
| 53 | +``` |
| 54 | + |
| 55 | +### Patch anatomy |
| 56 | + |
| 57 | +```patch |
| 58 | +# Description (shown in -d output) |
| 59 | +@@ |
| 60 | +var x expression # matches any Go expression |
| 61 | +var n identifier # matches any single identifier |
| 62 | +@@ |
| 63 | +-old code |
| 64 | ++new code |
| 65 | +``` |
| 66 | + |
| 67 | +**Metavariable types**: `expression` (calls, field accesses, literals…), `identifier` (single name). |
| 68 | +**Undeclared identifiers** are matched literally — `a`, `b`, `c` in the body match only args named exactly `a`, `b`, `c`. Use `...` (elision) or declare `var a expression` explicitly. |
| 69 | +**`...`**: matches zero or more statements/arguments. Multiple `@@…@@` blocks = multiple patches run in order. |
| 70 | + |
| 71 | +### Examples |
| 72 | + |
| 73 | +**Replace call + fix import in one patch:** |
| 74 | +```patch |
| 75 | +@@ |
| 76 | +@@ |
| 77 | +-import "io/ioutil" |
| 78 | ++import "io" |
| 79 | + |
| 80 | +-ioutil.ReadAll(...) |
| 81 | ++io.ReadAll(...) |
| 82 | +``` |
| 83 | + |
| 84 | +**Remove boilerplate with elision:** |
| 85 | +```patch |
| 86 | +# Delete redundant gomock.Controller.Finish() |
| 87 | +@@ |
| 88 | +var ctrl, gomock identifier |
| 89 | +var t expression |
| 90 | +@@ |
| 91 | + import gomock "github.com/golang/mock/gomock" |
| 92 | + |
| 93 | + ctrl := gomock.NewController(t) |
| 94 | + ... |
| 95 | +-defer ctrl.Finish() |
| 96 | +``` |
| 97 | + |
| 98 | +**Replace expression with sub-expression wildcard:** |
| 99 | +```patch |
| 100 | +@@ |
| 101 | +var x expression |
| 102 | +@@ |
| 103 | +-time.Now().Sub(x) |
| 104 | ++time.Since(x) |
| 105 | +``` |
| 106 | + |
| 107 | +--- |
| 108 | + |
| 109 | +## `rsc.io/rf` — rename, move, expression rewrite |
| 110 | + |
| 111 | +> ⚠ **Experimental**: rf's README says *"incredibly rough and likely to be buggy and change incompatibly."* Prefer `gopatch` for structural work; use `rf` when type-awareness is required (renames, moves, typed expression rewrites). |
| 112 | +
|
| 113 | +Takes a **single script argument** and operates on the module in the current directory — no `./...`. |
| 114 | + |
| 115 | +```bash |
| 116 | +go install rsc.io/rf@latest |
| 117 | +rf -diff 'mv Foo Bar' # show diff without writing |
| 118 | +``` |
| 119 | + |
| 120 | +### `mv` — rename or move |
| 121 | + |
| 122 | +```bash |
| 123 | +rf 'mv OldType NewType' |
| 124 | +rf 'mv pkg.OldFunc pkg.NewFunc' |
| 125 | +rf 'mv mypkg.Foo otherpkg.Foo' |
| 126 | +rf 'mv MyStruct.OldField MyStruct.NewField' |
| 127 | +``` |
| 128 | + |
| 129 | +### `ex` — type-aware expression rewrite |
| 130 | + |
| 131 | +Variables inside `{ }` are **typed** metavariables — only values of the declared type match, so there are no false positives on same-named identifiers from other packages. If a type doesn't precisely match the AST node, the rule is silently skipped. Multiple rules go in one block. |
| 132 | + |
| 133 | +```bash |
| 134 | +rf 'ex { |
| 135 | + var s string |
| 136 | + var r io.Reader |
| 137 | + fmt.Sprintf("%s", s) -> s |
| 138 | + ioutil.ReadAll(r) -> io.ReadAll(r) |
| 139 | +}' |
| 140 | +``` |
| 141 | + |
| 142 | +`avoid` prevents rewriting inside named method bodies: |
| 143 | +```bash |
| 144 | +rf 'ex { |
| 145 | + var m myMap |
| 146 | + var k, v int |
| 147 | + avoid myMap.Get |
| 148 | + m[k] -> m.Get(k) |
| 149 | +}' |
| 150 | +``` |
| 151 | + |
| 152 | +### `rm` / `add` |
| 153 | + |
| 154 | +```bash |
| 155 | +rf 'rm OldFunc' |
| 156 | +rf 'rm MyType.DeprecatedField' |
| 157 | +rf 'add MyStruct.NewField int `json:"new_field"`' |
| 158 | +``` |
| 159 | + |
| 160 | +--- |
| 161 | + |
| 162 | +## Common recipes |
| 163 | + |
| 164 | +### Rename a public type |
| 165 | + |
| 166 | +```bash |
| 167 | +rf 'mv pkg.OldName pkg.NewName' |
| 168 | +``` |
0 commit comments