1package checkers
2
3import (
4	"go/ast"
5	"go/types"
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/astp"
11)
12
13func init() {
14	var info linter.CheckerInfo
15	info.Name = "underef"
16	info.Tags = []string{"style"}
17	info.Params = linter.CheckerParams{
18		"skipRecvDeref": {
19			Value: true,
20			Usage: "whether to skip (*x).method() calls where x is a pointer receiver",
21		},
22	}
23	info.Summary = "Detects dereference expressions that can be omitted"
24	info.Before = `
25(*k).field = 5
26v := (*a)[5] // only if a is array`
27	info.After = `
28k.field = 5
29v := a[5]`
30
31	collection.AddChecker(&info, func(ctx *linter.CheckerContext) linter.FileWalker {
32		c := &underefChecker{ctx: ctx}
33		c.skipRecvDeref = info.Params.Bool("skipRecvDeref")
34		return astwalk.WalkerForExpr(c)
35	})
36}
37
38type underefChecker struct {
39	astwalk.WalkHandler
40	ctx *linter.CheckerContext
41
42	skipRecvDeref bool
43}
44
45func (c *underefChecker) VisitExpr(expr ast.Expr) {
46	switch n := expr.(type) {
47	case *ast.SelectorExpr:
48		expr := astcast.ToParenExpr(n.X)
49		if c.skipRecvDeref && c.isPtrRecvMethodCall(n.Sel) {
50			return
51		}
52
53		if expr, ok := expr.X.(*ast.StarExpr); ok {
54			if c.checkStarExpr(expr) {
55				c.warnSelect(n)
56			}
57		}
58	case *ast.IndexExpr:
59		expr := astcast.ToParenExpr(n.X)
60		if expr, ok := expr.X.(*ast.StarExpr); ok {
61			if !c.checkStarExpr(expr) {
62				return
63			}
64			if c.checkArray(expr) {
65				c.warnArray(n)
66			}
67		}
68	}
69}
70
71func (c *underefChecker) isPtrRecvMethodCall(fn *ast.Ident) bool {
72	typ, ok := c.ctx.TypeOf(fn).(*types.Signature)
73	if ok && typ != nil && typ.Recv() != nil {
74		_, ok := typ.Recv().Type().(*types.Pointer)
75		return ok
76	}
77	return false
78}
79
80func (c *underefChecker) underef(x *ast.ParenExpr) ast.Expr {
81	// If there is only 1 deref, can remove parenthesis,
82	// otherwise can remove StarExpr only.
83	dereferenced := x.X.(*ast.StarExpr).X
84	if astp.IsStarExpr(dereferenced) {
85		return &ast.ParenExpr{X: dereferenced}
86	}
87	return dereferenced
88}
89
90func (c *underefChecker) warnSelect(expr *ast.SelectorExpr) {
91	// TODO: add () to function output.
92	c.ctx.Warn(expr, "could simplify %s to %s.%s",
93		expr,
94		c.underef(expr.X.(*ast.ParenExpr)),
95		expr.Sel.Name)
96}
97
98func (c *underefChecker) warnArray(expr *ast.IndexExpr) {
99	c.ctx.Warn(expr, "could simplify %s to %s[%s]",
100		expr,
101		c.underef(expr.X.(*ast.ParenExpr)),
102		expr.Index)
103}
104
105// checkStarExpr checks if ast.StarExpr could be simplified.
106func (c *underefChecker) checkStarExpr(expr *ast.StarExpr) bool {
107	typ, ok := c.ctx.TypeOf(expr.X).Underlying().(*types.Pointer)
108	if !ok {
109		return false
110	}
111
112	switch typ.Elem().Underlying().(type) {
113	case *types.Pointer, *types.Interface:
114		return false
115	default:
116		return true
117	}
118}
119
120func (c *underefChecker) checkArray(expr *ast.StarExpr) bool {
121	typ, ok := c.ctx.TypeOf(expr.X).(*types.Pointer)
122	if !ok {
123		return false
124	}
125	_, ok = typ.Elem().(*types.Array)
126	return ok
127}
128