github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/stylecheck/st1016/st1016.go (about)

     1  package st1016
     2  
     3  import (
     4  	"fmt"
     5  	"go/types"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/amarpal/go-tools/analysis/code"
    10  	"github.com/amarpal/go-tools/analysis/facts/generated"
    11  	"github.com/amarpal/go-tools/analysis/lint"
    12  	"github.com/amarpal/go-tools/analysis/report"
    13  	"github.com/amarpal/go-tools/go/types/typeutil"
    14  	"github.com/amarpal/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.Documentation{
    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  }