github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa4014/sa4014.go (about) 1 package sa4014 2 3 import ( 4 "go/ast" 5 6 "github.com/amarpal/go-tools/analysis/code" 7 "github.com/amarpal/go-tools/analysis/lint" 8 "github.com/amarpal/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.Documentation{ 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 }