github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/if-return.go (about) 1 package rule 2 3 import ( 4 "go/ast" 5 "go/token" 6 "strings" 7 8 "github.com/mgechev/revive/lint" 9 ) 10 11 // IfReturnRule lints given else constructs. 12 type IfReturnRule struct{} 13 14 // Apply applies the rule to given file. 15 func (r *IfReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { 16 var failures []lint.Failure 17 18 onFailure := func(failure lint.Failure) { 19 failures = append(failures, failure) 20 } 21 22 astFile := file.AST 23 w := &lintElseError{astFile, onFailure} 24 ast.Walk(w, astFile) 25 return failures 26 } 27 28 // Name returns the rule name. 29 func (r *IfReturnRule) Name() string { 30 return "if-return" 31 } 32 33 type lintElseError struct { 34 file *ast.File 35 onFailure func(lint.Failure) 36 } 37 38 func (w *lintElseError) Visit(node ast.Node) ast.Visitor { 39 switch v := node.(type) { 40 case *ast.BlockStmt: 41 for i := 0; i < len(v.List)-1; i++ { 42 // if var := whatever; var != nil { return var } 43 s, ok := v.List[i].(*ast.IfStmt) 44 if !ok || s.Body == nil || len(s.Body.List) != 1 || s.Else != nil { 45 continue 46 } 47 assign, ok := s.Init.(*ast.AssignStmt) 48 if !ok || len(assign.Lhs) != 1 || !(assign.Tok == token.DEFINE || assign.Tok == token.ASSIGN) { 49 continue 50 } 51 id, ok := assign.Lhs[0].(*ast.Ident) 52 if !ok { 53 continue 54 } 55 expr, ok := s.Cond.(*ast.BinaryExpr) 56 if !ok || expr.Op != token.NEQ { 57 continue 58 } 59 if lhs, ok := expr.X.(*ast.Ident); !ok || lhs.Name != id.Name { 60 continue 61 } 62 if rhs, ok := expr.Y.(*ast.Ident); !ok || rhs.Name != "nil" { 63 continue 64 } 65 r, ok := s.Body.List[0].(*ast.ReturnStmt) 66 if !ok || len(r.Results) != 1 { 67 continue 68 } 69 if r, ok := r.Results[0].(*ast.Ident); !ok || r.Name != id.Name { 70 continue 71 } 72 73 // return nil 74 r, ok = v.List[i+1].(*ast.ReturnStmt) 75 if !ok || len(r.Results) != 1 { 76 continue 77 } 78 if r, ok := r.Results[0].(*ast.Ident); !ok || r.Name != "nil" { 79 continue 80 } 81 82 // check if there are any comments explaining the construct, don't emit an error if there are some. 83 if containsComments(s.Pos(), r.Pos(), w.file) { 84 continue 85 } 86 87 w.onFailure(lint.Failure{ 88 Confidence: .9, 89 Node: v.List[i], 90 Failure: "redundant if ...; err != nil check, just return error instead.", 91 }) 92 } 93 } 94 return w 95 } 96 97 func containsComments(start, end token.Pos, f *ast.File) bool { 98 for _, cgroup := range f.Comments { 99 comments := cgroup.List 100 if comments[0].Slash >= end { 101 // All comments starting with this group are after end pos. 102 return false 103 } 104 if comments[len(comments)-1].Slash < start { 105 // Comments group ends before start pos. 106 continue 107 } 108 for _, c := range comments { 109 if start <= c.Slash && c.Slash < end && !strings.HasPrefix(c.Text, "// MATCH ") { 110 return true 111 } 112 } 113 } 114 return false 115 }