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