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.

modifies-value-receiver.go 2.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. package rule
  2. import (
  3. "go/ast"
  4. "strings"
  5. "github.com/mgechev/revive/lint"
  6. )
  7. // ModifiesValRecRule lints assignments to value method-receivers.
  8. type ModifiesValRecRule struct{}
  9. // Apply applies the rule to given file.
  10. func (r *ModifiesValRecRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
  11. var failures []lint.Failure
  12. onFailure := func(failure lint.Failure) {
  13. failures = append(failures, failure)
  14. }
  15. w := lintModifiesValRecRule{file: file, onFailure: onFailure}
  16. file.Pkg.TypeCheck()
  17. ast.Walk(w, file.AST)
  18. return failures
  19. }
  20. // Name returns the rule name.
  21. func (r *ModifiesValRecRule) Name() string {
  22. return "modifies-value-receiver"
  23. }
  24. type lintModifiesValRecRule struct {
  25. file *lint.File
  26. onFailure func(lint.Failure)
  27. }
  28. func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor {
  29. switch n := node.(type) {
  30. case *ast.FuncDecl:
  31. if n.Recv == nil {
  32. return nil // skip, not a method
  33. }
  34. receiver := n.Recv.List[0]
  35. if _, ok := receiver.Type.(*ast.StarExpr); ok {
  36. return nil // skip, method with pointer receiver
  37. }
  38. if w.skipType(receiver.Type) {
  39. return nil // skip, receiver is a map or array
  40. }
  41. if len(receiver.Names) < 1 {
  42. return nil // skip, anonymous receiver
  43. }
  44. receiverName := receiver.Names[0].Name
  45. if receiverName == "_" {
  46. return nil // skip, anonymous receiver
  47. }
  48. fselect := func(n ast.Node) bool {
  49. // look for assignments with the receiver in the right hand
  50. asgmt, ok := n.(*ast.AssignStmt)
  51. if !ok {
  52. return false
  53. }
  54. for _, exp := range asgmt.Lhs {
  55. switch e := exp.(type) {
  56. case *ast.IndexExpr: // receiver...[] = ...
  57. continue
  58. case *ast.StarExpr: // *receiver = ...
  59. continue
  60. case *ast.SelectorExpr: // receiver.field = ...
  61. name := w.getNameFromExpr(e.X)
  62. if name == "" || name != receiverName {
  63. continue
  64. }
  65. if w.skipType(ast.Expr(e.Sel)) {
  66. continue
  67. }
  68. case *ast.Ident: // receiver := ...
  69. if e.Name != receiverName {
  70. continue
  71. }
  72. default:
  73. continue
  74. }
  75. return true
  76. }
  77. return false
  78. }
  79. assignmentsToReceiver := pick(n.Body, fselect, nil)
  80. for _, assignment := range assignmentsToReceiver {
  81. w.onFailure(lint.Failure{
  82. Node: assignment,
  83. Confidence: 1,
  84. Failure: "suspicious assignment to a by-value method receiver",
  85. })
  86. }
  87. }
  88. return w
  89. }
  90. func (w lintModifiesValRecRule) skipType(t ast.Expr) bool {
  91. rt := w.file.Pkg.TypeOf(t)
  92. if rt == nil {
  93. return false
  94. }
  95. rt = rt.Underlying()
  96. rtName := rt.String()
  97. // skip when receiver is a map or array
  98. return strings.HasPrefix(rtName, "[]") || strings.HasPrefix(rtName, "map[")
  99. }
  100. func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
  101. ident, ok := ie.(*ast.Ident)
  102. if !ok {
  103. return ""
  104. }
  105. return ident.Name
  106. }