st1016.go raw

   1  package st1016
   2  
   3  import (
   4  	"fmt"
   5  	"go/types"
   6  	"sort"
   7  	"strings"
   8  
   9  	"honnef.co/go/tools/analysis/code"
  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/go/types/typeutil"
  14  	"honnef.co/go/tools/internal/passes/buildir"
  15  
  16  	"golang.org/x/tools/go/analysis"
  17  )
  18  
  19  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  20  	Analyzer: &analysis.Analyzer{
  21  		Name:     "ST1016",
  22  		Run:      run,
  23  		Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
  24  	},
  25  	Doc: &lint.RawDocumentation{
  26  		Title:      `Use consistent method receiver names`,
  27  		Since:      "2019.1",
  28  		NonDefault: true,
  29  		MergeIf:    lint.MergeIfAny,
  30  	},
  31  })
  32  
  33  var Analyzer = SCAnalyzer.Analyzer
  34  
  35  func run(pass *analysis.Pass) (interface{}, error) {
  36  	irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
  37  	for _, m := range irpkg.Members {
  38  		names := map[string]int{}
  39  
  40  		var firstFn *types.Func
  41  		if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
  42  			ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
  43  			for _, sel := range ms {
  44  				fn := sel.Obj().(*types.Func)
  45  				recv := fn.Type().(*types.Signature).Recv()
  46  				if code.IsGenerated(pass, recv.Pos()) {
  47  					// Don't concern ourselves with methods in generated code
  48  					continue
  49  				}
  50  				if typeutil.Dereference(recv.Type()) != T.Type() {
  51  					// skip embedded methods
  52  					continue
  53  				}
  54  				if firstFn == nil {
  55  					firstFn = fn
  56  				}
  57  				if recv.Name() != "" && recv.Name() != "_" {
  58  					names[recv.Name()]++
  59  				}
  60  			}
  61  		}
  62  
  63  		if len(names) > 1 {
  64  			var seen []string
  65  			for name, count := range names {
  66  				seen = append(seen, fmt.Sprintf("%dx %q", count, name))
  67  			}
  68  			sort.Strings(seen)
  69  
  70  			report.Report(pass, firstFn, fmt.Sprintf("methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", ")))
  71  		}
  72  	}
  73  	return nil, nil
  74  }
  75