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.

utils.go 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package rule
  2. import (
  3. "bytes"
  4. "fmt"
  5. "go/ast"
  6. "go/printer"
  7. "go/token"
  8. "go/types"
  9. "regexp"
  10. "strings"
  11. "github.com/mgechev/revive/lint"
  12. )
  13. const styleGuideBase = "https://golang.org/wiki/CodeReviewComments"
  14. // isBlank returns whether id is the blank identifier "_".
  15. // If id == nil, the answer is false.
  16. func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" }
  17. func isTest(f *lint.File) bool {
  18. return strings.HasSuffix(f.Name, "_test.go")
  19. }
  20. var commonMethods = map[string]bool{
  21. "Error": true,
  22. "Read": true,
  23. "ServeHTTP": true,
  24. "String": true,
  25. "Write": true,
  26. }
  27. func receiverType(fn *ast.FuncDecl) string {
  28. switch e := fn.Recv.List[0].Type.(type) {
  29. case *ast.Ident:
  30. return e.Name
  31. case *ast.StarExpr:
  32. if id, ok := e.X.(*ast.Ident); ok {
  33. return id.Name
  34. }
  35. }
  36. // The parser accepts much more than just the legal forms.
  37. return "invalid-type"
  38. }
  39. var knownNameExceptions = map[string]bool{
  40. "LastInsertId": true, // must match database/sql
  41. "kWh": true,
  42. }
  43. func isCgoExported(f *ast.FuncDecl) bool {
  44. if f.Recv != nil || f.Doc == nil {
  45. return false
  46. }
  47. cgoExport := regexp.MustCompile(fmt.Sprintf("(?m)^//export %s$", regexp.QuoteMeta(f.Name.Name)))
  48. for _, c := range f.Doc.List {
  49. if cgoExport.MatchString(c.Text) {
  50. return true
  51. }
  52. }
  53. return false
  54. }
  55. var allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`)
  56. func isIdent(expr ast.Expr, ident string) bool {
  57. id, ok := expr.(*ast.Ident)
  58. return ok && id.Name == ident
  59. }
  60. var zeroLiteral = map[string]bool{
  61. "false": true, // bool
  62. // runes
  63. `'\x00'`: true,
  64. `'\000'`: true,
  65. // strings
  66. `""`: true,
  67. "``": true,
  68. // numerics
  69. "0": true,
  70. "0.": true,
  71. "0.0": true,
  72. "0i": true,
  73. }
  74. func validType(T types.Type) bool {
  75. return T != nil &&
  76. T != types.Typ[types.Invalid] &&
  77. !strings.Contains(T.String(), "invalid type") // good but not foolproof
  78. }
  79. func isPkgDot(expr ast.Expr, pkg, name string) bool {
  80. sel, ok := expr.(*ast.SelectorExpr)
  81. return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name)
  82. }
  83. func srcLine(src []byte, p token.Position) string {
  84. // Run to end of line in both directions if not at line start/end.
  85. lo, hi := p.Offset, p.Offset+1
  86. for lo > 0 && src[lo-1] != '\n' {
  87. lo--
  88. }
  89. for hi < len(src) && src[hi-1] != '\n' {
  90. hi++
  91. }
  92. return string(src[lo:hi])
  93. }
  94. // pick yields a list of nodes by picking them from a sub-ast with root node n.
  95. // Nodes are selected by applying the fselect function
  96. // f function is applied to each selected node before inseting it in the final result.
  97. // If f==nil then it defaults to the identity function (ie it returns the node itself)
  98. func pick(n ast.Node, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node {
  99. var result []ast.Node
  100. if n == nil {
  101. return result
  102. }
  103. if f == nil {
  104. f = func(n ast.Node) []ast.Node { return []ast.Node{n} }
  105. }
  106. onSelect := func(n ast.Node) {
  107. result = append(result, f(n)...)
  108. }
  109. p := picker{fselect: fselect, onSelect: onSelect}
  110. ast.Walk(p, n)
  111. return result
  112. }
  113. func pickFromExpList(l []ast.Expr, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node {
  114. result := make([]ast.Node, 0)
  115. for _, e := range l {
  116. result = append(result, pick(e, fselect, f)...)
  117. }
  118. return result
  119. }
  120. type picker struct {
  121. fselect func(n ast.Node) bool
  122. onSelect func(n ast.Node)
  123. }
  124. func (p picker) Visit(node ast.Node) ast.Visitor {
  125. if p.fselect == nil {
  126. return nil
  127. }
  128. if p.fselect(node) {
  129. p.onSelect(node)
  130. }
  131. return p
  132. }
  133. // isBoolOp returns true if the given token corresponds to
  134. // a bool operator
  135. func isBoolOp(t token.Token) bool {
  136. switch t {
  137. case token.LAND, token.LOR, token.EQL, token.NEQ:
  138. return true
  139. }
  140. return false
  141. }
  142. const (
  143. trueName = "true"
  144. falseName = "false"
  145. )
  146. func isExprABooleanLit(n ast.Node) (lexeme string, ok bool) {
  147. oper, ok := n.(*ast.Ident)
  148. if !ok {
  149. return "", false
  150. }
  151. return oper.Name, (oper.Name == trueName || oper.Name == falseName)
  152. }
  153. // gofmt returns a string representation of an AST subtree.
  154. func gofmt(x interface{}) string {
  155. buf := bytes.Buffer{}
  156. fs := token.NewFileSet()
  157. printer.Fprint(&buf, fs, x)
  158. return buf.String()
  159. }