| // Copyright 2011 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 main |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/parser" |
| "go/token" |
| "os" |
| "path" |
| "reflect" |
| "strconv" |
| "strings" |
| ) |
| |
| type fix struct { |
| name string |
| date string // date that fix was introduced, in YYYY-MM-DD format |
| f func(*ast.File) bool |
| desc string |
| } |
| |
| // main runs sort.Sort(byName(fixes)) before printing list of fixes. |
| type byName []fix |
| |
| func (f byName) Len() int { return len(f) } |
| func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } |
| func (f byName) Less(i, j int) bool { return f[i].name < f[j].name } |
| |
| // main runs sort.Sort(byDate(fixes)) before applying fixes. |
| type byDate []fix |
| |
| func (f byDate) Len() int { return len(f) } |
| func (f byDate) Swap(i, j int) { f[i], f[j] = f[j], f[i] } |
| func (f byDate) Less(i, j int) bool { return f[i].date < f[j].date } |
| |
| var fixes []fix |
| |
| func register(f fix) { |
| fixes = append(fixes, f) |
| } |
| |
| // walk traverses the AST x, calling visit(y) for each node y in the tree but |
| // also with a pointer to each ast.Expr, ast.Stmt, and *ast.BlockStmt, |
| // in a bottom-up traversal. |
| func walk(x interface{}, visit func(interface{})) { |
| walkBeforeAfter(x, nop, visit) |
| } |
| |
| func nop(interface{}) {} |
| |
| // walkBeforeAfter is like walk but calls before(x) before traversing |
| // x's children and after(x) afterward. |
| func walkBeforeAfter(x interface{}, before, after func(interface{})) { |
| before(x) |
| |
| switch n := x.(type) { |
| default: |
| panic(fmt.Errorf("unexpected type %T in walkBeforeAfter", x)) |
| |
| case nil: |
| |
| // pointers to interfaces |
| case *ast.Decl: |
| walkBeforeAfter(*n, before, after) |
| case *ast.Expr: |
| walkBeforeAfter(*n, before, after) |
| case *ast.Spec: |
| walkBeforeAfter(*n, before, after) |
| case *ast.Stmt: |
| walkBeforeAfter(*n, before, after) |
| |
| // pointers to struct pointers |
| case **ast.BlockStmt: |
| walkBeforeAfter(*n, before, after) |
| case **ast.CallExpr: |
| walkBeforeAfter(*n, before, after) |
| case **ast.FieldList: |
| walkBeforeAfter(*n, before, after) |
| case **ast.FuncType: |
| walkBeforeAfter(*n, before, after) |
| case **ast.Ident: |
| walkBeforeAfter(*n, before, after) |
| case **ast.BasicLit: |
| walkBeforeAfter(*n, before, after) |
| |
| // pointers to slices |
| case *[]ast.Decl: |
| walkBeforeAfter(*n, before, after) |
| case *[]ast.Expr: |
| walkBeforeAfter(*n, before, after) |
| case *[]*ast.File: |
| walkBeforeAfter(*n, before, after) |
| case *[]*ast.Ident: |
| walkBeforeAfter(*n, before, after) |
| case *[]ast.Spec: |
| walkBeforeAfter(*n, before, after) |
| case *[]ast.Stmt: |
| walkBeforeAfter(*n, before, after) |
| |
| // These are ordered and grouped to match ../../go/ast/ast.go |
| case *ast.Field: |
| walkBeforeAfter(&n.Names, before, after) |
| walkBeforeAfter(&n.Type, before, after) |
| walkBeforeAfter(&n.Tag, before, after) |
| case *ast.FieldList: |
| for _, field := range n.List { |
| walkBeforeAfter(field, before, after) |
| } |
| case *ast.BadExpr: |
| case *ast.Ident: |
| case *ast.Ellipsis: |
| walkBeforeAfter(&n.Elt, before, after) |
| case *ast.BasicLit: |
| case *ast.FuncLit: |
| walkBeforeAfter(&n.Type, before, after) |
| walkBeforeAfter(&n.Body, before, after) |
| case *ast.CompositeLit: |
| walkBeforeAfter(&n.Type, before, after) |
| walkBeforeAfter(&n.Elts, before, after) |
| case *ast.ParenExpr: |
| walkBeforeAfter(&n.X, before, after) |
| case *ast.SelectorExpr: |
| walkBeforeAfter(&n.X, before, after) |
| case *ast.IndexExpr: |
| walkBeforeAfter(&n.X, before, after) |
| walkBeforeAfter(&n.Index, before, after) |
| case *ast.SliceExpr: |
| walkBeforeAfter(&n.X, before, after) |
| if n.Low != nil { |
| walkBeforeAfter(&n.Low, before, after) |
| } |
| if n.High != nil { |
| walkBeforeAfter(&n.High, before, after) |
| } |
| case *ast.TypeAssertExpr: |
| walkBeforeAfter(&n.X, before, after) |
| walkBeforeAfter(&n.Type, before, after) |
| case *ast.CallExpr: |
| walkBeforeAfter(&n.Fun, before, after) |
| walkBeforeAfter(&n.Args, before, after) |
| case *ast.StarExpr: |
| walkBeforeAfter(&n.X, before, after) |
| case *ast.UnaryExpr: |
| walkBeforeAfter(&n.X, before, after) |
| case *ast.BinaryExpr: |
| walkBeforeAfter(&n.X, before, after) |
| walkBeforeAfter(&n.Y, before, after) |
| case *ast.KeyValueExpr: |
| walkBeforeAfter(&n.Key, before, after) |
| walkBeforeAfter(&n.Value, before, after) |
| |
| case *ast.ArrayType: |
| walkBeforeAfter(&n.Len, before, after) |
| walkBeforeAfter(&n.Elt, before, after) |
| case *ast.StructType: |
| walkBeforeAfter(&n.Fields, before, after) |
| case *ast.FuncType: |
| walkBeforeAfter(&n.Params, before, after) |
| if n.Results != nil { |
| walkBeforeAfter(&n.Results, before, after) |
| } |
| case *ast.InterfaceType: |
| walkBeforeAfter(&n.Methods, before, after) |
| case *ast.MapType: |
| walkBeforeAfter(&n.Key, before, after) |
| walkBeforeAfter(&n.Value, before, after) |
| case *ast.ChanType: |
| walkBeforeAfter(&n.Value, before, after) |
| |
| case *ast.BadStmt: |
| case *ast.DeclStmt: |
| walkBeforeAfter(&n.Decl, before, after) |
| case *ast.EmptyStmt: |
| case *ast.LabeledStmt: |
| walkBeforeAfter(&n.Stmt, before, after) |
| case *ast.ExprStmt: |
| walkBeforeAfter(&n.X, before, after) |
| case *ast.SendStmt: |
| walkBeforeAfter(&n.Chan, before, after) |
| walkBeforeAfter(&n.Value, before, after) |
| case *ast.IncDecStmt: |
| walkBeforeAfter(&n.X, before, after) |
| case *ast.AssignStmt: |
| walkBeforeAfter(&n.Lhs, before, after) |
| walkBeforeAfter(&n.Rhs, before, after) |
| case *ast.GoStmt: |
| walkBeforeAfter(&n.Call, before, after) |
| case *ast.DeferStmt: |
| walkBeforeAfter(&n.Call, before, after) |
| case *ast.ReturnStmt: |
| walkBeforeAfter(&n.Results, before, after) |
| case *ast.BranchStmt: |
| case *ast.BlockStmt: |
| walkBeforeAfter(&n.List, before, after) |
| case *ast.IfStmt: |
| walkBeforeAfter(&n.Init, before, after) |
| walkBeforeAfter(&n.Cond, before, after) |
| walkBeforeAfter(&n.Body, before, after) |
| walkBeforeAfter(&n.Else, before, after) |
| case *ast.CaseClause: |
| walkBeforeAfter(&n.List, before, after) |
| walkBeforeAfter(&n.Body, before, after) |
| case *ast.SwitchStmt: |
| walkBeforeAfter(&n.Init, before, after) |
| walkBeforeAfter(&n.Tag, before, after) |
| walkBeforeAfter(&n.Body, before, after) |
| case *ast.TypeSwitchStmt: |
| walkBeforeAfter(&n.Init, before, after) |
| walkBeforeAfter(&n.Assign, before, after) |
| walkBeforeAfter(&n.Body, before, after) |
| case *ast.CommClause: |
| walkBeforeAfter(&n.Comm, before, after) |
| walkBeforeAfter(&n.Body, before, after) |
| case *ast.SelectStmt: |
| walkBeforeAfter(&n.Body, before, after) |
| case *ast.ForStmt: |
| walkBeforeAfter(&n.Init, before, after) |
| walkBeforeAfter(&n.Cond, before, after) |
| walkBeforeAfter(&n.Post, before, after) |
| walkBeforeAfter(&n.Body, before, after) |
| case *ast.RangeStmt: |
| walkBeforeAfter(&n.Key, before, after) |
| walkBeforeAfter(&n.Value, before, after) |
| walkBeforeAfter(&n.X, before, after) |
| walkBeforeAfter(&n.Body, before, after) |
| |
| case *ast.ImportSpec: |
| case *ast.ValueSpec: |
| walkBeforeAfter(&n.Type, before, after) |
| walkBeforeAfter(&n.Values, before, after) |
| walkBeforeAfter(&n.Names, before, after) |
| case *ast.TypeSpec: |
| walkBeforeAfter(&n.Type, before, after) |
| |
| case *ast.BadDecl: |
| case *ast.GenDecl: |
| walkBeforeAfter(&n.Specs, before, after) |
| case *ast.FuncDecl: |
| if n.Recv != nil { |
| walkBeforeAfter(&n.Recv, before, after) |
| } |
| walkBeforeAfter(&n.Type, before, after) |
| if n.Body != nil { |
| walkBeforeAfter(&n.Body, before, after) |
| } |
| |
| case *ast.File: |
| walkBeforeAfter(&n.Decls, before, after) |
| |
| case *ast.Package: |
| walkBeforeAfter(&n.Files, before, after) |
| |
| case []*ast.File: |
| for i := range n { |
| walkBeforeAfter(&n[i], before, after) |
| } |
| case []ast.Decl: |
| for i := range n { |
| walkBeforeAfter(&n[i], before, after) |
| } |
| case []ast.Expr: |
| for i := range n { |
| walkBeforeAfter(&n[i], before, after) |
| } |
| case []*ast.Ident: |
| for i := range n { |
| walkBeforeAfter(&n[i], before, after) |
| } |
| case []ast.Stmt: |
| for i := range n { |
| walkBeforeAfter(&n[i], before, after) |
| } |
| case []ast.Spec: |
| for i := range n { |
| walkBeforeAfter(&n[i], before, after) |
| } |
| } |
| after(x) |
| } |
| |
| // imports returns true if f imports path. |
| func imports(f *ast.File, path string) bool { |
| return importSpec(f, path) != nil |
| } |
| |
| // importSpec returns the import spec if f imports path, |
| // or nil otherwise. |
| func importSpec(f *ast.File, path string) *ast.ImportSpec { |
| for _, s := range f.Imports { |
| if importPath(s) == path { |
| return s |
| } |
| } |
| return nil |
| } |
| |
| // importPath returns the unquoted import path of s, |
| // or "" if the path is not properly quoted. |
| func importPath(s *ast.ImportSpec) string { |
| t, err := strconv.Unquote(s.Path.Value) |
| if err == nil { |
| return t |
| } |
| return "" |
| } |
| |
| // declImports reports whether gen contains an import of path. |
| func declImports(gen *ast.GenDecl, path string) bool { |
| if gen.Tok != token.IMPORT { |
| return false |
| } |
| for _, spec := range gen.Specs { |
| impspec := spec.(*ast.ImportSpec) |
| if importPath(impspec) == path { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // isPkgDot returns true if t is the expression "pkg.name" |
| // where pkg is an imported identifier. |
| func isPkgDot(t ast.Expr, pkg, name string) bool { |
| sel, ok := t.(*ast.SelectorExpr) |
| return ok && isTopName(sel.X, pkg) && sel.Sel.String() == name |
| } |
| |
| // isPtrPkgDot returns true if f is the expression "*pkg.name" |
| // where pkg is an imported identifier. |
| func isPtrPkgDot(t ast.Expr, pkg, name string) bool { |
| ptr, ok := t.(*ast.StarExpr) |
| return ok && isPkgDot(ptr.X, pkg, name) |
| } |
| |
| // isTopName returns true if n is a top-level unresolved identifier with the given name. |
| func isTopName(n ast.Expr, name string) bool { |
| id, ok := n.(*ast.Ident) |
| return ok && id.Name == name && id.Obj == nil |
| } |
| |
| // isName returns true if n is an identifier with the given name. |
| func isName(n ast.Expr, name string) bool { |
| id, ok := n.(*ast.Ident) |
| return ok && id.String() == name |
| } |
| |
| // isCall returns true if t is a call to pkg.name. |
| func isCall(t ast.Expr, pkg, name string) bool { |
| call, ok := t.(*ast.CallExpr) |
| return ok && isPkgDot(call.Fun, pkg, name) |
| } |
| |
| // If n is an *ast.Ident, isIdent returns it; otherwise isIdent returns nil. |
| func isIdent(n interface{}) *ast.Ident { |
| id, _ := n.(*ast.Ident) |
| return id |
| } |
| |
| // refersTo returns true if n is a reference to the same object as x. |
| func refersTo(n ast.Node, x *ast.Ident) bool { |
| id, ok := n.(*ast.Ident) |
| // The test of id.Name == x.Name handles top-level unresolved |
| // identifiers, which all have Obj == nil. |
| return ok && id.Obj == x.Obj && id.Name == x.Name |
| } |
| |
| // isBlank returns true if n is the blank identifier. |
| func isBlank(n ast.Expr) bool { |
| return isName(n, "_") |
| } |
| |
| // isEmptyString returns true if n is an empty string literal. |
| func isEmptyString(n ast.Expr) bool { |
| lit, ok := n.(*ast.BasicLit) |
| return ok && lit.Kind == token.STRING && len(lit.Value) == 2 |
| } |
| |
| func warn(pos token.Pos, msg string, args ...interface{}) { |
| if pos.IsValid() { |
| msg = "%s: " + msg |
| arg1 := []interface{}{fset.Position(pos).String()} |
| args = append(arg1, args...) |
| } |
| fmt.Fprintf(os.Stderr, msg+"\n", args...) |
| } |
| |
| // countUses returns the number of uses of the identifier x in scope. |
| func countUses(x *ast.Ident, scope []ast.Stmt) int { |
| count := 0 |
| ff := func(n interface{}) { |
| if n, ok := n.(ast.Node); ok && refersTo(n, x) { |
| count++ |
| } |
| } |
| for _, n := range scope { |
| walk(n, ff) |
| } |
| return count |
| } |
| |
| // rewriteUses replaces all uses of the identifier x and !x in scope |
| // with f(x.Pos()) and fnot(x.Pos()). |
| func rewriteUses(x *ast.Ident, f, fnot func(token.Pos) ast.Expr, scope []ast.Stmt) { |
| var lastF ast.Expr |
| ff := func(n interface{}) { |
| ptr, ok := n.(*ast.Expr) |
| if !ok { |
| return |
| } |
| nn := *ptr |
| |
| // The child node was just walked and possibly replaced. |
| // If it was replaced and this is a negation, replace with fnot(p). |
| not, ok := nn.(*ast.UnaryExpr) |
| if ok && not.Op == token.NOT && not.X == lastF { |
| *ptr = fnot(nn.Pos()) |
| return |
| } |
| if refersTo(nn, x) { |
| lastF = f(nn.Pos()) |
| *ptr = lastF |
| } |
| } |
| for _, n := range scope { |
| walk(n, ff) |
| } |
| } |
| |
| // assignsTo returns true if any of the code in scope assigns to or takes the address of x. |
| func assignsTo(x *ast.Ident, scope []ast.Stmt) bool { |
| assigned := false |
| ff := func(n interface{}) { |
| if assigned { |
| return |
| } |
| switch n := n.(type) { |
| case *ast.UnaryExpr: |
| // use of &x |
| if n.Op == token.AND && refersTo(n.X, x) { |
| assigned = true |
| return |
| } |
| case *ast.AssignStmt: |
| for _, l := range n.Lhs { |
| if refersTo(l, x) { |
| assigned = true |
| return |
| } |
| } |
| } |
| } |
| for _, n := range scope { |
| if assigned { |
| break |
| } |
| walk(n, ff) |
| } |
| return assigned |
| } |
| |
| // newPkgDot returns an ast.Expr referring to "pkg.name" at position pos. |
| func newPkgDot(pos token.Pos, pkg, name string) ast.Expr { |
| return &ast.SelectorExpr{ |
| X: &ast.Ident{ |
| NamePos: pos, |
| Name: pkg, |
| }, |
| Sel: &ast.Ident{ |
| NamePos: pos, |
| Name: name, |
| }, |
| } |
| } |
| |
| // renameTop renames all references to the top-level name old. |
| // It returns true if it makes any changes. |
| func renameTop(f *ast.File, old, new string) bool { |
| var fixed bool |
| |
| // Rename any conflicting imports |
| // (assuming package name is last element of path). |
| for _, s := range f.Imports { |
| if s.Name != nil { |
| if s.Name.Name == old { |
| s.Name.Name = new |
| fixed = true |
| } |
| } else { |
| _, thisName := path.Split(importPath(s)) |
| if thisName == old { |
| s.Name = ast.NewIdent(new) |
| fixed = true |
| } |
| } |
| } |
| |
| // Rename any top-level declarations. |
| for _, d := range f.Decls { |
| switch d := d.(type) { |
| case *ast.FuncDecl: |
| if d.Recv == nil && d.Name.Name == old { |
| d.Name.Name = new |
| d.Name.Obj.Name = new |
| fixed = true |
| } |
| case *ast.GenDecl: |
| for _, s := range d.Specs { |
| switch s := s.(type) { |
| case *ast.TypeSpec: |
| if s.Name.Name == old { |
| s.Name.Name = new |
| s.Name.Obj.Name = new |
| fixed = true |
| } |
| case *ast.ValueSpec: |
| for _, n := range s.Names { |
| if n.Name == old { |
| n.Name = new |
| n.Obj.Name = new |
| fixed = true |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // Rename top-level old to new, both unresolved names |
| // (probably defined in another file) and names that resolve |
| // to a declaration we renamed. |
| walk(f, func(n interface{}) { |
| id, ok := n.(*ast.Ident) |
| if ok && isTopName(id, old) { |
| id.Name = new |
| fixed = true |
| } |
| if ok && id.Obj != nil && id.Name == old && id.Obj.Name == new { |
| id.Name = id.Obj.Name |
| fixed = true |
| } |
| }) |
| |
| return fixed |
| } |
| |
| // matchLen returns the length of the longest prefix shared by x and y. |
| func matchLen(x, y string) int { |
| i := 0 |
| for i < len(x) && i < len(y) && x[i] == y[i] { |
| i++ |
| } |
| return i |
| } |
| |
| // addImport adds the import path to the file f, if absent. |
| func addImport(f *ast.File, ipath string) (added bool) { |
| if imports(f, ipath) { |
| return false |
| } |
| |
| // Determine name of import. |
| // Assume added imports follow convention of using last element. |
| _, name := path.Split(ipath) |
| |
| // Rename any conflicting top-level references from name to name_. |
| renameTop(f, name, name+"_") |
| |
| newImport := &ast.ImportSpec{ |
| Path: &ast.BasicLit{ |
| Kind: token.STRING, |
| Value: strconv.Quote(ipath), |
| }, |
| } |
| |
| // Find an import decl to add to. |
| var ( |
| bestMatch = -1 |
| lastImport = -1 |
| impDecl *ast.GenDecl |
| impIndex = -1 |
| ) |
| for i, decl := range f.Decls { |
| gen, ok := decl.(*ast.GenDecl) |
| if ok && gen.Tok == token.IMPORT { |
| lastImport = i |
| // Do not add to import "C", to avoid disrupting the |
| // association with its doc comment, breaking cgo. |
| if declImports(gen, "C") { |
| continue |
| } |
| |
| // Compute longest shared prefix with imports in this block. |
| for j, spec := range gen.Specs { |
| impspec := spec.(*ast.ImportSpec) |
| n := matchLen(importPath(impspec), ipath) |
| if n > bestMatch { |
| bestMatch = n |
| impDecl = gen |
| impIndex = j |
| } |
| } |
| } |
| } |
| |
| // If no import decl found, add one after the last import. |
| if impDecl == nil { |
| impDecl = &ast.GenDecl{ |
| Tok: token.IMPORT, |
| } |
| f.Decls = append(f.Decls, nil) |
| copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:]) |
| f.Decls[lastImport+1] = impDecl |
| } |
| |
| // Ensure the import decl has parentheses, if needed. |
| if len(impDecl.Specs) > 0 && !impDecl.Lparen.IsValid() { |
| impDecl.Lparen = impDecl.Pos() |
| } |
| |
| insertAt := impIndex + 1 |
| if insertAt == 0 { |
| insertAt = len(impDecl.Specs) |
| } |
| impDecl.Specs = append(impDecl.Specs, nil) |
| copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:]) |
| impDecl.Specs[insertAt] = newImport |
| if insertAt > 0 { |
| // Assign same position as the previous import, |
| // so that the sorter sees it as being in the same block. |
| prev := impDecl.Specs[insertAt-1] |
| newImport.Path.ValuePos = prev.Pos() |
| newImport.EndPos = prev.Pos() |
| } |
| |
| f.Imports = append(f.Imports, newImport) |
| return true |
| } |
| |
| // deleteImport deletes the import path from the file f, if present. |
| func deleteImport(f *ast.File, path string) (deleted bool) { |
| oldImport := importSpec(f, path) |
| |
| // Find the import node that imports path, if any. |
| for i, decl := range f.Decls { |
| gen, ok := decl.(*ast.GenDecl) |
| if !ok || gen.Tok != token.IMPORT { |
| continue |
| } |
| for j, spec := range gen.Specs { |
| impspec := spec.(*ast.ImportSpec) |
| if oldImport != impspec { |
| continue |
| } |
| |
| // We found an import spec that imports path. |
| // Delete it. |
| deleted = true |
| copy(gen.Specs[j:], gen.Specs[j+1:]) |
| gen.Specs = gen.Specs[:len(gen.Specs)-1] |
| |
| // If this was the last import spec in this decl, |
| // delete the decl, too. |
| if len(gen.Specs) == 0 { |
| copy(f.Decls[i:], f.Decls[i+1:]) |
| f.Decls = f.Decls[:len(f.Decls)-1] |
| } else if len(gen.Specs) == 1 { |
| gen.Lparen = token.NoPos // drop parens |
| } |
| if j > 0 { |
| // We deleted an entry but now there will be |
| // a blank line-sized hole where the import was. |
| // Close the hole by making the previous |
| // import appear to "end" where this one did. |
| gen.Specs[j-1].(*ast.ImportSpec).EndPos = impspec.End() |
| } |
| break |
| } |
| } |
| |
| // Delete it from f.Imports. |
| for i, imp := range f.Imports { |
| if imp == oldImport { |
| copy(f.Imports[i:], f.Imports[i+1:]) |
| f.Imports = f.Imports[:len(f.Imports)-1] |
| break |
| } |
| } |
| |
| return |
| } |
| |
| // rewriteImport rewrites any import of path oldPath to path newPath. |
| func rewriteImport(f *ast.File, oldPath, newPath string) (rewrote bool) { |
| for _, imp := range f.Imports { |
| if importPath(imp) == oldPath { |
| rewrote = true |
| // record old End, because the default is to compute |
| // it using the length of imp.Path.Value. |
| imp.EndPos = imp.End() |
| imp.Path.Value = strconv.Quote(newPath) |
| } |
| } |
| return |
| } |
| |
| func usesImport(f *ast.File, path string) (used bool) { |
| spec := importSpec(f, path) |
| if spec == nil { |
| return |
| } |
| |
| name := spec.Name.String() |
| switch name { |
| case "<nil>": |
| // If the package name is not explicitly specified, |
| // make an educated guess. This is not guaranteed to be correct. |
| lastSlash := strings.LastIndex(path, "/") |
| if lastSlash == -1 { |
| name = path |
| } else { |
| name = path[lastSlash+1:] |
| } |
| case "_", ".": |
| // Not sure if this import is used - err on the side of caution. |
| return true |
| } |
| |
| walk(f, func(n interface{}) { |
| sel, ok := n.(*ast.SelectorExpr) |
| if ok && isTopName(sel.X, name) { |
| used = true |
| } |
| }) |
| |
| return |
| } |
| |
| func expr(s string) ast.Expr { |
| x, err := parser.ParseExpr(s) |
| if err != nil { |
| panic("parsing " + s + ": " + err.Error()) |
| } |
| // Remove position information to avoid spurious newlines. |
| killPos(reflect.ValueOf(x)) |
| return x |
| } |
| |
| var posType = reflect.TypeOf(token.Pos(0)) |
| |
| func killPos(v reflect.Value) { |
| switch v.Kind() { |
| case reflect.Ptr, reflect.Interface: |
| if !v.IsNil() { |
| killPos(v.Elem()) |
| } |
| case reflect.Slice: |
| n := v.Len() |
| for i := 0; i < n; i++ { |
| killPos(v.Index(i)) |
| } |
| case reflect.Struct: |
| n := v.NumField() |
| for i := 0; i < n; i++ { |
| f := v.Field(i) |
| if f.Type() == posType { |
| f.SetInt(0) |
| continue |
| } |
| killPos(f) |
| } |
| } |
| } |
| |
| // A Rename describes a single renaming. |
| type rename struct { |
| OldImport string // only apply rename if this import is present |
| NewImport string // add this import during rewrite |
| Old string // old name: p.T or *p.T |
| New string // new name: p.T or *p.T |
| } |
| |
| func renameFix(tab []rename) func(*ast.File) bool { |
| return func(f *ast.File) bool { |
| return renameFixTab(f, tab) |
| } |
| } |
| |
| func parseName(s string) (ptr bool, pkg, nam string) { |
| i := strings.Index(s, ".") |
| if i < 0 { |
| panic("parseName: invalid name " + s) |
| } |
| if strings.HasPrefix(s, "*") { |
| ptr = true |
| s = s[1:] |
| i-- |
| } |
| pkg = s[:i] |
| nam = s[i+1:] |
| return |
| } |
| |
| func renameFixTab(f *ast.File, tab []rename) bool { |
| fixed := false |
| added := map[string]bool{} |
| check := map[string]bool{} |
| for _, t := range tab { |
| if !imports(f, t.OldImport) { |
| continue |
| } |
| optr, opkg, onam := parseName(t.Old) |
| walk(f, func(n interface{}) { |
| np, ok := n.(*ast.Expr) |
| if !ok { |
| return |
| } |
| x := *np |
| if optr { |
| p, ok := x.(*ast.StarExpr) |
| if !ok { |
| return |
| } |
| x = p.X |
| } |
| if !isPkgDot(x, opkg, onam) { |
| return |
| } |
| if t.NewImport != "" && !added[t.NewImport] { |
| addImport(f, t.NewImport) |
| added[t.NewImport] = true |
| } |
| *np = expr(t.New) |
| check[t.OldImport] = true |
| fixed = true |
| }) |
| } |
| |
| for ipath := range check { |
| if !usesImport(f, ipath) { |
| deleteImport(f, ipath) |
| } |
| } |
| return fixed |
| } |