1package checkers 2 3import ( 4 "go/ast" 5 "go/types" 6 "strings" 7 8 "github.com/go-critic/go-critic/checkers/internal/astwalk" 9 "github.com/go-critic/go-critic/checkers/internal/lintutil" 10 "github.com/go-critic/go-critic/framework/linter" 11 "github.com/go-toolsmith/astcast" 12 "github.com/go-toolsmith/astp" 13 "github.com/go-toolsmith/typep" 14) 15 16func init() { 17 var info linter.CheckerInfo 18 info.Name = "mapKey" 19 info.Tags = []string{"diagnostic"} 20 info.Summary = "Detects suspicious map literal keys" 21 info.Before = ` 22_ = map[string]int{ 23 "foo": 1, 24 "bar ": 2, 25}` 26 info.After = ` 27_ = map[string]int{ 28 "foo": 1, 29 "bar": 2, 30}` 31 32 collection.AddChecker(&info, func(ctx *linter.CheckerContext) (linter.FileWalker, error) { 33 return astwalk.WalkerForExpr(&mapKeyChecker{ctx: ctx}), nil 34 }) 35} 36 37type mapKeyChecker struct { 38 astwalk.WalkHandler 39 ctx *linter.CheckerContext 40 41 astSet lintutil.AstSet 42} 43 44func (c *mapKeyChecker) VisitExpr(expr ast.Expr) { 45 lit := astcast.ToCompositeLit(expr) 46 if len(lit.Elts) < 2 { 47 return 48 } 49 50 typ, ok := c.ctx.TypeOf(lit).Underlying().(*types.Map) 51 if !ok { 52 return 53 } 54 if !typep.HasStringKind(typ.Key().Underlying()) { 55 return 56 } 57 58 c.checkWhitespace(lit) 59 c.checkDuplicates(lit) 60} 61 62func (c *mapKeyChecker) checkDuplicates(lit *ast.CompositeLit) { 63 c.astSet.Clear() 64 65 for _, elt := range lit.Elts { 66 kv := astcast.ToKeyValueExpr(elt) 67 if astp.IsBasicLit(kv.Key) { 68 // Basic lits are handled by the compiler. 69 continue 70 } 71 if !typep.SideEffectFree(c.ctx.TypesInfo, kv.Key) { 72 continue 73 } 74 if !c.astSet.Insert(kv.Key) { 75 c.warnDupKey(kv.Key) 76 } 77 } 78} 79 80func (c *mapKeyChecker) checkWhitespace(lit *ast.CompositeLit) { 81 var whitespaceKey ast.Node 82 for _, elt := range lit.Elts { 83 key := astcast.ToBasicLit(astcast.ToKeyValueExpr(elt).Key) 84 if len(key.Value) < len(`" "`) { 85 continue 86 } 87 // s is unquoted string literal value. 88 s := key.Value[len(`"`) : len(key.Value)-len(`"`)] 89 if !strings.Contains(s, " ") { 90 continue 91 } 92 if whitespaceKey != nil { 93 // Already seen something with a whitespace. 94 // More than one entry => not suspicious. 95 return 96 } 97 if s == " " { 98 // If space is used as a key, maybe this map 99 // has something to do with spaces. Give up. 100 return 101 } 102 // Check if it has exactly 1 space prefix or suffix. 103 bad := strings.HasPrefix(s, " ") && !strings.HasPrefix(s, " ") || 104 strings.HasSuffix(s, " ") && !strings.HasSuffix(s, " ") 105 if !bad { 106 // These spaces can be a padding, 107 // or a legitimate part of a key. Give up. 108 return 109 } 110 whitespaceKey = key 111 } 112 113 if whitespaceKey != nil { 114 c.warnWhitespace(whitespaceKey) 115 } 116} 117 118func (c *mapKeyChecker) warnWhitespace(key ast.Node) { 119 c.ctx.Warn(key, "suspucious whitespace in %s key", key) 120} 121 122func (c *mapKeyChecker) warnDupKey(key ast.Node) { 123 c.ctx.Warn(key, "suspicious duplicate %s key", key) 124} 125