src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa4014.go (2036B)


      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) (any, 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 }