mirror of
https://github.com/golang/go
synced 2024-11-25 03:47:57 -07:00
gofix: new command for updating code to new release
R=bradfitzgo, dsymonds, r, gri, adg CC=golang-dev https://golang.org/cl/4282044
This commit is contained in:
parent
66f09fd459
commit
a34f1bbb22
@ -41,6 +41,7 @@ CLEANDIRS=\
|
|||||||
cgo\
|
cgo\
|
||||||
ebnflint\
|
ebnflint\
|
||||||
godoc\
|
godoc\
|
||||||
|
gofix\
|
||||||
gofmt\
|
gofmt\
|
||||||
goinstall\
|
goinstall\
|
||||||
gotype\
|
gotype\
|
||||||
|
16
src/cmd/gofix/Makefile
Normal file
16
src/cmd/gofix/Makefile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
include ../../Make.inc
|
||||||
|
|
||||||
|
TARG=gofix
|
||||||
|
GOFILES=\
|
||||||
|
fix.go\
|
||||||
|
main.go\
|
||||||
|
httpserver.go\
|
||||||
|
|
||||||
|
include ../../Make.cmd
|
||||||
|
|
||||||
|
test:
|
||||||
|
gotest
|
34
src/cmd/gofix/doc.go
Normal file
34
src/cmd/gofix/doc.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gofix finds Go programs that use old APIs and rewrites them to use
|
||||||
|
newer ones. After you update to a new Go release, gofix helps make
|
||||||
|
the necessary changes to your programs.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
gofix [-r name,...] [path ...]
|
||||||
|
|
||||||
|
Without an explicit path, gofix reads standard input and writes the
|
||||||
|
result to standard output.
|
||||||
|
|
||||||
|
If the named path is a file, gofix rewrites the named files in place.
|
||||||
|
If the named path is a directory, gofix rewrites all .go files in that
|
||||||
|
directory tree. When gofix rewrites a file, it prints a line to standard
|
||||||
|
error giving the name of the file and the rewrite applied.
|
||||||
|
|
||||||
|
The -r flag restricts the set of rewrites considered to those in the
|
||||||
|
named list. By default gofix considers all known rewrites. Gofix's
|
||||||
|
rewrites are idempotent, so that it is safe to apply gofix to updated
|
||||||
|
or partially updated code even without using the -r flag.
|
||||||
|
|
||||||
|
Gofix prints the full list of fixes it can apply in its help output;
|
||||||
|
to see them, run godoc -?.
|
||||||
|
|
||||||
|
Gofix does not make backup copies of the files that it edits.
|
||||||
|
Instead, use a version control system's ``diff'' functionality to inspect
|
||||||
|
the changes that gofix makes before committing them.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package documentation
|
297
src/cmd/gofix/fix.go
Normal file
297
src/cmd/gofix/fix.go
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
// 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"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fix struct {
|
||||||
|
name string
|
||||||
|
f func(*ast.File) bool
|
||||||
|
desc string
|
||||||
|
}
|
||||||
|
|
||||||
|
// main runs sort.Sort(fixes) after init process is done.
|
||||||
|
type fixlist []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 }
|
||||||
|
|
||||||
|
var fixes fixlist
|
||||||
|
|
||||||
|
func register(f fix) {
|
||||||
|
fixes = append(fixes, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewrite walks the AST x, calling visit(y) for each node y in the tree but
|
||||||
|
// also with a pointer to each ast.Expr, in a bottom-up traversal.
|
||||||
|
func rewrite(x interface{}, visit func(interface{})) {
|
||||||
|
switch n := x.(type) {
|
||||||
|
case *ast.Expr:
|
||||||
|
rewrite(*n, visit)
|
||||||
|
|
||||||
|
// everything else just recurses
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unexpected type %T in walk", x, visit))
|
||||||
|
|
||||||
|
case nil:
|
||||||
|
|
||||||
|
// These are ordered and grouped to match ../../pkg/go/ast/ast.go
|
||||||
|
case *ast.Field:
|
||||||
|
rewrite(&n.Type, visit)
|
||||||
|
case *ast.FieldList:
|
||||||
|
for _, field := range n.List {
|
||||||
|
rewrite(field, visit)
|
||||||
|
}
|
||||||
|
case *ast.BadExpr:
|
||||||
|
case *ast.Ident:
|
||||||
|
case *ast.Ellipsis:
|
||||||
|
case *ast.BasicLit:
|
||||||
|
case *ast.FuncLit:
|
||||||
|
rewrite(n.Type, visit)
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
case *ast.CompositeLit:
|
||||||
|
rewrite(&n.Type, visit)
|
||||||
|
rewrite(n.Elts, visit)
|
||||||
|
case *ast.ParenExpr:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
case *ast.IndexExpr:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
rewrite(&n.Index, visit)
|
||||||
|
case *ast.SliceExpr:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
if n.Low != nil {
|
||||||
|
rewrite(&n.Low, visit)
|
||||||
|
}
|
||||||
|
if n.High != nil {
|
||||||
|
rewrite(&n.High, visit)
|
||||||
|
}
|
||||||
|
case *ast.TypeAssertExpr:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
rewrite(&n.Type, visit)
|
||||||
|
case *ast.CallExpr:
|
||||||
|
rewrite(&n.Fun, visit)
|
||||||
|
rewrite(n.Args, visit)
|
||||||
|
case *ast.StarExpr:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
case *ast.UnaryExpr:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
rewrite(&n.Y, visit)
|
||||||
|
case *ast.KeyValueExpr:
|
||||||
|
rewrite(&n.Key, visit)
|
||||||
|
rewrite(&n.Value, visit)
|
||||||
|
|
||||||
|
case *ast.ArrayType:
|
||||||
|
rewrite(&n.Len, visit)
|
||||||
|
rewrite(&n.Elt, visit)
|
||||||
|
case *ast.StructType:
|
||||||
|
rewrite(n.Fields, visit)
|
||||||
|
case *ast.FuncType:
|
||||||
|
rewrite(n.Params, visit)
|
||||||
|
if n.Results != nil {
|
||||||
|
rewrite(n.Results, visit)
|
||||||
|
}
|
||||||
|
case *ast.InterfaceType:
|
||||||
|
rewrite(n.Methods, visit)
|
||||||
|
case *ast.MapType:
|
||||||
|
rewrite(&n.Key, visit)
|
||||||
|
rewrite(&n.Value, visit)
|
||||||
|
case *ast.ChanType:
|
||||||
|
rewrite(&n.Value, visit)
|
||||||
|
|
||||||
|
case *ast.BadStmt:
|
||||||
|
case *ast.DeclStmt:
|
||||||
|
rewrite(n.Decl, visit)
|
||||||
|
case *ast.EmptyStmt:
|
||||||
|
case *ast.LabeledStmt:
|
||||||
|
rewrite(n.Stmt, visit)
|
||||||
|
case *ast.ExprStmt:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
case *ast.SendStmt:
|
||||||
|
rewrite(&n.Chan, visit)
|
||||||
|
rewrite(&n.Value, visit)
|
||||||
|
case *ast.IncDecStmt:
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
case *ast.AssignStmt:
|
||||||
|
rewrite(n.Lhs, visit)
|
||||||
|
if len(n.Lhs) == 2 && len(n.Rhs) == 1 {
|
||||||
|
rewrite(n.Rhs, visit)
|
||||||
|
} else {
|
||||||
|
rewrite(n.Rhs, visit)
|
||||||
|
}
|
||||||
|
case *ast.GoStmt:
|
||||||
|
rewrite(n.Call, visit)
|
||||||
|
case *ast.DeferStmt:
|
||||||
|
rewrite(n.Call, visit)
|
||||||
|
case *ast.ReturnStmt:
|
||||||
|
rewrite(n.Results, visit)
|
||||||
|
case *ast.BranchStmt:
|
||||||
|
case *ast.BlockStmt:
|
||||||
|
rewrite(n.List, visit)
|
||||||
|
case *ast.IfStmt:
|
||||||
|
rewrite(n.Init, visit)
|
||||||
|
rewrite(&n.Cond, visit)
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
rewrite(n.Else, visit)
|
||||||
|
case *ast.CaseClause:
|
||||||
|
rewrite(n.Values, visit)
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
case *ast.SwitchStmt:
|
||||||
|
rewrite(n.Init, visit)
|
||||||
|
rewrite(&n.Tag, visit)
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
case *ast.TypeCaseClause:
|
||||||
|
rewrite(n.Types, visit)
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
case *ast.TypeSwitchStmt:
|
||||||
|
rewrite(n.Init, visit)
|
||||||
|
rewrite(n.Assign, visit)
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
case *ast.CommClause:
|
||||||
|
rewrite(n.Comm, visit)
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
case *ast.SelectStmt:
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
case *ast.ForStmt:
|
||||||
|
rewrite(n.Init, visit)
|
||||||
|
rewrite(&n.Cond, visit)
|
||||||
|
rewrite(n.Post, visit)
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
case *ast.RangeStmt:
|
||||||
|
rewrite(&n.Key, visit)
|
||||||
|
rewrite(&n.Value, visit)
|
||||||
|
rewrite(&n.X, visit)
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
|
||||||
|
case *ast.ImportSpec:
|
||||||
|
case *ast.ValueSpec:
|
||||||
|
rewrite(&n.Type, visit)
|
||||||
|
rewrite(n.Values, visit)
|
||||||
|
case *ast.TypeSpec:
|
||||||
|
rewrite(&n.Type, visit)
|
||||||
|
|
||||||
|
case *ast.BadDecl:
|
||||||
|
case *ast.GenDecl:
|
||||||
|
rewrite(n.Specs, visit)
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
if n.Recv != nil {
|
||||||
|
rewrite(n.Recv, visit)
|
||||||
|
}
|
||||||
|
rewrite(n.Type, visit)
|
||||||
|
if n.Body != nil {
|
||||||
|
rewrite(n.Body, visit)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.File:
|
||||||
|
rewrite(n.Decls, visit)
|
||||||
|
|
||||||
|
case *ast.Package:
|
||||||
|
for _, file := range n.Files {
|
||||||
|
rewrite(file, visit)
|
||||||
|
}
|
||||||
|
|
||||||
|
case []ast.Decl:
|
||||||
|
for _, d := range n {
|
||||||
|
rewrite(d, visit)
|
||||||
|
}
|
||||||
|
case []ast.Expr:
|
||||||
|
for i := range n {
|
||||||
|
rewrite(&n[i], visit)
|
||||||
|
}
|
||||||
|
case []ast.Stmt:
|
||||||
|
for _, s := range n {
|
||||||
|
rewrite(s, visit)
|
||||||
|
}
|
||||||
|
case []ast.Spec:
|
||||||
|
for _, s := range n {
|
||||||
|
rewrite(s, visit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visit(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imports(f *ast.File, path string) bool {
|
||||||
|
for _, decl := range f.Decls {
|
||||||
|
d, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, spec := range d.Specs {
|
||||||
|
s, ok := spec.(*ast.ImportSpec)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if string(s.Path.Value) == `"`+path+`"` {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPkgDot(t ast.Expr, pkg, name string) bool {
|
||||||
|
sel, ok := t.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isName(sel.X, pkg) && sel.Sel.String() == name
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPtrPkgDot(t ast.Expr, pkg, name string) bool {
|
||||||
|
ptr, ok := t.(*ast.StarExpr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isPkgDot(ptr.X, pkg, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isName(n ast.Expr, name string) bool {
|
||||||
|
id, ok := n.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return id.String() == name
|
||||||
|
}
|
||||||
|
|
||||||
|
func refersTo(n ast.Node, x *ast.Ident) bool {
|
||||||
|
id, ok := n.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return id.String() == x.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBlank(n ast.Expr) bool {
|
||||||
|
return isName(n, "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmptyString(n ast.Expr) bool {
|
||||||
|
lit, ok := n.(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lit.Kind != token.STRING {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s := string(lit.Value)
|
||||||
|
return s == `""` || s == "``"
|
||||||
|
}
|
||||||
|
|
||||||
|
func warn(pos token.Pos, msg string, args ...interface{}) {
|
||||||
|
s := ""
|
||||||
|
if pos.IsValid() {
|
||||||
|
s = fmt.Sprintf("%s: ", fset.Position(pos).String())
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "%s"+msg+"\n", append([]interface{}{s}, args...))
|
||||||
|
}
|
125
src/cmd/gofix/httpserver.go
Normal file
125
src/cmd/gofix/httpserver.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// 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 (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
var httpserverFix = fix{
|
||||||
|
"httpserver",
|
||||||
|
httpserver,
|
||||||
|
`Adapt http server methods and functions to changes
|
||||||
|
made to the http ResponseWriter interface.
|
||||||
|
|
||||||
|
http://codereview.appspot.com/4245064 Hijacker
|
||||||
|
http://codereview.appspot.com/4239076 Header
|
||||||
|
http://codereview.appspot.com/4239077 Flusher
|
||||||
|
http://codereview.appspot.com/4248075 RemoteAddr, UsingTLS
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register(httpserverFix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpserver(f *ast.File) bool {
|
||||||
|
if !imports(f, "http") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed := false
|
||||||
|
for _, decl := range f.Decls {
|
||||||
|
fn, ok := decl.(*ast.FuncDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w, req, ok := isServeHTTP(fn)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rewrite(fn.Body, func(n interface{}) {
|
||||||
|
// Want to replace expression sometimes,
|
||||||
|
// so record pointer to it for updating below.
|
||||||
|
ptr, ok := n.(*ast.Expr)
|
||||||
|
if ok {
|
||||||
|
n = *ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for w.UsingTLS() and w.Remoteaddr().
|
||||||
|
call, ok := n.(*ast.CallExpr)
|
||||||
|
if !ok || len(call.Args) != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sel, ok := call.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !refersTo(sel.X, w) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch sel.Sel.String() {
|
||||||
|
case "Hijack":
|
||||||
|
// replace w with w.(http.Hijacker)
|
||||||
|
sel.X = &ast.TypeAssertExpr{
|
||||||
|
X: sel.X,
|
||||||
|
Type: ast.NewIdent("http.Hijacker"),
|
||||||
|
}
|
||||||
|
fixed = true
|
||||||
|
case "Flush":
|
||||||
|
// replace w with w.(http.Flusher)
|
||||||
|
sel.X = &ast.TypeAssertExpr{
|
||||||
|
X: sel.X,
|
||||||
|
Type: ast.NewIdent("http.Flusher"),
|
||||||
|
}
|
||||||
|
fixed = true
|
||||||
|
case "UsingTLS":
|
||||||
|
if ptr == nil {
|
||||||
|
// can only replace expression if we have pointer to it
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// replace with req.TLS != nil
|
||||||
|
*ptr = &ast.BinaryExpr{
|
||||||
|
X: &ast.SelectorExpr{
|
||||||
|
X: ast.NewIdent(req.String()),
|
||||||
|
Sel: ast.NewIdent("TLS"),
|
||||||
|
},
|
||||||
|
Op: token.NEQ,
|
||||||
|
Y: ast.NewIdent("nil"),
|
||||||
|
}
|
||||||
|
fixed = true
|
||||||
|
case "RemoteAddr":
|
||||||
|
if ptr == nil {
|
||||||
|
// can only replace expression if we have pointer to it
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// replace with req.RemoteAddr
|
||||||
|
*ptr = &ast.SelectorExpr{
|
||||||
|
X: ast.NewIdent(req.String()),
|
||||||
|
Sel: ast.NewIdent("RemoteAddr"),
|
||||||
|
}
|
||||||
|
fixed = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return fixed
|
||||||
|
}
|
||||||
|
|
||||||
|
func isServeHTTP(fn *ast.FuncDecl) (w, req *ast.Ident, ok bool) {
|
||||||
|
for _, field := range fn.Type.Params.List {
|
||||||
|
if isPkgDot(field.Type, "http", "ResponseWriter") {
|
||||||
|
w = field.Names[0]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isPtrPkgDot(field.Type, "http", "Request") {
|
||||||
|
req = field.Names[0]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = w != nil && req != nil
|
||||||
|
return
|
||||||
|
}
|
46
src/cmd/gofix/httpserver_test.go
Normal file
46
src/cmd/gofix/httpserver_test.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
addTestCases(httpserverTests)
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpserverTests = []testCase{
|
||||||
|
{
|
||||||
|
Name: "httpserver.0",
|
||||||
|
Fn: httpserver,
|
||||||
|
In: `package main
|
||||||
|
|
||||||
|
import "http"
|
||||||
|
|
||||||
|
func f(xyz http.ResponseWriter, abc *http.Request, b string) {
|
||||||
|
xyz.Hijack()
|
||||||
|
xyz.Flush()
|
||||||
|
go xyz.Hijack()
|
||||||
|
defer xyz.Flush()
|
||||||
|
_ = xyz.UsingTLS()
|
||||||
|
_ = true == xyz.UsingTLS()
|
||||||
|
_ = xyz.RemoteAddr()
|
||||||
|
_ = xyz.RemoteAddr() == "hello"
|
||||||
|
if xyz.UsingTLS() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Out: `package main
|
||||||
|
|
||||||
|
import "http"
|
||||||
|
|
||||||
|
func f(xyz http.ResponseWriter, abc *http.Request, b string) {
|
||||||
|
xyz.(http.Hijacker).Hijack()
|
||||||
|
xyz.(http.Flusher).Flush()
|
||||||
|
go xyz.(http.Hijacker).Hijack()
|
||||||
|
defer xyz.(http.Flusher).Flush()
|
||||||
|
_ = abc.TLS != nil
|
||||||
|
_ = true == (abc.TLS != nil)
|
||||||
|
_ = abc.RemoteAddr
|
||||||
|
_ = abc.RemoteAddr == "hello"
|
||||||
|
if abc.TLS != nil {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
179
src/cmd/gofix/main.go
Normal file
179
src/cmd/gofix/main.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/parser"
|
||||||
|
"go/printer"
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fset = token.NewFileSet()
|
||||||
|
exitCode = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
var allowedRewrites = flag.String("r", "",
|
||||||
|
"restrict the rewrites to this comma-separated list")
|
||||||
|
|
||||||
|
var allowed map[string]bool
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "usage: gofix [-r fixname,...] [path ...]\n")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
fmt.Fprintf(os.Stderr, "\nAvailable rewrites are:\n")
|
||||||
|
for _, f := range fixes {
|
||||||
|
fmt.Fprintf(os.Stderr, "\n%s\n", f.name)
|
||||||
|
desc := strings.TrimSpace(f.desc)
|
||||||
|
desc = strings.Replace(desc, "\n", "\n\t", -1)
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%s\n", desc)
|
||||||
|
}
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
sort.Sort(fixes)
|
||||||
|
|
||||||
|
flag.Usage = usage
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *allowedRewrites != "" {
|
||||||
|
allowed = make(map[string]bool)
|
||||||
|
for _, f := range strings.Split(*allowedRewrites, ",", -1) {
|
||||||
|
allowed[f] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag.NArg() == 0 {
|
||||||
|
if err := processFile("standard input", true); err != nil {
|
||||||
|
report(err)
|
||||||
|
}
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < flag.NArg(); i++ {
|
||||||
|
path := flag.Arg(i)
|
||||||
|
switch dir, err := os.Stat(path); {
|
||||||
|
case err != nil:
|
||||||
|
report(err)
|
||||||
|
case dir.IsRegular():
|
||||||
|
if err := processFile(path, false); err != nil {
|
||||||
|
report(err)
|
||||||
|
}
|
||||||
|
case dir.IsDirectory():
|
||||||
|
walkDir(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
tabWidth = 8
|
||||||
|
parserMode = parser.ParseComments
|
||||||
|
printerMode = printer.TabIndent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func processFile(filename string, useStdin bool) os.Error {
|
||||||
|
var f *os.File
|
||||||
|
var err os.Error
|
||||||
|
|
||||||
|
if useStdin {
|
||||||
|
f = os.Stdin
|
||||||
|
} else {
|
||||||
|
f, err = os.Open(filename, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := parser.ParseFile(fset, filename, src, parserMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed := false
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, fix := range fixes {
|
||||||
|
if allowed != nil && !allowed[fix.desc] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fix.f(file) {
|
||||||
|
fixed = true
|
||||||
|
fmt.Fprintf(&buf, " %s", fix.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fixed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %s\n", filename, buf.String()[1:])
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
_, err = (&printer.Config{printerMode, tabWidth}).Fprint(&buf, fset, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if useStdin {
|
||||||
|
os.Stdout.Write(buf.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(f.Name(), buf.Bytes(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func report(err os.Error) {
|
||||||
|
scanner.PrintError(os.Stderr, err)
|
||||||
|
exitCode = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkDir(path string) {
|
||||||
|
v := make(fileVisitor)
|
||||||
|
go func() {
|
||||||
|
filepath.Walk(path, v, v)
|
||||||
|
close(v)
|
||||||
|
}()
|
||||||
|
for err := range v {
|
||||||
|
if err != nil {
|
||||||
|
report(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileVisitor chan os.Error
|
||||||
|
|
||||||
|
func (v fileVisitor) VisitDir(path string, f *os.FileInfo) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v fileVisitor) VisitFile(path string, f *os.FileInfo) {
|
||||||
|
if isGoFile(f) {
|
||||||
|
v <- nil // synchronize error handler
|
||||||
|
if err := processFile(path, false); err != nil {
|
||||||
|
v <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGoFile(f *os.FileInfo) bool {
|
||||||
|
// ignore non-Go files
|
||||||
|
return f.IsRegular() && !strings.HasPrefix(f.Name, ".") && strings.HasSuffix(f.Name, ".go")
|
||||||
|
}
|
102
src/cmd/gofix/main_test.go
Normal file
102
src/cmd/gofix/main_test.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/printer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
Name string
|
||||||
|
Fn func(*ast.File) bool
|
||||||
|
In string
|
||||||
|
Out string
|
||||||
|
}
|
||||||
|
|
||||||
|
var testCases []testCase
|
||||||
|
|
||||||
|
func addTestCases(t []testCase) {
|
||||||
|
testCases = append(testCases, t...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFixPrint(t *testing.T, fn func(*ast.File) bool, desc, in string) (out string, fixed, ok bool) {
|
||||||
|
file, err := parser.ParseFile(fset, desc, in, parserMode)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: parsing: %v", desc, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.Reset()
|
||||||
|
_, err = (&printer.Config{printerMode, tabWidth}).Fprint(&buf, fset, file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: printing: %v", desc, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s := buf.String(); in != s {
|
||||||
|
t.Errorf("%s: not gofmt-formatted.\n--- %s\n%s\n--- %s | gofmt\n%s",
|
||||||
|
desc, desc, in, desc, s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if fn == nil {
|
||||||
|
for _, fix := range fixes {
|
||||||
|
if fix.f(file) {
|
||||||
|
fixed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fixed = fn(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
_, err = (&printer.Config{printerMode, tabWidth}).Fprint(&buf, fset, file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: printing: %v", desc, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), fixed, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRewrite(t *testing.T) {
|
||||||
|
for _, tt := range testCases {
|
||||||
|
// Apply fix: should get tt.Out.
|
||||||
|
out, fixed, ok := parseFixPrint(t, tt.Fn, tt.Name, tt.In)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if out != tt.Out {
|
||||||
|
t.Errorf("%s: incorrect output.\n--- have\n%s\n--- want\n%s", tt.Name, out, tt.Out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed := out != tt.In; changed != fixed {
|
||||||
|
t.Errorf("%s: changed=%v != fixed=%v", tt.Name, changed, fixed)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should not change if run again.
|
||||||
|
out2, fixed2, ok := parseFixPrint(t, tt.Fn, tt.Name+" output", out)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fixed2 {
|
||||||
|
t.Errorf("%s: applied fixes during second round", tt.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if out2 != out {
|
||||||
|
t.Errorf("%s: changed output after second round of fixes.\n--- output after first round\n%s\n--- output after second round\n%s",
|
||||||
|
tt.Name, out, out2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -155,6 +155,7 @@ DIRS=\
|
|||||||
../cmd/cgo\
|
../cmd/cgo\
|
||||||
../cmd/ebnflint\
|
../cmd/ebnflint\
|
||||||
../cmd/godoc\
|
../cmd/godoc\
|
||||||
|
../cmd/gofix\
|
||||||
../cmd/gofmt\
|
../cmd/gofmt\
|
||||||
../cmd/gotype\
|
../cmd/gotype\
|
||||||
../cmd/goinstall\
|
../cmd/goinstall\
|
||||||
|
Loading…
Reference in New Issue
Block a user