sa4014.go raw
1 package sa4014
2
3 import (
4 "go/ast"
5
6 "honnef.co/go/tools/analysis/code"
7 "honnef.co/go/tools/analysis/lint"
8 "honnef.co/go/tools/analysis/report"
9
10 "golang.org/x/tools/go/analysis"
11 "golang.org/x/tools/go/analysis/passes/inspect"
12 )
13
14 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
15 Analyzer: &analysis.Analyzer{
16 Name: "SA4014",
17 Run: run,
18 Requires: []*analysis.Analyzer{inspect.Analyzer},
19 },
20 Doc: &lint.RawDocumentation{
21 Title: `An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either`,
22 Since: "2017.1",
23 Severity: lint.SeverityWarning,
24 MergeIf: lint.MergeIfAll,
25 },
26 })
27
28 var Analyzer = SCAnalyzer.Analyzer
29
30 func run(pass *analysis.Pass) (interface{}, error) {
31 seen := map[ast.Node]bool{}
32
33 var collectConds func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool)
34 collectConds = func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool) {
35 seen[ifstmt] = true
36 // Bail if any if-statement has an Init statement or side effects in its condition
37 if ifstmt.Init != nil {
38 return nil, false
39 }
40 if code.MayHaveSideEffects(pass, ifstmt.Cond, nil) {
41 return nil, false
42 }
43
44 conds = append(conds, ifstmt.Cond)
45 if elsestmt, ok := ifstmt.Else.(*ast.IfStmt); ok {
46 return collectConds(elsestmt, conds)
47 }
48 return conds, true
49 }
50 fn := func(node ast.Node) {
51 ifstmt := node.(*ast.IfStmt)
52 if seen[ifstmt] {
53 // this if-statement is part of an if/else-if chain that we've already processed
54 return
55 }
56 if ifstmt.Else == nil {
57 // there can be at most one condition
58 return
59 }
60 conds, ok := collectConds(ifstmt, nil)
61 if !ok {
62 return
63 }
64 if len(conds) < 2 {
65 return
66 }
67 counts := map[string]int{}
68 for _, cond := range conds {
69 s := report.Render(pass, cond)
70 counts[s]++
71 if counts[s] == 2 {
72 report.Report(pass, cond, "this condition occurs multiple times in this if/else if chain")
73 }
74 }
75 }
76 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
77 return nil, nil
78 }
79