github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/unhandled-error.go (about) 1 package rule 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/types" 7 8 "github.com/songshiyun/revive/lint" 9 ) 10 11 // UnhandledErrorRule lints given else constructs. 12 type UnhandledErrorRule struct { 13 ignoreList ignoreListType 14 } 15 16 type ignoreListType map[string]struct{} 17 18 // Apply applies the rule to given file. 19 func (r *UnhandledErrorRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { 20 if r.ignoreList == nil { 21 r.ignoreList = make(ignoreListType, len(args)) 22 23 for _, arg := range args { 24 argStr, ok := arg.(string) 25 if !ok { 26 panic(fmt.Sprintf("Invalid argument to the unhandled-error rule. Expecting a string, got %T", arg)) 27 } 28 29 r.ignoreList[argStr] = struct{}{} 30 } 31 } 32 33 var failures []lint.Failure 34 35 walker := &lintUnhandledErrors{ 36 ignoreList: r.ignoreList, 37 pkg: file.Pkg, 38 onFailure: func(failure lint.Failure) { 39 failures = append(failures, failure) 40 }, 41 } 42 43 file.Pkg.TypeCheck() 44 ast.Walk(walker, file.AST) 45 46 return failures 47 } 48 49 // Name returns the rule name. 50 func (r *UnhandledErrorRule) Name() string { 51 return "unhandled-error" 52 } 53 54 type lintUnhandledErrors struct { 55 ignoreList ignoreListType 56 pkg *lint.Package 57 onFailure func(lint.Failure) 58 } 59 60 // Visit looks for statements that are function calls. 61 // If the called function returns a value of type error a failure will be created. 62 func (w *lintUnhandledErrors) Visit(node ast.Node) ast.Visitor { 63 switch n := node.(type) { 64 case *ast.ExprStmt: 65 fCall, ok := n.X.(*ast.CallExpr) 66 if !ok { 67 return nil // not a function call 68 } 69 70 funcType := w.pkg.TypeOf(fCall) 71 if funcType == nil { 72 return nil // skip, type info not available 73 } 74 75 switch t := funcType.(type) { 76 case *types.Named: 77 if !w.isTypeError(t) { 78 return nil // func call does not return an error 79 } 80 81 w.addFailure(fCall) 82 default: 83 retTypes, ok := funcType.Underlying().(*types.Tuple) 84 if !ok { 85 return nil // skip, unable to retrieve return type of the called function 86 } 87 88 if w.returnsAnError(retTypes) { 89 w.addFailure(fCall) 90 } 91 } 92 } 93 return w 94 } 95 96 func (w *lintUnhandledErrors) addFailure(n *ast.CallExpr) { 97 funcName := gofmt(n.Fun) 98 if _, mustIgnore := w.ignoreList[funcName]; mustIgnore { 99 return 100 } 101 102 w.onFailure(lint.Failure{ 103 Category: "bad practice", 104 Confidence: 1, 105 Node: n, 106 Failure: fmt.Sprintf("Unhandled error in call to function %v", funcName), 107 }) 108 } 109 110 func (*lintUnhandledErrors) isTypeError(t *types.Named) bool { 111 const errorTypeName = "_.error" 112 113 return t.Obj().Id() == errorTypeName 114 } 115 116 func (w *lintUnhandledErrors) returnsAnError(tt *types.Tuple) bool { 117 for i := 0; i < tt.Len(); i++ { 118 nt, ok := tt.At(i).Type().(*types.Named) 119 if ok && w.isTypeError(nt) { 120 return true 121 } 122 } 123 return false 124 }