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