github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/superfluous-else.go (about)

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  
     8  	"github.com/mgechev/revive/lint"
     9  )
    10  
    11  // SuperfluousElseRule lints given else constructs.
    12  type SuperfluousElseRule struct{}
    13  
    14  // Apply applies the rule to given file.
    15  func (r *SuperfluousElseRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    16  	var failures []lint.Failure
    17  	onFailure := func(failure lint.Failure) {
    18  		failures = append(failures, failure)
    19  	}
    20  
    21  	var branchingFunctions = map[string]map[string]bool{
    22  		"os": {"Exit": true},
    23  		"log": {
    24  			"Fatal":   true,
    25  			"Fatalf":  true,
    26  			"Fatalln": true,
    27  			"Panic":   true,
    28  			"Panicf":  true,
    29  			"Panicln": true,
    30  		},
    31  	}
    32  
    33  	w := lintSuperfluousElse{make(map[*ast.IfStmt]bool), onFailure, branchingFunctions}
    34  	ast.Walk(w, file.AST)
    35  	return failures
    36  }
    37  
    38  // Name returns the rule name.
    39  func (r *SuperfluousElseRule) Name() string {
    40  	return "superfluous-else"
    41  }
    42  
    43  type lintSuperfluousElse struct {
    44  	ignore             map[*ast.IfStmt]bool
    45  	onFailure          func(lint.Failure)
    46  	branchingFunctions map[string]map[string]bool
    47  }
    48  
    49  func (w lintSuperfluousElse) Visit(node ast.Node) ast.Visitor {
    50  	ifStmt, ok := node.(*ast.IfStmt)
    51  	if !ok || ifStmt.Else == nil {
    52  		return w
    53  	}
    54  	if w.ignore[ifStmt] {
    55  		if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok {
    56  			w.ignore[elseif] = true
    57  		}
    58  		return w
    59  	}
    60  	if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok {
    61  		w.ignore[elseif] = true
    62  		return w
    63  	}
    64  	if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok {
    65  		// only care about elses without conditions
    66  		return w
    67  	}
    68  	if len(ifStmt.Body.List) == 0 {
    69  		return w
    70  	}
    71  	shortDecl := false // does the if statement have a ":=" initialization statement?
    72  	if ifStmt.Init != nil {
    73  		if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE {
    74  			shortDecl = true
    75  		}
    76  	}
    77  	extra := ""
    78  	if shortDecl {
    79  		extra = " (move short variable declaration to its own line if necessary)"
    80  	}
    81  
    82  	lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1]
    83  	switch stmt := lastStmt.(type) {
    84  	case *ast.BranchStmt:
    85  		token := stmt.Tok.String()
    86  		if token != "fallthrough" {
    87  			w.onFailure(newFailure(ifStmt.Else, "if block ends with a "+token+" statement, so drop this else and outdent its block"+extra))
    88  		}
    89  	case *ast.ExprStmt:
    90  		if ce, ok := stmt.X.(*ast.CallExpr); ok { // it's a function call
    91  			if fc, ok := ce.Fun.(*ast.SelectorExpr); ok {
    92  				if id, ok := fc.X.(*ast.Ident); ok {
    93  					fn := fc.Sel.Name
    94  					pkg := id.Name
    95  					if w.branchingFunctions[pkg][fn] { // it's a call to a branching function
    96  						w.onFailure(
    97  							newFailure(ifStmt.Else, fmt.Sprintf("if block ends with call to %s.%s function, so drop this else and outdent its block%s", pkg, fn, extra)))
    98  					}
    99  				}
   100  			}
   101  		}
   102  	}
   103  
   104  	return w
   105  }
   106  
   107  func newFailure(node ast.Node, msg string) lint.Failure {
   108  	return lint.Failure{
   109  		Confidence: 1,
   110  		Node:       node,
   111  		Category:   "indent",
   112  		Failure:    msg,
   113  	}
   114  }