names.go 7.7 KB

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