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