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