github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/add-constant.go (about) 1 package rule 2 3 import ( 4 "fmt" 5 "go/ast" 6 "strconv" 7 "strings" 8 9 "github.com/mgechev/revive/lint" 10 ) 11 12 const ( 13 defaultStrLitLimit = 2 14 kindFLOAT = "FLOAT" 15 kindINT = "INT" 16 kindSTRING = "STRING" 17 ) 18 19 type whiteList map[string]map[string]bool 20 21 func newWhiteList() whiteList { 22 return map[string]map[string]bool{kindINT: {}, kindFLOAT: {}, kindSTRING: {}} 23 } 24 25 func (wl whiteList) add(kind string, list string) { 26 elems := strings.Split(list, ",") 27 for _, e := range elems { 28 wl[kind][e] = true 29 } 30 } 31 32 // AddConstantRule lints unused params in functions. 33 type AddConstantRule struct{} 34 35 // Apply applies the rule to given file. 36 func (r *AddConstantRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { 37 strLitLimit := defaultStrLitLimit 38 var whiteList = newWhiteList() 39 if len(arguments) > 0 { 40 args, ok := arguments[0].(map[string]interface{}) 41 if !ok { 42 panic(fmt.Sprintf("Invalid argument to the add-constant rule. Expecting a k,v map, got %T", arguments[0])) 43 } 44 for k, v := range args { 45 kind := "" 46 switch k { 47 case "allowFloats": 48 kind = kindFLOAT 49 fallthrough 50 case "allowInts": 51 if kind == "" { 52 kind = kindINT 53 } 54 fallthrough 55 case "allowStrs": 56 if kind == "" { 57 kind = kindSTRING 58 } 59 list, ok := v.(string) 60 if !ok { 61 panic(fmt.Sprintf("Invalid argument to the add-constant rule, string expected. Got '%v' (%T)", v, v)) 62 } 63 whiteList.add(kind, list) 64 case "maxLitCount": 65 sl, ok := v.(string) 66 if !ok { 67 panic(fmt.Sprintf("Invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v' (%T)", v, v)) 68 } 69 70 limit, err := strconv.Atoi(sl) 71 if err != nil { 72 panic(fmt.Sprintf("Invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v'", v)) 73 } 74 strLitLimit = limit 75 } 76 } 77 } 78 79 var failures []lint.Failure 80 81 onFailure := func(failure lint.Failure) { 82 failures = append(failures, failure) 83 } 84 85 w := lintAddConstantRule{onFailure: onFailure, strLits: make(map[string]int, 0), strLitLimit: strLitLimit, whiteLst: whiteList} 86 87 ast.Walk(w, file.AST) 88 89 return failures 90 } 91 92 // Name returns the rule name. 93 func (r *AddConstantRule) Name() string { 94 return "add-constant" 95 } 96 97 type lintAddConstantRule struct { 98 onFailure func(lint.Failure) 99 strLits map[string]int 100 strLitLimit int 101 whiteLst whiteList 102 } 103 104 func (w lintAddConstantRule) Visit(node ast.Node) ast.Visitor { 105 switch n := node.(type) { 106 case *ast.GenDecl: 107 return nil // skip declarations 108 case *ast.BasicLit: 109 switch kind := n.Kind.String(); kind { 110 case kindFLOAT, kindINT: 111 w.checkNumLit(kind, n) 112 case kindSTRING: 113 w.checkStrLit(n) 114 } 115 } 116 117 return w 118 119 } 120 121 func (w lintAddConstantRule) checkStrLit(n *ast.BasicLit) { 122 if w.whiteLst[kindSTRING][n.Value] { 123 return 124 } 125 126 count := w.strLits[n.Value] 127 if count >= 0 { 128 w.strLits[n.Value] = count + 1 129 if w.strLits[n.Value] > w.strLitLimit { 130 w.onFailure(lint.Failure{ 131 Confidence: 1, 132 Node: n, 133 Category: "style", 134 Failure: fmt.Sprintf("string literal %s appears, at least, %d times, create a named constant for it", n.Value, w.strLits[n.Value]), 135 }) 136 w.strLits[n.Value] = -1 // mark it to avoid failing again on the same literal 137 } 138 } 139 } 140 141 func (w lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) { 142 if w.whiteLst[kind][n.Value] { 143 return 144 } 145 146 w.onFailure(lint.Failure{ 147 Confidence: 1, 148 Node: n, 149 Category: "style", 150 Failure: fmt.Sprintf("avoid magic numbers like '%s', create a named constant for it", n.Value), 151 }) 152 }