workgroups/vendor/github.com/Djarvur/go-err113/comparison.go

124 lines
2.5 KiB
Go
Raw Permalink Normal View History

2021-09-24 17:34:17 +02:00
package err113
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
)
func inspectComparision(pass *analysis.Pass, n ast.Node) bool { // nolint: unparam
// check whether the call expression matches time.Now().Sub()
be, ok := n.(*ast.BinaryExpr)
if !ok {
return true
}
// check if it is a comparison operation
if be.Op != token.EQL && be.Op != token.NEQ {
return true
}
if !areBothErrors(be.X, be.Y, pass.TypesInfo) {
return true
}
oldExpr := render(pass.Fset, be)
negate := ""
if be.Op == token.NEQ {
negate = "!"
}
newExpr := fmt.Sprintf("%s%s.Is(%s, %s)", negate, "errors", rawString(be.X), rawString(be.Y))
pass.Report(
analysis.Diagnostic{
Pos: be.Pos(),
Message: fmt.Sprintf("do not compare errors directly %q, use %q instead", oldExpr, newExpr),
SuggestedFixes: []analysis.SuggestedFix{
{
Message: fmt.Sprintf("should replace %q with %q", oldExpr, newExpr),
TextEdits: []analysis.TextEdit{
{
Pos: be.Pos(),
End: be.End(),
NewText: []byte(newExpr),
},
},
},
},
},
)
return true
}
func isError(v ast.Expr, info *types.Info) bool {
if intf, ok := info.TypeOf(v).Underlying().(*types.Interface); ok {
return intf.NumMethods() == 1 && intf.Method(0).FullName() == "(error).Error"
}
return false
}
func isEOF(ex ast.Expr, info *types.Info) bool {
se, ok := ex.(*ast.SelectorExpr)
if !ok || se.Sel.Name != "EOF" {
return false
}
if ep, ok := asImportedName(se.X, info); !ok || ep != "io" {
return false
}
return true
}
func asImportedName(ex ast.Expr, info *types.Info) (string, bool) {
ei, ok := ex.(*ast.Ident)
if !ok {
return "", false
}
ep, ok := info.ObjectOf(ei).(*types.PkgName)
if !ok {
return "", false
}
return ep.Imported().Path(), true
}
func areBothErrors(x, y ast.Expr, typesInfo *types.Info) bool {
// check that both left and right hand side are not nil
if typesInfo.Types[x].IsNil() || typesInfo.Types[y].IsNil() {
return false
}
// check that both left and right hand side are not io.EOF
if isEOF(x, typesInfo) || isEOF(y, typesInfo) {
return false
}
// check that both left and right hand side are errors
if !isError(x, typesInfo) && !isError(y, typesInfo) {
return false
}
return true
}
func rawString(x ast.Expr) string {
switch t := x.(type) {
case *ast.Ident:
return t.Name
case *ast.SelectorExpr:
return fmt.Sprintf("%s.%s", rawString(t.X), t.Sel.Name)
case *ast.CallExpr:
return fmt.Sprintf("%s()", rawString(t.Fun))
}
return fmt.Sprintf("%s", x)
}