| // 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": {}, |
| }, |
| 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 "" |
| } |