1package rule 2 3import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 8 "github.com/mgechev/revive/lint" 9) 10 11// SuperfluousElseRule lints given else constructs. 12type SuperfluousElseRule struct{} 13 14// Apply applies the rule to given file. 15func (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": map[string]bool{"Exit": true}, 23 "log": map[string]bool{ 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. 39func (r *SuperfluousElseRule) Name() string { 40 return "superfluous-else" 41} 42 43type lintSuperfluousElse struct { 44 ignore map[*ast.IfStmt]bool 45 onFailure func(lint.Failure) 46 branchingFunctions map[string]map[string]bool 47} 48 49func (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 107func 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} 115