|  | // Copyright 2017 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package astutil_test | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "go/ast" | 
|  | "go/format" | 
|  | "go/parser" | 
|  | "go/token" | 
|  | "testing" | 
|  |  | 
|  | "golang.org/x/tools/go/ast/astutil" | 
|  | ) | 
|  |  | 
|  | var rewriteTests = [...]struct { | 
|  | name       string | 
|  | orig, want string | 
|  | pre, post  astutil.ApplyFunc | 
|  | }{ | 
|  | {name: "nop", orig: "package p\n", want: "package p\n"}, | 
|  |  | 
|  | {name: "replace", | 
|  | orig: `package p | 
|  |  | 
|  | var x int | 
|  | `, | 
|  | want: `package p | 
|  |  | 
|  | var t T | 
|  | `, | 
|  | post: func(c *astutil.Cursor) bool { | 
|  | if _, ok := c.Node().(*ast.ValueSpec); ok { | 
|  | c.Replace(valspec("t", "T")) | 
|  | return false | 
|  | } | 
|  | return true | 
|  | }, | 
|  | }, | 
|  |  | 
|  | {name: "set doc strings", | 
|  | orig: `package p | 
|  |  | 
|  | const z = 0 | 
|  |  | 
|  | type T struct{} | 
|  |  | 
|  | var x int | 
|  | `, | 
|  | want: `package p | 
|  | // a foo is a foo | 
|  | const z = 0 | 
|  | // a foo is a foo | 
|  | type T struct{} | 
|  | // a foo is a foo | 
|  | var x int | 
|  | `, | 
|  | post: func(c *astutil.Cursor) bool { | 
|  | if _, ok := c.Parent().(*ast.GenDecl); ok && c.Name() == "Doc" && c.Node() == nil { | 
|  | c.Replace(&ast.CommentGroup{List: []*ast.Comment{{Text: "// a foo is a foo"}}}) | 
|  | } | 
|  | return true | 
|  | }, | 
|  | }, | 
|  |  | 
|  | {name: "insert names", | 
|  | orig: `package p | 
|  |  | 
|  | const a = 1 | 
|  | `, | 
|  | want: `package p | 
|  |  | 
|  | const a, b, c = 1, 2, 3 | 
|  | `, | 
|  | pre: func(c *astutil.Cursor) bool { | 
|  | if _, ok := c.Parent().(*ast.ValueSpec); ok { | 
|  | switch c.Name() { | 
|  | case "Names": | 
|  | c.InsertAfter(ast.NewIdent("c")) | 
|  | c.InsertAfter(ast.NewIdent("b")) | 
|  | case "Values": | 
|  | c.InsertAfter(&ast.BasicLit{Kind: token.INT, Value: "3"}) | 
|  | c.InsertAfter(&ast.BasicLit{Kind: token.INT, Value: "2"}) | 
|  | } | 
|  | } | 
|  | return true | 
|  | }, | 
|  | }, | 
|  |  | 
|  | {name: "insert", | 
|  | orig: `package p | 
|  |  | 
|  | var ( | 
|  | x int | 
|  | y int | 
|  | ) | 
|  | `, | 
|  | want: `package p | 
|  |  | 
|  | var before1 int | 
|  | var before2 int | 
|  |  | 
|  | var ( | 
|  | x int | 
|  | y int | 
|  | ) | 
|  | var after2 int | 
|  | var after1 int | 
|  | `, | 
|  | pre: func(c *astutil.Cursor) bool { | 
|  | if _, ok := c.Node().(*ast.GenDecl); ok { | 
|  | c.InsertBefore(vardecl("before1", "int")) | 
|  | c.InsertAfter(vardecl("after1", "int")) | 
|  | c.InsertAfter(vardecl("after2", "int")) | 
|  | c.InsertBefore(vardecl("before2", "int")) | 
|  | } | 
|  | return true | 
|  | }, | 
|  | }, | 
|  |  | 
|  | {name: "delete", | 
|  | orig: `package p | 
|  |  | 
|  | var x int | 
|  | var y int | 
|  | var z int | 
|  | `, | 
|  | want: `package p | 
|  |  | 
|  | var y int | 
|  | var z int | 
|  | `, | 
|  | pre: func(c *astutil.Cursor) bool { | 
|  | n := c.Node() | 
|  | if d, ok := n.(*ast.GenDecl); ok && d.Specs[0].(*ast.ValueSpec).Names[0].Name == "x" { | 
|  | c.Delete() | 
|  | } | 
|  | return true | 
|  | }, | 
|  | }, | 
|  |  | 
|  | {name: "insertafter-delete", | 
|  | orig: `package p | 
|  |  | 
|  | var x int | 
|  | var y int | 
|  | var z int | 
|  | `, | 
|  | want: `package p | 
|  |  | 
|  | var x1 int | 
|  |  | 
|  | var y int | 
|  | var z int | 
|  | `, | 
|  | pre: func(c *astutil.Cursor) bool { | 
|  | n := c.Node() | 
|  | if d, ok := n.(*ast.GenDecl); ok && d.Specs[0].(*ast.ValueSpec).Names[0].Name == "x" { | 
|  | c.InsertAfter(vardecl("x1", "int")) | 
|  | c.Delete() | 
|  | } | 
|  | return true | 
|  | }, | 
|  | }, | 
|  |  | 
|  | {name: "delete-insertafter", | 
|  | orig: `package p | 
|  |  | 
|  | var x int | 
|  | var y int | 
|  | var z int | 
|  | `, | 
|  | want: `package p | 
|  |  | 
|  | var y int | 
|  | var x1 int | 
|  | var z int | 
|  | `, | 
|  | pre: func(c *astutil.Cursor) bool { | 
|  | n := c.Node() | 
|  | if d, ok := n.(*ast.GenDecl); ok && d.Specs[0].(*ast.ValueSpec).Names[0].Name == "x" { | 
|  | c.Delete() | 
|  | // The cursor is now effectively atop the 'var y int' node. | 
|  | c.InsertAfter(vardecl("x1", "int")) | 
|  | } | 
|  | return true | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | func valspec(name, typ string) *ast.ValueSpec { | 
|  | return &ast.ValueSpec{Names: []*ast.Ident{ast.NewIdent(name)}, | 
|  | Type: ast.NewIdent(typ), | 
|  | } | 
|  | } | 
|  |  | 
|  | func vardecl(name, typ string) *ast.GenDecl { | 
|  | return &ast.GenDecl{ | 
|  | Tok:   token.VAR, | 
|  | Specs: []ast.Spec{valspec(name, typ)}, | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestRewrite(t *testing.T) { | 
|  | t.Run("*", func(t *testing.T) { | 
|  | for _, test := range rewriteTests { | 
|  | test := test | 
|  | t.Run(test.name, func(t *testing.T) { | 
|  | t.Parallel() | 
|  | fset := token.NewFileSet() | 
|  | f, err := parser.ParseFile(fset, test.name, test.orig, parser.ParseComments) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | n := astutil.Apply(f, test.pre, test.post) | 
|  | var buf bytes.Buffer | 
|  | if err := format.Node(&buf, fset, n); err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | got := buf.String() | 
|  | if got != test.want { | 
|  | t.Errorf("got:\n\n%s\nwant:\n\n%s\n", got, test.want) | 
|  | } | 
|  | }) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | var sink ast.Node | 
|  |  | 
|  | func BenchmarkRewrite(b *testing.B) { | 
|  | for _, test := range rewriteTests { | 
|  | b.Run(test.name, func(b *testing.B) { | 
|  | for i := 0; i < b.N; i++ { | 
|  | b.StopTimer() | 
|  | fset := token.NewFileSet() | 
|  | f, err := parser.ParseFile(fset, test.name, test.orig, parser.ParseComments) | 
|  | if err != nil { | 
|  | b.Fatal(err) | 
|  | } | 
|  | b.StartTimer() | 
|  | sink = astutil.Apply(f, test.pre, test.post) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } |