summaryrefslogtreecommitdiff
path: root/src/cmd/gofix/fix.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/gofix/fix.go')
-rw-r--r--src/cmd/gofix/fix.go250
1 files changed, 211 insertions, 39 deletions
diff --git a/src/cmd/gofix/fix.go b/src/cmd/gofix/fix.go
index cc85ceafa..d1a7bc874 100644
--- a/src/cmd/gofix/fix.go
+++ b/src/cmd/gofix/fix.go
@@ -4,29 +4,47 @@
package main
+/*
+receiver named error
+function named error
+method on error
+exiterror
+slice of named type (go/scanner)
+*/
+
import (
"fmt"
"go/ast"
+ "go/parser"
"go/token"
"os"
+ "path"
"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(fixes) after init process is done.
-type fixlist []fix
+// 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 fixlist) Len() int { return len(f) }
-func (f fixlist) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
-func (f fixlist) Less(i, j int) bool { return f[i].name < f[j].name }
+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 fixlist
+var fixes []fix
func register(f fix) {
fixes = append(fixes, f)
@@ -73,6 +91,8 @@ func walkBeforeAfter(x interface{}, before, after func(interface{})) {
walkBeforeAfter(*n, before, after)
case **ast.Ident:
walkBeforeAfter(*n, before, after)
+ case **ast.BasicLit:
+ walkBeforeAfter(*n, before, after)
// pointers to slices
case *[]ast.Decl:
@@ -90,7 +110,9 @@ func walkBeforeAfter(x interface{}, before, after func(interface{})) {
// These are ordered and grouped to match ../../pkg/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)
@@ -292,6 +314,20 @@ func importPath(s *ast.ImportSpec) string {
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 {
@@ -446,66 +482,172 @@ func newPkgDot(pos token.Pos, pkg, name string) ast.Expr {
}
}
+// 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, path string) {
- if imports(f, path) {
- return
+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(path),
+ Value: strconv.Quote(ipath),
},
}
- var impdecl *ast.GenDecl
-
// Find an import decl to add to.
- for _, decl := range f.Decls {
+ 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 {
- impdecl = gen
- break
+ 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
+ }
+ }
}
}
- // No import decl found. Add one.
- if impdecl == nil {
- impdecl = &ast.GenDecl{
+ // 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[1:], f.Decls)
- f.Decls[0] = impdecl
+ 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()
+ if len(impDecl.Specs) > 0 && !impDecl.Lparen.IsValid() {
+ impDecl.Lparen = impDecl.Pos()
}
- // Assume the import paths are alphabetically ordered.
- // If they are not, the result is ugly, but legal.
- insertAt := len(impdecl.Specs) // default to end of specs
- for i, spec := range impdecl.Specs {
- impspec := spec.(*ast.ImportSpec)
- if importPath(impspec) > path {
- insertAt = i
- break
- }
+ 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()
}
-
- impdecl.Specs = append(impdecl.Specs, nil)
- copy(impdecl.Specs[insertAt+1:], impdecl.Specs[insertAt:])
- impdecl.Specs[insertAt] = newImport
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) {
+func deleteImport(f *ast.File, path string) (deleted bool) {
oldImport := importSpec(f, path)
// Find the import node that imports path, if any.
@@ -516,13 +658,13 @@ func deleteImport(f *ast.File, path string) {
}
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]
@@ -534,7 +676,13 @@ func deleteImport(f *ast.File, path string) {
} 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
}
}
@@ -547,6 +695,22 @@ func deleteImport(f *ast.File, path string) {
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, beacuse 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) {
@@ -580,3 +744,11 @@ func usesImport(f *ast.File, path string) (used bool) {
return
}
+
+func expr(s string) ast.Expr {
+ x, err := parser.ParseExpr(s)
+ if err != nil {
+ panic("parsing " + s + ": " + err.Error())
+ }
+ return x
+}