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/astequal" 12) 13 14func init() { 15 var info linter.CheckerInfo 16 info.Name = "sloppyReassign" 17 info.Tags = []string{"diagnostic", "experimental"} 18 info.Summary = "Detects suspicious/confusing re-assignments" 19 info.Before = `if err = f(); err != nil { return err }` 20 info.After = `if err := f(); err != nil { return err }` 21 22 collection.AddChecker(&info, func(ctx *linter.CheckerContext) linter.FileWalker { 23 return astwalk.WalkerForStmt(&sloppyReassignChecker{ctx: ctx}) 24 }) 25} 26 27type sloppyReassignChecker struct { 28 astwalk.WalkHandler 29 ctx *linter.CheckerContext 30} 31 32func (c *sloppyReassignChecker) VisitStmt(stmt ast.Stmt) { 33 // Right now only check assignments in if statements init. 34 ifStmt := astcast.ToIfStmt(stmt) 35 assign := astcast.ToAssignStmt(ifStmt.Init) 36 if assign.Tok != token.ASSIGN { 37 return 38 } 39 40 // TODO(quasilyte): is handling of multi-value assignments worthwhile? 41 if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { 42 return 43 } 44 45 // TODO(quasilyte): handle not only the simplest, return-only case. 46 body := ifStmt.Body.List 47 if len(body) != 1 { 48 return 49 } 50 51 // Variable that is being re-assigned. 52 reAssigned := astcast.ToIdent(assign.Lhs[0]) 53 if reAssigned.Name == "" { 54 return 55 } 56 57 // TODO(quasilyte): handle not only nil comparisons. 58 eqToNil := &ast.BinaryExpr{ 59 Op: token.NEQ, 60 X: reAssigned, 61 Y: &ast.Ident{Name: "nil"}, 62 } 63 if !astequal.Expr(ifStmt.Cond, eqToNil) { 64 return 65 } 66 67 results := astcast.ToReturnStmt(body[0]).Results 68 for _, res := range results { 69 if astequal.Expr(reAssigned, res) { 70 c.warnAssignToDefine(assign, reAssigned.Name) 71 break 72 } 73 } 74} 75 76func (c *sloppyReassignChecker) warnAssignToDefine(assign *ast.AssignStmt, name string) { 77 suggest := astcopy.AssignStmt(assign) 78 suggest.Tok = token.DEFINE 79 c.ctx.Warn(assign, "re-assignment to `%s` can be replaced with `%s`", name, suggest) 80} 81