├── .github └── FUNDING.yml ├── .travis.yml ├── astrewrite_example_test.go ├── LICENSE ├── README.md └── astrewrite.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["fatih"] 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.7.x 4 | - tip 5 | 6 | -------------------------------------------------------------------------------- /astrewrite_example_test.go: -------------------------------------------------------------------------------- 1 | package astrewrite 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/ast" 7 | "go/parser" 8 | "go/printer" 9 | "go/token" 10 | ) 11 | 12 | func ExampleWalk() { 13 | src := `package main 14 | 15 | type Foo struct{}` 16 | 17 | fset := token.NewFileSet() 18 | file, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | rewriteFunc := func(n ast.Node) (ast.Node, bool) { 24 | x, ok := n.(*ast.TypeSpec) 25 | if !ok { 26 | return n, true 27 | } 28 | 29 | // change struct type name to "Bar" 30 | x.Name.Name = "Bar" 31 | return x, true 32 | } 33 | 34 | rewritten := Walk(file, rewriteFunc) 35 | 36 | var buf bytes.Buffer 37 | printer.Fprint(&buf, fset, rewritten) 38 | fmt.Println(buf.String()) 39 | // Output: 40 | // package main 41 | // 42 | // type Bar struct{} 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fatih Arslan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is archived and not maintained. There is a better implementation 2 | and solution in the 3 | [`golang.org/x/tools/go/ast/astutil`](https://pkg.go.dev/golang.org/x/tools/go/ast/astutil?tab=doc#Apply) 4 | package that you can use. 5 | 6 | # astrewrite [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/astrewrite) [![Build Status](http://img.shields.io/travis/fatih/astrewrite.svg?style=flat-square)](https://travis-ci.org/fatih/astrewrite) 7 | 8 | astrewrite provides a `Walk()` function, similar to [ast.Inspect()](https://godoc.org/go/ast#Inspect) from the 9 | [ast](https://godoc.org/go/ast) package. The only difference is that the passed walk function can also 10 | return a node, which is used to rewrite the parent node. This provides an easy 11 | way to rewrite a given ast.Node while walking the AST. 12 | 13 | # Example 14 | 15 | ```go 16 | package main 17 | 18 | import ( 19 | "bytes" 20 | "fmt" 21 | "go/ast" 22 | "go/parser" 23 | "go/printer" 24 | "go/token" 25 | 26 | "github.com/fatih/astrewrite" 27 | ) 28 | 29 | func main() { 30 | src := `package main 31 | 32 | type Foo struct{}` 33 | 34 | fset := token.NewFileSet() 35 | file, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | rewriteFunc := func(n ast.Node) (ast.Node, bool) { 41 | x, ok := n.(*ast.TypeSpec) 42 | if !ok { 43 | return n, true 44 | } 45 | 46 | // change struct type name to "Bar" 47 | x.Name.Name = "Bar" 48 | return x, true 49 | } 50 | 51 | rewritten := astrewrite.Walk(file, rewriteFunc) 52 | 53 | var buf bytes.Buffer 54 | printer.Fprint(&buf, fset, rewritten) 55 | fmt.Println(buf.String()) 56 | // Output: 57 | // package main 58 | // 59 | // type Bar struct{} 60 | } 61 | ``` 62 | 63 | -------------------------------------------------------------------------------- /astrewrite.go: -------------------------------------------------------------------------------- 1 | package astrewrite 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | ) 7 | 8 | // WalkFunc describes a function to be called for each node during a Walk. The 9 | // returned node can be used to rewrite the AST. Walking stops if the returned 10 | // bool is false. 11 | type WalkFunc func(ast.Node) (ast.Node, bool) 12 | 13 | // Walk traverses an AST in depth-first order: It starts by calling 14 | // fn(node); node must not be nil. It returns the rewritten node. If fn returns 15 | // true, Walk invokes fn recursively for each of the non-nil children of node, 16 | // followed by a call of fn(nil). The returned node of fn can be used to 17 | // rewrite the passed node to fn. Panics if the returned type is not the same 18 | // type as the original one. 19 | func Walk(node ast.Node, fn WalkFunc) ast.Node { 20 | rewritten, ok := fn(node) 21 | if !ok { 22 | return rewritten 23 | } 24 | 25 | // walk children 26 | // (the order of the cases matches the order 27 | // of the corresponding node types in ast.go) 28 | switch n := node.(type) { 29 | // Comments and fields 30 | case *ast.Comment: 31 | // nothing to do 32 | 33 | case *ast.CommentGroup: 34 | for i, c := range n.List { 35 | n.List[i] = Walk(c, fn).(*ast.Comment) 36 | } 37 | 38 | case *ast.Field: 39 | if n.Doc != nil { 40 | n.Doc = Walk(n.Doc, fn).(*ast.CommentGroup) 41 | } 42 | walkIdentList(n.Names, fn) 43 | n.Type = Walk(n.Type, fn).(ast.Expr) 44 | if n.Tag != nil { 45 | n.Tag = Walk(n.Tag, fn).(*ast.BasicLit) 46 | } 47 | if n.Comment != nil { 48 | n.Comment = Walk(n.Comment, fn).(*ast.CommentGroup) 49 | } 50 | 51 | case *ast.FieldList: 52 | for i, f := range n.List { 53 | n.List[i] = Walk(f, fn).(*ast.Field) 54 | } 55 | 56 | // Expressions 57 | case *ast.BadExpr, *ast.Ident, *ast.BasicLit: 58 | // nothing to do 59 | 60 | case *ast.Ellipsis: 61 | if n.Elt != nil { 62 | n.Elt = Walk(n.Elt, fn).(ast.Expr) 63 | } 64 | 65 | case *ast.FuncLit: 66 | n.Type = Walk(n.Type, fn).(*ast.FuncType) 67 | n.Body = Walk(n.Body, fn).(*ast.BlockStmt) 68 | 69 | case *ast.CompositeLit: 70 | if n.Type != nil { 71 | n.Type = Walk(n.Type, fn).(ast.Expr) 72 | } 73 | walkExprList(n.Elts, fn) 74 | 75 | case *ast.ParenExpr: 76 | n.X = Walk(n.X, fn).(ast.Expr) 77 | 78 | case *ast.SelectorExpr: 79 | n.X = Walk(n.X, fn).(ast.Expr) 80 | n.Sel = Walk(n.Sel, fn).(*ast.Ident) 81 | 82 | case *ast.IndexExpr: 83 | n.X = Walk(n.X, fn).(ast.Expr) 84 | n.Index = Walk(n.Index, fn).(ast.Expr) 85 | 86 | case *ast.SliceExpr: 87 | n.X = Walk(n.X, fn).(ast.Expr) 88 | if n.Low != nil { 89 | n.Low = Walk(n.Low, fn).(ast.Expr) 90 | } 91 | if n.High != nil { 92 | n.High = Walk(n.High, fn).(ast.Expr) 93 | } 94 | if n.Max != nil { 95 | n.Max = Walk(n.Max, fn).(ast.Expr) 96 | } 97 | 98 | case *ast.TypeAssertExpr: 99 | n.X = Walk(n.X, fn).(ast.Expr) 100 | if n.Type != nil { 101 | n.Type = Walk(n.Type, fn).(ast.Expr) 102 | } 103 | 104 | case *ast.CallExpr: 105 | n.Fun = Walk(n.Fun, fn).(ast.Expr) 106 | walkExprList(n.Args, fn) 107 | 108 | case *ast.StarExpr: 109 | n.X = Walk(n.X, fn).(ast.Expr) 110 | 111 | case *ast.UnaryExpr: 112 | n.X = Walk(n.X, fn).(ast.Expr) 113 | 114 | case *ast.BinaryExpr: 115 | n.X = Walk(n.X, fn).(ast.Expr) 116 | n.Y = Walk(n.Y, fn).(ast.Expr) 117 | 118 | case *ast.KeyValueExpr: 119 | n.Key = Walk(n.Key, fn).(ast.Expr) 120 | n.Value = Walk(n.Value, fn).(ast.Expr) 121 | 122 | // Types 123 | case *ast.ArrayType: 124 | if n.Len != nil { 125 | n.Len = Walk(n.Len, fn).(ast.Expr) 126 | } 127 | n.Elt = Walk(n.Elt, fn).(ast.Expr) 128 | 129 | case *ast.StructType: 130 | n.Fields = Walk(n.Fields, fn).(*ast.FieldList) 131 | 132 | case *ast.FuncType: 133 | if n.Params != nil { 134 | n.Params = Walk(n.Params, fn).(*ast.FieldList) 135 | } 136 | if n.Results != nil { 137 | n.Results = Walk(n.Results, fn).(*ast.FieldList) 138 | } 139 | 140 | case *ast.InterfaceType: 141 | n.Methods = Walk(n.Methods, fn).(*ast.FieldList) 142 | 143 | case *ast.MapType: 144 | n.Key = Walk(n.Key, fn).(ast.Expr) 145 | n.Value = Walk(n.Value, fn).(ast.Expr) 146 | 147 | case *ast.ChanType: 148 | n.Value = Walk(n.Value, fn).(ast.Expr) 149 | 150 | // Statements 151 | case *ast.BadStmt: 152 | // nothing to do 153 | 154 | case *ast.DeclStmt: 155 | n.Decl = Walk(n.Decl, fn).(ast.Decl) 156 | 157 | case *ast.EmptyStmt: 158 | // nothing to do 159 | 160 | case *ast.LabeledStmt: 161 | n.Label = Walk(n.Label, fn).(*ast.Ident) 162 | n.Stmt = Walk(n.Stmt, fn).(ast.Stmt) 163 | 164 | case *ast.ExprStmt: 165 | n.X = Walk(n.X, fn).(ast.Expr) 166 | 167 | case *ast.SendStmt: 168 | n.Chan = Walk(n.Chan, fn).(ast.Expr) 169 | n.Value = Walk(n.Value, fn).(ast.Expr) 170 | 171 | case *ast.IncDecStmt: 172 | n.X = Walk(n.X, fn).(ast.Expr) 173 | 174 | case *ast.AssignStmt: 175 | walkExprList(n.Lhs, fn) 176 | walkExprList(n.Rhs, fn) 177 | 178 | case *ast.GoStmt: 179 | n.Call = Walk(n.Call, fn).(*ast.CallExpr) 180 | 181 | case *ast.DeferStmt: 182 | n.Call = Walk(n.Call, fn).(*ast.CallExpr) 183 | 184 | case *ast.ReturnStmt: 185 | walkExprList(n.Results, fn) 186 | 187 | case *ast.BranchStmt: 188 | if n.Label != nil { 189 | n.Label = Walk(n.Label, fn).(*ast.Ident) 190 | } 191 | 192 | case *ast.BlockStmt: 193 | walkStmtList(n.List, fn) 194 | 195 | case *ast.IfStmt: 196 | if n.Init != nil { 197 | n.Init = Walk(n.Init, fn).(ast.Stmt) 198 | } 199 | n.Cond = Walk(n.Cond, fn).(ast.Expr) 200 | n.Body = Walk(n.Body, fn).(*ast.BlockStmt) 201 | if n.Else != nil { 202 | n.Else = Walk(n.Else, fn).(ast.Stmt) 203 | } 204 | 205 | case *ast.CaseClause: 206 | walkExprList(n.List, fn) 207 | walkStmtList(n.Body, fn) 208 | 209 | case *ast.SwitchStmt: 210 | if n.Init != nil { 211 | n.Init = Walk(n.Init, fn).(ast.Stmt) 212 | } 213 | if n.Tag != nil { 214 | n.Tag = Walk(n.Tag, fn).(ast.Expr) 215 | } 216 | n.Body = Walk(n.Body, fn).(*ast.BlockStmt) 217 | 218 | case *ast.TypeSwitchStmt: 219 | if n.Init != nil { 220 | n.Init = Walk(n.Init, fn).(ast.Stmt) 221 | } 222 | n.Assign = Walk(n.Assign, fn).(ast.Stmt) 223 | n.Body = Walk(n.Body, fn).(*ast.BlockStmt) 224 | 225 | case *ast.CommClause: 226 | if n.Comm != nil { 227 | n.Comm = Walk(n.Comm, fn).(ast.Stmt) 228 | } 229 | walkStmtList(n.Body, fn) 230 | 231 | case *ast.SelectStmt: 232 | n.Body = Walk(n.Body, fn).(*ast.BlockStmt) 233 | 234 | case *ast.ForStmt: 235 | if n.Init != nil { 236 | n.Init = Walk(n.Init, fn).(ast.Stmt) 237 | } 238 | if n.Cond != nil { 239 | n.Cond = Walk(n.Cond, fn).(ast.Expr) 240 | } 241 | if n.Post != nil { 242 | n.Post = Walk(n.Post, fn).(ast.Stmt) 243 | } 244 | n.Body = Walk(n.Body, fn).(*ast.BlockStmt) 245 | 246 | case *ast.RangeStmt: 247 | if n.Key != nil { 248 | n.Key = Walk(n.Key, fn).(ast.Expr) 249 | } 250 | if n.Value != nil { 251 | n.Value = Walk(n.Value, fn).(ast.Expr) 252 | } 253 | n.X = Walk(n.X, fn).(ast.Expr) 254 | n.Body = Walk(n.Body, fn).(*ast.BlockStmt) 255 | 256 | // Declarations 257 | case *ast.ImportSpec: 258 | if n.Doc != nil { 259 | n.Doc = Walk(n.Doc, fn).(*ast.CommentGroup) 260 | } 261 | if n.Name != nil { 262 | n.Name = Walk(n.Name, fn).(*ast.Ident) 263 | } 264 | n.Path = Walk(n.Path, fn).(*ast.BasicLit) 265 | if n.Comment != nil { 266 | n.Comment = Walk(n.Comment, fn).(*ast.CommentGroup) 267 | } 268 | 269 | case *ast.ValueSpec: 270 | if n.Doc != nil { 271 | n.Doc = Walk(n.Doc, fn).(*ast.CommentGroup) 272 | } 273 | walkIdentList(n.Names, fn) 274 | if n.Type != nil { 275 | n.Type = Walk(n.Type, fn).(ast.Expr) 276 | } 277 | walkExprList(n.Values, fn) 278 | if n.Comment != nil { 279 | n.Comment = Walk(n.Comment, fn).(*ast.CommentGroup) 280 | } 281 | 282 | case *ast.TypeSpec: 283 | if n.Doc != nil { 284 | n.Doc = Walk(n.Doc, fn).(*ast.CommentGroup) 285 | } 286 | Walk(n.Name, fn) 287 | Walk(n.Type, fn) 288 | if n.Comment != nil { 289 | n.Comment = Walk(n.Comment, fn).(*ast.CommentGroup) 290 | } 291 | 292 | case *ast.BadDecl: 293 | // nothing to do 294 | 295 | case *ast.GenDecl: 296 | if n.Doc != nil { 297 | n.Doc = Walk(n.Doc, fn).(*ast.CommentGroup) 298 | } 299 | for i, s := range n.Specs { 300 | n.Specs[i] = Walk(s, fn).(ast.Spec) 301 | } 302 | 303 | case *ast.FuncDecl: 304 | if n.Doc != nil { 305 | n.Doc = Walk(n.Doc, fn).(*ast.CommentGroup) 306 | } 307 | if n.Recv != nil { 308 | n.Recv = Walk(n.Recv, fn).(*ast.FieldList) 309 | } 310 | n.Name = Walk(n.Name, fn).(*ast.Ident) 311 | n.Type = Walk(n.Type, fn).(*ast.FuncType) 312 | if n.Body != nil { 313 | n.Body = Walk(n.Body, fn).(*ast.BlockStmt) 314 | } 315 | 316 | // Files and packages 317 | case *ast.File: 318 | if n.Doc != nil { 319 | n.Doc = Walk(n.Doc, fn).(*ast.CommentGroup) 320 | } 321 | n.Name = Walk(n.Name, fn).(*ast.Ident) 322 | walkDeclList(n.Decls, fn) 323 | // don't walk n.Comments - they have been 324 | // visited already through the individual 325 | // nodes 326 | 327 | case *ast.Package: 328 | for i, f := range n.Files { 329 | n.Files[i] = Walk(f, fn).(*ast.File) 330 | } 331 | 332 | default: 333 | panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n)) 334 | } 335 | 336 | fn(nil) 337 | return rewritten 338 | } 339 | 340 | func walkIdentList(list []*ast.Ident, fn WalkFunc) { 341 | for i, x := range list { 342 | list[i] = Walk(x, fn).(*ast.Ident) 343 | } 344 | } 345 | 346 | func walkExprList(list []ast.Expr, fn WalkFunc) { 347 | for i, x := range list { 348 | list[i] = Walk(x, fn).(ast.Expr) 349 | } 350 | } 351 | 352 | func walkStmtList(list []ast.Stmt, fn WalkFunc) { 353 | for i, x := range list { 354 | list[i] = Walk(x, fn).(ast.Stmt) 355 | } 356 | } 357 | 358 | func walkDeclList(list []ast.Decl, fn WalkFunc) { 359 | for i, x := range list { 360 | list[i] = Walk(x, fn).(ast.Decl) 361 | } 362 | } 363 | --------------------------------------------------------------------------------