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  }