1package checkers
2
3import (
4	"go/ast"
5	"go/token"
6
7	"github.com/go-critic/go-critic/checkers/internal/astwalk"
8	"github.com/go-critic/go-critic/framework/linter"
9	"github.com/go-toolsmith/astcast"
10	"github.com/go-toolsmith/astcopy"
11	"github.com/go-toolsmith/typep"
12)
13
14func init() {
15	var info linter.CheckerInfo
16	info.Name = "methodExprCall"
17	info.Tags = []string{"style", "experimental"}
18	info.Summary = "Detects method expression call that can be replaced with a method call"
19	info.Before = `f := foo{}
20foo.bar(f)`
21	info.After = `f := foo{}
22f.bar()`
23
24	collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) {
25		return astwalk.WalkerForExpr(&methodExprCallChecker{ctx: ctx}), nil
26	})
27}
28
29type methodExprCallChecker struct {
30	astwalk.WalkHandler
31	ctx *linter.CheckerContext
32}
33
34func (c *methodExprCallChecker) VisitExpr(x ast.Expr) {
35	call := astcast.ToCallExpr(x)
36	s := astcast.ToSelectorExpr(call.Fun)
37
38	if len(call.Args) < 1 || astcast.ToIdent(call.Args[0]).Name == "nil" {
39		return
40	}
41
42	if typep.IsTypeExpr(c.ctx.TypesInfo, s.X) {
43		c.warn(call, s)
44	}
45}
46
47func (c *methodExprCallChecker) warn(cause *ast.CallExpr, s *ast.SelectorExpr) {
48	selector := astcopy.SelectorExpr(s)
49	selector.X = cause.Args[0]
50
51	// Remove "&" from the receiver (if any).
52	if u, ok := selector.X.(*ast.UnaryExpr); ok && u.Op == token.AND {
53		selector.X = u.X
54	}
55
56	c.ctx.Warn(cause, "consider to change `%s` to `%s`", cause.Fun, selector)
57}
58