s1034.go raw

   1  package s1034
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/types"
   7  
   8  	"honnef.co/go/tools/analysis/code"
   9  	"honnef.co/go/tools/analysis/edit"
  10  	"honnef.co/go/tools/analysis/facts/generated"
  11  	"honnef.co/go/tools/analysis/lint"
  12  	"honnef.co/go/tools/analysis/report"
  13  	"honnef.co/go/tools/pattern"
  14  
  15  	"golang.org/x/tools/go/analysis"
  16  	"golang.org/x/tools/go/analysis/passes/inspect"
  17  )
  18  
  19  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  20  	Analyzer: &analysis.Analyzer{
  21  		Name:     "S1034",
  22  		Run:      run,
  23  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
  24  	},
  25  	Doc: &lint.RawDocumentation{
  26  		Title:   `Use result of type assertion to simplify cases`,
  27  		Since:   "2019.2",
  28  		MergeIf: lint.MergeIfAny,
  29  	},
  30  })
  31  
  32  var Analyzer = SCAnalyzer.Analyzer
  33  
  34  var (
  35  	checkSimplifyTypeSwitchQ = pattern.MustParse(`
  36  		(TypeSwitchStmt
  37  			nil
  38  			expr@(TypeAssertExpr ident@(Ident _) _)
  39  			body)`)
  40  	checkSimplifyTypeSwitchR = pattern.MustParse(`(AssignStmt ident ":=" expr)`)
  41  )
  42  
  43  func run(pass *analysis.Pass) (interface{}, error) {
  44  	fn := func(node ast.Node) {
  45  		m, ok := code.Match(pass, checkSimplifyTypeSwitchQ, node)
  46  		if !ok {
  47  			return
  48  		}
  49  		stmt := node.(*ast.TypeSwitchStmt)
  50  		expr := m.State["expr"].(ast.Node)
  51  		ident := m.State["ident"].(*ast.Ident)
  52  
  53  		x := pass.TypesInfo.ObjectOf(ident)
  54  		var allOffenders []*ast.TypeAssertExpr
  55  		canSuggestFix := true
  56  		for _, clause := range stmt.Body.List {
  57  			clause := clause.(*ast.CaseClause)
  58  			if len(clause.List) != 1 {
  59  				continue
  60  			}
  61  			hasUnrelatedAssertion := false
  62  			var offenders []*ast.TypeAssertExpr
  63  			ast.Inspect(clause, func(node ast.Node) bool {
  64  				assert2, ok := node.(*ast.TypeAssertExpr)
  65  				if !ok {
  66  					return true
  67  				}
  68  				ident, ok := assert2.X.(*ast.Ident)
  69  				if !ok {
  70  					hasUnrelatedAssertion = true
  71  					return false
  72  				}
  73  				if pass.TypesInfo.ObjectOf(ident) != x {
  74  					hasUnrelatedAssertion = true
  75  					return false
  76  				}
  77  
  78  				if !types.Identical(pass.TypesInfo.TypeOf(clause.List[0]), pass.TypesInfo.TypeOf(assert2.Type)) {
  79  					hasUnrelatedAssertion = true
  80  					return false
  81  				}
  82  				offenders = append(offenders, assert2)
  83  				return true
  84  			})
  85  			if !hasUnrelatedAssertion {
  86  				// don't flag cases that have other type assertions
  87  				// unrelated to the one in the case clause. often
  88  				// times, this is done for symmetry, when two
  89  				// different values have to be asserted to the same
  90  				// type.
  91  				allOffenders = append(allOffenders, offenders...)
  92  			}
  93  			canSuggestFix = canSuggestFix && !hasUnrelatedAssertion
  94  		}
  95  		if len(allOffenders) != 0 {
  96  			var opts []report.Option
  97  			for _, offender := range allOffenders {
  98  				opts = append(opts, report.Related(offender, "could eliminate this type assertion"))
  99  			}
 100  			opts = append(opts, report.FilterGenerated())
 101  
 102  			msg := fmt.Sprintf("assigning the result of this type assertion to a variable (switch %s := %s.(type)) could eliminate type assertions in switch cases",
 103  				report.Render(pass, ident), report.Render(pass, ident))
 104  			if canSuggestFix {
 105  				var edits []analysis.TextEdit
 106  				edits = append(edits, edit.ReplaceWithPattern(pass.Fset, expr, checkSimplifyTypeSwitchR, m.State))
 107  				for _, offender := range allOffenders {
 108  					edits = append(edits, edit.ReplaceWithNode(pass.Fset, offender, offender.X))
 109  				}
 110  				opts = append(opts, report.Fixes(edit.Fix("simplify type switch", edits...)))
 111  				report.Report(pass, expr, msg, opts...)
 112  			} else {
 113  				report.Report(pass, expr, msg, opts...)
 114  			}
 115  		}
 116  	}
 117  	code.Preorder(pass, fn, (*ast.TypeSwitchStmt)(nil))
 118  	return nil, nil
 119  }
 120