github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/var-naming.go (about) 1 package rule 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "strings" 8 9 "github.com/mgechev/revive/lint" 10 ) 11 12 // VarNamingRule lints given else constructs. 13 type VarNamingRule struct{} 14 15 // Apply applies the rule to given file. 16 func (r *VarNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { 17 var failures []lint.Failure 18 19 var whitelist []string 20 var blacklist []string 21 22 if len(arguments) >= 1 { 23 whitelist = getList(arguments[0], "whitelist") 24 } 25 26 if len(arguments) >= 2 { 27 blacklist = getList(arguments[1], "blacklist") 28 } 29 30 fileAst := file.AST 31 walker := lintNames{ 32 file: file, 33 fileAst: fileAst, 34 whitelist: whitelist, 35 blacklist: blacklist, 36 onFailure: func(failure lint.Failure) { 37 failures = append(failures, failure) 38 }, 39 } 40 41 // Package names need slightly different handling than other names. 42 if strings.Contains(walker.fileAst.Name.Name, "_") && !strings.HasSuffix(walker.fileAst.Name.Name, "_test") { 43 walker.onFailure(lint.Failure{ 44 Failure: "don't use an underscore in package name", 45 Confidence: 1, 46 Node: walker.fileAst, 47 Category: "naming", 48 }) 49 } 50 51 ast.Walk(&walker, fileAst) 52 53 return failures 54 } 55 56 // Name returns the rule name. 57 func (r *VarNamingRule) Name() string { 58 return "var-naming" 59 } 60 61 func checkList(fl *ast.FieldList, thing string, w *lintNames) { 62 if fl == nil { 63 return 64 } 65 for _, f := range fl.List { 66 for _, id := range f.Names { 67 check(id, thing, w) 68 } 69 } 70 } 71 72 func check(id *ast.Ident, thing string, w *lintNames) { 73 if id.Name == "_" { 74 return 75 } 76 if knownNameExceptions[id.Name] { 77 return 78 } 79 80 // Handle two common styles from other languages that don't belong in Go. 81 if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") { 82 w.onFailure(lint.Failure{ 83 Failure: "don't use ALL_CAPS in Go names; use CamelCase", 84 Confidence: 0.8, 85 Node: id, 86 Category: "naming", 87 }) 88 return 89 } 90 if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' { 91 should := string(id.Name[1]+'a'-'A') + id.Name[2:] 92 w.onFailure(lint.Failure{ 93 Failure: fmt.Sprintf("don't use leading k in Go names; %s %s should be %s", thing, id.Name, should), 94 Confidence: 0.8, 95 Node: id, 96 Category: "naming", 97 }) 98 } 99 100 should := lint.Name(id.Name, w.whitelist, w.blacklist) 101 if id.Name == should { 102 return 103 } 104 105 if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") { 106 w.onFailure(lint.Failure{ 107 Failure: fmt.Sprintf("don't use underscores in Go names; %s %s should be %s", thing, id.Name, should), 108 Confidence: 0.9, 109 Node: id, 110 Category: "naming", 111 }) 112 return 113 } 114 w.onFailure(lint.Failure{ 115 Failure: fmt.Sprintf("%s %s should be %s", thing, id.Name, should), 116 Confidence: 0.8, 117 Node: id, 118 Category: "naming", 119 }) 120 } 121 122 type lintNames struct { 123 file *lint.File 124 fileAst *ast.File 125 lastGen *ast.GenDecl 126 genDeclMissingComments map[*ast.GenDecl]bool 127 onFailure func(lint.Failure) 128 whitelist []string 129 blacklist []string 130 } 131 132 func (w *lintNames) Visit(n ast.Node) ast.Visitor { 133 switch v := n.(type) { 134 case *ast.AssignStmt: 135 if v.Tok == token.ASSIGN { 136 return w 137 } 138 for _, exp := range v.Lhs { 139 if id, ok := exp.(*ast.Ident); ok { 140 check(id, "var", w) 141 } 142 } 143 case *ast.FuncDecl: 144 if w.file.IsTest() && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) { 145 return w 146 } 147 148 thing := "func" 149 if v.Recv != nil { 150 thing = "method" 151 } 152 153 // Exclude naming warnings for functions that are exported to C but 154 // not exported in the Go API. 155 // See https://github.com/golang/lint/issues/144. 156 if ast.IsExported(v.Name.Name) || !isCgoExported(v) { 157 check(v.Name, thing, w) 158 } 159 160 checkList(v.Type.Params, thing+" parameter", w) 161 checkList(v.Type.Results, thing+" result", w) 162 case *ast.GenDecl: 163 if v.Tok == token.IMPORT { 164 return w 165 } 166 var thing string 167 switch v.Tok { 168 case token.CONST: 169 thing = "const" 170 case token.TYPE: 171 thing = "type" 172 case token.VAR: 173 thing = "var" 174 } 175 for _, spec := range v.Specs { 176 switch s := spec.(type) { 177 case *ast.TypeSpec: 178 check(s.Name, thing, w) 179 case *ast.ValueSpec: 180 for _, id := range s.Names { 181 check(id, thing, w) 182 } 183 } 184 } 185 case *ast.InterfaceType: 186 // Do not check interface method names. 187 // They are often constrainted by the method names of concrete types. 188 for _, x := range v.Methods.List { 189 ft, ok := x.Type.(*ast.FuncType) 190 if !ok { // might be an embedded interface name 191 continue 192 } 193 checkList(ft.Params, "interface method parameter", w) 194 checkList(ft.Results, "interface method result", w) 195 } 196 case *ast.RangeStmt: 197 if v.Tok == token.ASSIGN { 198 return w 199 } 200 if id, ok := v.Key.(*ast.Ident); ok { 201 check(id, "range var", w) 202 } 203 if id, ok := v.Value.(*ast.Ident); ok { 204 check(id, "range var", w) 205 } 206 case *ast.StructType: 207 for _, f := range v.Fields.List { 208 for _, id := range f.Names { 209 check(id, "struct field", w) 210 } 211 } 212 } 213 return w 214 } 215 216 func getList(arg interface{}, argName string) []string { 217 temp, ok := arg.([]interface{}) 218 if !ok { 219 panic(fmt.Sprintf("Invalid argument to the var-naming rule. Expecting a %s of type slice with initialisms, got %T", argName, arg)) 220 } 221 var list []string 222 for _, v := range temp { 223 if val, ok := v.(string); ok { 224 list = append(list, val) 225 } else { 226 panic(fmt.Sprintf("Invalid %s values of the var-naming rule. Expecting slice of strings but got element of type %T", val, arg)) 227 } 228 } 229 return list 230 }