names.go 7.3 KB

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