blob: 5e20ff683bf3ae3a1c8a395f31c109b10e5f19b4 [file] [log] [blame]
// 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"
"regexp"
"strings"
)
func init() {
register(errorFix)
}
var errorFix = fix{
"error",
"2011-11-02",
errorFn,
`Use error instead of os.Error.
This fix rewrites code using os.Error to use error:
os.Error -> error
os.NewError -> errors.New
os.EOF -> io.EOF
Seeing the old names above (os.Error and so on) triggers the following
heuristic rewrites. The heuristics can be forced using the -force=error flag.
A top-level function, variable, or constant named error is renamed error_.
Error implementations—those types used as os.Error or named
XxxError—have their String methods renamed to Error. Any existing
Error field or method is renamed to Err.
Error values—those with type os.Error or named e, err, error, err1,
and so on—have method calls and field references rewritten just
as the types do (String to Error, Error to Err). Also, a type assertion
of the form err.(*os.Waitmsg) becomes err.(*exec.ExitError).
http://codereview.appspot.com/5305066
`,
}
// At minimum, this fix applies the following rewrites:
//
// os.Error -> error
// os.NewError -> errors.New
// os.EOF -> io.EOF
//
// However, if can apply any of those rewrites, it assumes that the
// file predates the error type and tries to update the code to use
// the new definition for error - an Error method, not a String method.
// This more heuristic procedure may not be 100% accurate, so it is
// only run when the file needs updating anyway. The heuristic can
// be forced to run using -force=error.
//
// First, we must identify the implementations of os.Error.
// These include the type of any value returned as or assigned to an os.Error.
// To that set we add any type whose name contains "Error" or "error".
// The heuristic helps for implementations that are not used as os.Error
// in the file in which they are defined.
//
// In any implementation of os.Error, we rename an existing struct field
// or method named Error to Err and rename the String method to Error.
//
// Second, we must identify the values of type os.Error.
// These include any value that obviously has type os.Error.
// To that set we add any variable whose name is e or err or error
// possibly followed by _ or a numeric or capitalized suffix.
// The heuristic helps for variables that are initialized using calls
// to functions in other packages. The type checker does not have
// information about those packages available, and in general cannot
// (because the packages may themselves not compile).
//
// For any value of type os.Error, we replace a call to String with a call to Error.
// We also replace type assertion err.(*os.Waitmsg) with err.(*exec.ExitError).
// Variables matching this regexp are assumed to have type os.Error.
var errVar = regexp.MustCompile(`^(e|err|error)_?([A-Z0-9].*)?$`)
// Types matching this regexp are assumed to be implementations of os.Error.
var errType = regexp.MustCompile(`^\*?([Ee]rror|.*Error)$`)
// Type-checking configuration: tell the type-checker this basic
// information about types, functions, and variables in external packages.
var errorTypeConfig = &TypeConfig{
Type: map[string]*Type{
"os.Error": &Type{},
},
Func: map[string]string{
"fmt.Errorf": "os.Error",
"os.NewError": "os.Error",
},
Var: map[string]string{
"os.EPERM": "os.Error",
"os.ENOENT": "os.Error",
"os.ESRCH": "os.Error",
"os.EINTR": "os.Error",
"os.EIO": "os.Error",
"os.ENXIO": "os.Error",
"os.E2BIG": "os.Error",
"os.ENOEXEC": "os.Error",
"os.EBADF": "os.Error",
"os.ECHILD": "os.Error",
"os.EDEADLK": "os.Error",
"os.ENOMEM": "os.Error",
"os.EACCES": "os.Error",
"os.EFAULT": "os.Error",
"os.EBUSY": "os.Error",
"os.EEXIST": "os.Error",
"os.EXDEV": "os.Error",
"os.ENODEV": "os.Error",
"os.ENOTDIR": "os.Error",
"os.EISDIR": "os.Error",
"os.EINVAL": "os.Error",
"os.ENFILE": "os.Error",
"os.EMFILE": "os.Error",
"os.ENOTTY": "os.Error",
"os.EFBIG": "os.Error",
"os.ENOSPC": "os.Error",
"os.ESPIPE": "os.Error",
"os.EROFS": "os.Error",
"os.EMLINK": "os.Error",
"os.EPIPE": "os.Error",
"os.EAGAIN": "os.Error",
"os.EDOM": "os.Error",
"os.ERANGE": "os.Error",
"os.EADDRINUSE": "os.Error",
"os.ECONNREFUSED": "os.Error",
"os.ENAMETOOLONG": "os.Error",
"os.EAFNOSUPPORT": "os.Error",
"os.ETIMEDOUT": "os.Error",
"os.ENOTCONN": "os.Error",
},
}
func errorFn(f *ast.File) bool {
if !imports(f, "os") && !force["error"] {
return false
}
// Fix gets called once to run the heuristics described above
// when we notice that this file definitely needs fixing
// (it mentions os.Error or something similar).
var fixed bool
var didHeuristic bool
heuristic := func() {
if didHeuristic {
return
}
didHeuristic = true
// We have identified a necessary fix (like os.Error -> error)
// but have not applied it or any others yet. Prepare the file
// for fixing and apply heuristic fixes.
// Rename error to error_ to make room for error.
fixed = renameTop(f, "error", "error_") || fixed
// Use type checker to build list of error implementations.
typeof, assign := typecheck(errorTypeConfig, f)
isError := map[string]bool{}
for _, val := range assign["os.Error"] {
t := typeof[val]
if strings.HasPrefix(t, "*") {
t = t[1:]
}
if t != "" && !strings.HasPrefix(t, "func(") {
isError[t] = true
}
}
// We use both the type check results and the "Error" name heuristic
// to identify implementations of os.Error.
isErrorImpl := func(typ string) bool {
return isError[typ] || errType.MatchString(typ)
}
isErrorVar := func(x ast.Expr) bool {
if typ := typeof[x]; typ != "" {
return isErrorImpl(typ) || typ == "os.Error"
}
if sel, ok := x.(*ast.SelectorExpr); ok {
return sel.Sel.Name == "Error" || sel.Sel.Name == "Err"
}
if id, ok := x.(*ast.Ident); ok {
return errVar.MatchString(id.Name)
}
return false
}
walk(f, func(n interface{}) {
// In method declaration on error implementation type,
// rename String() to Error() and Error() to Err().
fn, ok := n.(*ast.FuncDecl)
if ok &&
fn.Recv != nil &&
len(fn.Recv.List) == 1 &&
isErrorImpl(typeName(fn.Recv.List[0].Type)) {
// Rename.
switch fn.Name.Name {
case "String":
fn.Name.Name = "Error"
fixed = true
case "Error":
fn.Name.Name = "Err"
fixed = true
}
return
}
// In type definition of an error implementation type,
// rename Error field to Err to make room for method.
// Given type XxxError struct { ... Error T } rename field to Err.
d, ok := n.(*ast.GenDecl)
if ok {
for _, s := range d.Specs {
switch s := s.(type) {
case *ast.TypeSpec:
if isErrorImpl(typeName(s.Name)) {
st, ok := s.Type.(*ast.StructType)
if ok {
for _, f := range st.Fields.List {
for _, n := range f.Names {
if n.Name == "Error" {
n.Name = "Err"
fixed = true
}
}
}
}
}
}
}
}
// For values that are an error implementation type,
// rename .Error to .Err and .String to .Error
sel, selok := n.(*ast.SelectorExpr)
if selok && isErrorImpl(typeof[sel.X]) {
switch sel.Sel.Name {
case "Error":
sel.Sel.Name = "Err"
fixed = true
case "String":
sel.Sel.Name = "Error"
fixed = true
}
}
// Assume x.Err is an error value and rename .String to .Error
// Children have been processed so the rewrite from Error to Err
// has already happened there.
if selok {
if subsel, ok := sel.X.(*ast.SelectorExpr); ok && subsel.Sel.Name == "Err" && sel.Sel.Name == "String" {
sel.Sel.Name = "Error"
fixed = true
}
}
// For values that are an error variable, rename .String to .Error.
if selok && isErrorVar(sel.X) && sel.Sel.Name == "String" {
sel.Sel.Name = "Error"
fixed = true
}
// Rewrite composite literal of error type to turn Error: into Err:.
lit, ok := n.(*ast.CompositeLit)
if ok && isErrorImpl(typeof[lit]) {
for _, e := range lit.Elts {
if kv, ok := e.(*ast.KeyValueExpr); ok && isName(kv.Key, "Error") {
kv.Key.(*ast.Ident).Name = "Err"
fixed = true
}
}
}
// Rename os.Waitmsg to exec.ExitError
// when used in a type assertion on an error.
ta, ok := n.(*ast.TypeAssertExpr)
if ok && isErrorVar(ta.X) && isPtrPkgDot(ta.Type, "os", "Waitmsg") {
addImport(f, "exec")
sel := ta.Type.(*ast.StarExpr).X.(*ast.SelectorExpr)
sel.X.(*ast.Ident).Name = "exec"
sel.Sel.Name = "ExitError"
fixed = true
}
})
}
fix := func() {
if fixed {
return
}
fixed = true
heuristic()
}
if force["error"] {
heuristic()
}
walk(f, func(n interface{}) {
p, ok := n.(*ast.Expr)
if !ok {
return
}
sel, ok := (*p).(*ast.SelectorExpr)
if !ok {
return
}
switch {
case isPkgDot(sel, "os", "Error"):
fix()
*p = &ast.Ident{NamePos: sel.Pos(), Name: "error"}
case isPkgDot(sel, "os", "NewError"):
fix()
addImport(f, "errors")
sel.X.(*ast.Ident).Name = "errors"
sel.Sel.Name = "New"
case isPkgDot(sel, "os", "EOF"):
fix()
addImport(f, "io")
sel.X.(*ast.Ident).Name = "io"
}
})
if fixed && !usesImport(f, "os") {
deleteImport(f, "os")
}
return fixed
}
func typeName(typ ast.Expr) string {
if p, ok := typ.(*ast.StarExpr); ok {
typ = p.X
}
id, ok := typ.(*ast.Ident)
if ok {
return id.Name
}
sel, ok := typ.(*ast.SelectorExpr)
if ok {
return typeName(sel.X) + "." + sel.Sel.Name
}
return ""
}