github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/stylecheck/names.go (about) 1 // Copyright (c) 2013 The Go Authors. All rights reserved. 2 // Copyright (c) 2018 Dominik Honnef. All rights reserved. 3 4 package stylecheck 5 6 import ( 7 "go/ast" 8 "go/token" 9 "strings" 10 "unicode" 11 12 "github.com/golangci/go-tools/lint" 13 . "github.com/golangci/go-tools/lint/lintdsl" 14 ) 15 16 // knownNameExceptions is a set of names that are known to be exempt from naming checks. 17 // This is usually because they are constrained by having to match names in the 18 // standard library. 19 var knownNameExceptions = map[string]bool{ 20 "LastInsertId": true, // must match database/sql 21 "kWh": true, 22 } 23 24 func (c *Checker) CheckNames(j *lint.Job) { 25 // A large part of this function is copied from 26 // github.com/golang/lint, Copyright (c) 2013 The Go Authors, 27 // licensed under the BSD 3-clause license. 28 29 allCaps := func(s string) bool { 30 for _, r := range s { 31 if !((r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') { 32 return false 33 } 34 } 35 return true 36 } 37 38 check := func(id *ast.Ident, thing string, initialisms map[string]bool) { 39 if id.Name == "_" { 40 return 41 } 42 if knownNameExceptions[id.Name] { 43 return 44 } 45 46 // Handle two common styles from other languages that don't belong in Go. 47 if len(id.Name) >= 5 && allCaps(id.Name) && strings.Contains(id.Name, "_") { 48 j.Errorf(id, "should not use ALL_CAPS in Go names; use CamelCase instead") 49 return 50 } 51 52 should := lintName(id.Name, initialisms) 53 if id.Name == should { 54 return 55 } 56 57 if len(id.Name) > 2 && strings.Contains(id.Name[1:len(id.Name)-1], "_") { 58 j.Errorf(id, "should not use underscores in Go names; %s %s should be %s", thing, id.Name, should) 59 return 60 } 61 j.Errorf(id, "%s %s should be %s", thing, id.Name, should) 62 } 63 checkList := func(fl *ast.FieldList, thing string, initialisms map[string]bool) { 64 if fl == nil { 65 return 66 } 67 for _, f := range fl.List { 68 for _, id := range f.Names { 69 check(id, thing, initialisms) 70 } 71 } 72 } 73 74 for _, pkg := range j.Program.InitialPackages { 75 initialisms := make(map[string]bool, len(pkg.Config.Initialisms)) 76 for _, word := range pkg.Config.Initialisms { 77 initialisms[word] = true 78 } 79 for _, f := range pkg.Syntax { 80 // Package names need slightly different handling than other names. 81 if !strings.HasSuffix(f.Name.Name, "_test") && strings.Contains(f.Name.Name, "_") { 82 j.Errorf(f, "should not use underscores in package names") 83 } 84 if strings.IndexFunc(f.Name.Name, unicode.IsUpper) != -1 { 85 j.Errorf(f, "should not use MixedCaps in package name; %s should be %s", f.Name.Name, strings.ToLower(f.Name.Name)) 86 } 87 88 ast.Inspect(f, func(node ast.Node) bool { 89 switch v := node.(type) { 90 case *ast.AssignStmt: 91 if v.Tok != token.DEFINE { 92 return true 93 } 94 for _, exp := range v.Lhs { 95 if id, ok := exp.(*ast.Ident); ok { 96 check(id, "var", initialisms) 97 } 98 } 99 case *ast.FuncDecl: 100 // Functions with no body are defined elsewhere (in 101 // assembly, or via go:linkname). These are likely to 102 // be something very low level (such as the runtime), 103 // where our rules don't apply. 104 if v.Body == nil { 105 return true 106 } 107 108 if IsInTest(j, v) && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) { 109 return true 110 } 111 112 thing := "func" 113 if v.Recv != nil { 114 thing = "method" 115 } 116 117 if !isTechnicallyExported(v) { 118 check(v.Name, thing, initialisms) 119 } 120 121 checkList(v.Type.Params, thing+" parameter", initialisms) 122 checkList(v.Type.Results, thing+" result", initialisms) 123 case *ast.GenDecl: 124 if v.Tok == token.IMPORT { 125 return true 126 } 127 var thing string 128 switch v.Tok { 129 case token.CONST: 130 thing = "const" 131 case token.TYPE: 132 thing = "type" 133 case token.VAR: 134 thing = "var" 135 } 136 for _, spec := range v.Specs { 137 switch s := spec.(type) { 138 case *ast.TypeSpec: 139 check(s.Name, thing, initialisms) 140 case *ast.ValueSpec: 141 for _, id := range s.Names { 142 check(id, thing, initialisms) 143 } 144 } 145 } 146 case *ast.InterfaceType: 147 // Do not check interface method names. 148 // They are often constrainted by the method names of concrete types. 149 for _, x := range v.Methods.List { 150 ft, ok := x.Type.(*ast.FuncType) 151 if !ok { // might be an embedded interface name 152 continue 153 } 154 checkList(ft.Params, "interface method parameter", initialisms) 155 checkList(ft.Results, "interface method result", initialisms) 156 } 157 case *ast.RangeStmt: 158 if v.Tok == token.ASSIGN { 159 return true 160 } 161 if id, ok := v.Key.(*ast.Ident); ok { 162 check(id, "range var", initialisms) 163 } 164 if id, ok := v.Value.(*ast.Ident); ok { 165 check(id, "range var", initialisms) 166 } 167 case *ast.StructType: 168 for _, f := range v.Fields.List { 169 for _, id := range f.Names { 170 check(id, "struct field", initialisms) 171 } 172 } 173 } 174 return true 175 }) 176 } 177 } 178 } 179 180 // lintName returns a different name if it should be different. 181 func lintName(name string, initialisms map[string]bool) (should string) { 182 // A large part of this function is copied from 183 // github.com/golang/lint, Copyright (c) 2013 The Go Authors, 184 // licensed under the BSD 3-clause license. 185 186 // Fast path for simple cases: "_" and all lowercase. 187 if name == "_" { 188 return name 189 } 190 if strings.IndexFunc(name, func(r rune) bool { return !unicode.IsLower(r) }) == -1 { 191 return name 192 } 193 194 // Split camelCase at any lower->upper transition, and split on underscores. 195 // Check each word for common initialisms. 196 runes := []rune(name) 197 w, i := 0, 0 // index of start of word, scan 198 for i+1 <= len(runes) { 199 eow := false // whether we hit the end of a word 200 if i+1 == len(runes) { 201 eow = true 202 } else if runes[i+1] == '_' && i+1 != len(runes)-1 { 203 // underscore; shift the remainder forward over any run of underscores 204 eow = true 205 n := 1 206 for i+n+1 < len(runes) && runes[i+n+1] == '_' { 207 n++ 208 } 209 210 // Leave at most one underscore if the underscore is between two digits 211 if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { 212 n-- 213 } 214 215 copy(runes[i+1:], runes[i+n+1:]) 216 runes = runes[:len(runes)-n] 217 } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { 218 // lower->non-lower 219 eow = true 220 } 221 i++ 222 if !eow { 223 continue 224 } 225 226 // [w,i) is a word. 227 word := string(runes[w:i]) 228 if u := strings.ToUpper(word); initialisms[u] { 229 // Keep consistent case, which is lowercase only at the start. 230 if w == 0 && unicode.IsLower(runes[w]) { 231 u = strings.ToLower(u) 232 } 233 // All the common initialisms are ASCII, 234 // so we can replace the bytes exactly. 235 // TODO(dh): this won't be true once we allow custom initialisms 236 copy(runes[w:], []rune(u)) 237 } else if w > 0 && strings.ToLower(word) == word { 238 // already all lowercase, and not the first word, so uppercase the first character. 239 runes[w] = unicode.ToUpper(runes[w]) 240 } 241 w = i 242 } 243 return string(runes) 244 } 245 246 func isTechnicallyExported(f *ast.FuncDecl) bool { 247 if f.Recv != nil || f.Doc == nil { 248 return false 249 } 250 251 const export = "//export " 252 const linkname = "//go:linkname " 253 for _, c := range f.Doc.List { 254 if strings.HasPrefix(c.Text, export) && len(c.Text) == len(export)+len(f.Name.Name) && c.Text[len(export):] == f.Name.Name { 255 return true 256 } 257 258 if strings.HasPrefix(c.Text, linkname) { 259 return true 260 } 261 } 262 return false 263 }