1package checkers 2 3import ( 4 "go/ast" 5 "go/constant" 6 "go/types" 7 "strings" 8 9 "github.com/go-critic/go-critic/checkers/internal/astwalk" 10 "github.com/go-critic/go-critic/framework/linter" 11 "github.com/go-toolsmith/astcast" 12) 13 14func init() { 15 var info linter.CheckerInfo 16 info.Name = "flagName" 17 info.Tags = []string{"diagnostic"} 18 info.Summary = "Detects suspicious flag names" 19 info.Before = `b := flag.Bool(" foo ", false, "description")` 20 info.After = `b := flag.Bool("foo", false, "description")` 21 info.Note = "https://github.com/golang/go/issues/41792" 22 23 collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { 24 return astwalk.WalkerForExpr(&flagNameChecker{ctx: ctx}), nil 25 }) 26} 27 28type flagNameChecker struct { 29 astwalk.WalkHandler 30 ctx *linter.CheckerContext 31} 32 33func (c *flagNameChecker) VisitExpr(expr ast.Expr) { 34 call := astcast.ToCallExpr(expr) 35 calledExpr := astcast.ToSelectorExpr(call.Fun) 36 obj, ok := c.ctx.TypesInfo.ObjectOf(astcast.ToIdent(calledExpr.X)).(*types.PkgName) 37 if !ok { 38 return 39 } 40 sym := calledExpr.Sel 41 pkg := obj.Imported() 42 if pkg.Path() != "flag" { 43 return 44 } 45 46 switch sym.Name { 47 case "Bool", "Duration", "Float64", "String", 48 "Int", "Int64", "Uint", "Uint64": 49 c.checkFlagName(call, call.Args[0]) 50 case "BoolVar", "DurationVar", "Float64Var", "StringVar", 51 "IntVar", "Int64Var", "UintVar", "Uint64Var": 52 c.checkFlagName(call, call.Args[1]) 53 } 54} 55 56func (c *flagNameChecker) checkFlagName(call *ast.CallExpr, arg ast.Expr) { 57 cv := c.ctx.TypesInfo.Types[arg].Value 58 if cv == nil { 59 return // Non-constant name 60 } 61 name := constant.StringVal(cv) 62 switch { 63 case name == "": 64 c.warnEmpty(call) 65 case strings.HasPrefix(name, "-"): 66 c.warnHypenPrefix(call, name) 67 case strings.Contains(name, "="): 68 c.warnEq(call, name) 69 case strings.Contains(name, " "): 70 c.warnWhitespace(call, name) 71 } 72} 73 74func (c *flagNameChecker) warnEmpty(cause ast.Node) { 75 c.ctx.Warn(cause, "empty flag name") 76} 77 78func (c *flagNameChecker) warnHypenPrefix(cause ast.Node, name string) { 79 c.ctx.Warn(cause, "flag name %q should not start with a hypen", name) 80} 81 82func (c *flagNameChecker) warnEq(cause ast.Node, name string) { 83 c.ctx.Warn(cause, "flag name %q should not contain '='", name) 84} 85 86func (c *flagNameChecker) warnWhitespace(cause ast.Node, name string) { 87 c.ctx.Warn(cause, "flag name %q contains whitespace", name) 88} 89