| // 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/token" |
| "path" |
| "strconv" |
| ) |
| |
| type fix struct { |
| name string |
| date string // date that fix was introduced, in YYYY-MM-DD format |
| f func(*ast.File) bool |
| desc string |
| disabled bool // whether this fix should be disabled by default |
| } |
| |
| // 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 any, visit func(any)) { |
| walkBeforeAfter(x, nop, visit) |
| } |
| |
| func nop(any) {} |
| |
| // walkBeforeAfter is like walk but calls before(x) before traversing |
| // x's children and after(x) afterward. |
| func walkBeforeAfter(x any, before, after func(any)) { |
| 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.IndexListExpr: |
| walkBeforeAfter(&n.X, before, after) |
| walkBeforeAfter(&n.Indices, 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: |
| if n.TypeParams != nil { |
| walkBeforeAfter(&n.TypeParams, before, after) |
| } |
| 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: |
| if n.TypeParams != nil { |
| walkBeforeAfter(&n.TypeParams, before, after) |
| } |
| 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 reports whether 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 |
| } |
| |
| // isTopName reports whether 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 |
| } |
| |
| // renameTop renames all references to the top-level name old. |
| // It reports whether 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 any) { |
| 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 |
| } |