You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

confusing-naming.go 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package rule
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "strings"
  6. "sync"
  7. "github.com/mgechev/revive/lint"
  8. )
  9. type referenceMethod struct {
  10. fileName string
  11. id *ast.Ident
  12. }
  13. type pkgMethods struct {
  14. pkg *lint.Package
  15. methods map[string]map[string]*referenceMethod
  16. mu *sync.Mutex
  17. }
  18. type packages struct {
  19. pkgs []pkgMethods
  20. mu sync.Mutex
  21. }
  22. func (ps *packages) methodNames(lp *lint.Package) pkgMethods {
  23. ps.mu.Lock()
  24. for _, pkg := range ps.pkgs {
  25. if pkg.pkg == lp {
  26. ps.mu.Unlock()
  27. return pkg
  28. }
  29. }
  30. pkgm := pkgMethods{pkg: lp, methods: make(map[string]map[string]*referenceMethod), mu: &sync.Mutex{}}
  31. ps.pkgs = append(ps.pkgs, pkgm)
  32. ps.mu.Unlock()
  33. return pkgm
  34. }
  35. var allPkgs = packages{pkgs: make([]pkgMethods, 1)}
  36. // ConfusingNamingRule lints method names that differ only by capitalization
  37. type ConfusingNamingRule struct{}
  38. // Apply applies the rule to given file.
  39. func (r *ConfusingNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
  40. var failures []lint.Failure
  41. fileAst := file.AST
  42. pkgm := allPkgs.methodNames(file.Pkg)
  43. walker := lintConfusingNames{
  44. fileName: file.Name,
  45. pkgm: pkgm,
  46. onFailure: func(failure lint.Failure) {
  47. failures = append(failures, failure)
  48. },
  49. }
  50. ast.Walk(&walker, fileAst)
  51. return failures
  52. }
  53. // Name returns the rule name.
  54. func (r *ConfusingNamingRule) Name() string {
  55. return "confusing-naming"
  56. }
  57. //checkMethodName checks if a given method/function name is similar (just case differences) to other method/function of the same struct/file.
  58. func checkMethodName(holder string, id *ast.Ident, w *lintConfusingNames) {
  59. if id.Name == "init" && holder == defaultStructName {
  60. // ignore init functions
  61. return
  62. }
  63. pkgm := w.pkgm
  64. name := strings.ToUpper(id.Name)
  65. pkgm.mu.Lock()
  66. defer pkgm.mu.Unlock()
  67. if pkgm.methods[holder] != nil {
  68. if pkgm.methods[holder][name] != nil {
  69. refMethod := pkgm.methods[holder][name]
  70. // confusing names
  71. var kind string
  72. if holder == defaultStructName {
  73. kind = "function"
  74. } else {
  75. kind = "method"
  76. }
  77. var fileName string
  78. if w.fileName == refMethod.fileName {
  79. fileName = "the same source file"
  80. } else {
  81. fileName = refMethod.fileName
  82. }
  83. w.onFailure(lint.Failure{
  84. Failure: fmt.Sprintf("Method '%s' differs only by capitalization to %s '%s' in %s", id.Name, kind, refMethod.id.Name, fileName),
  85. Confidence: 1,
  86. Node: id,
  87. Category: "naming",
  88. })
  89. return
  90. }
  91. } else {
  92. pkgm.methods[holder] = make(map[string]*referenceMethod, 1)
  93. }
  94. // update the black list
  95. if pkgm.methods[holder] == nil {
  96. println("no entry for '", holder, "'")
  97. }
  98. pkgm.methods[holder][name] = &referenceMethod{fileName: w.fileName, id: id}
  99. }
  100. type lintConfusingNames struct {
  101. fileName string
  102. pkgm pkgMethods
  103. onFailure func(lint.Failure)
  104. }
  105. const defaultStructName = "_" // used to map functions
  106. //getStructName of a function receiver. Defaults to defaultStructName
  107. func getStructName(r *ast.FieldList) string {
  108. result := defaultStructName
  109. if r == nil || len(r.List) < 1 {
  110. return result
  111. }
  112. t := r.List[0].Type
  113. if p, _ := t.(*ast.StarExpr); p != nil { // if a pointer receiver => dereference pointer receiver types
  114. t = p.X
  115. }
  116. if p, _ := t.(*ast.Ident); p != nil {
  117. result = p.Name
  118. }
  119. return result
  120. }
  121. func checkStructFields(fields *ast.FieldList, structName string, w *lintConfusingNames) {
  122. bl := make(map[string]bool, len(fields.List))
  123. for _, f := range fields.List {
  124. for _, id := range f.Names {
  125. normName := strings.ToUpper(id.Name)
  126. if bl[normName] {
  127. w.onFailure(lint.Failure{
  128. Failure: fmt.Sprintf("Field '%s' differs only by capitalization to other field in the struct type %s", id.Name, structName),
  129. Confidence: 1,
  130. Node: id,
  131. Category: "naming",
  132. })
  133. } else {
  134. bl[normName] = true
  135. }
  136. }
  137. }
  138. }
  139. func (w *lintConfusingNames) Visit(n ast.Node) ast.Visitor {
  140. switch v := n.(type) {
  141. case *ast.FuncDecl:
  142. // Exclude naming warnings for functions that are exported to C but
  143. // not exported in the Go API.
  144. // See https://github.com/golang/lint/issues/144.
  145. if ast.IsExported(v.Name.Name) || !isCgoExported(v) {
  146. checkMethodName(getStructName(v.Recv), v.Name, w)
  147. }
  148. case *ast.TypeSpec:
  149. if s, ok := v.Type.(*ast.StructType); ok {
  150. checkStructFields(s.Fields, v.Name.Name, w)
  151. }
  152. default:
  153. // will add other checks like field names, struct names, etc.
  154. }
  155. return w
  156. }