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.

defer.go 3.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package rule
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "github.com/mgechev/revive/lint"
  6. )
  7. // DeferRule lints unused params in functions.
  8. type DeferRule struct{}
  9. // Apply applies the rule to given file.
  10. func (r *DeferRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
  11. allow := r.allowFromArgs(arguments)
  12. var failures []lint.Failure
  13. onFailure := func(failure lint.Failure) {
  14. failures = append(failures, failure)
  15. }
  16. w := lintDeferRule{onFailure: onFailure, allow: allow}
  17. ast.Walk(w, file.AST)
  18. return failures
  19. }
  20. // Name returns the rule name.
  21. func (r *DeferRule) Name() string {
  22. return "defer"
  23. }
  24. func (r *DeferRule) allowFromArgs(args lint.Arguments) map[string]bool {
  25. if len(args) < 1 {
  26. allow := map[string]bool{
  27. "loop": true,
  28. "call-chain": true,
  29. "method-call": true,
  30. "return": true,
  31. "recover": true,
  32. }
  33. return allow
  34. }
  35. aa, ok := args[0].([]interface{})
  36. if !ok {
  37. panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting []string, got %T", args[0], args[0]))
  38. }
  39. allow := make(map[string]bool, len(aa))
  40. for _, subcase := range aa {
  41. sc, ok := subcase.(string)
  42. if !ok {
  43. panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting string, got %T", subcase, subcase))
  44. }
  45. allow[sc] = true
  46. }
  47. return allow
  48. }
  49. type lintDeferRule struct {
  50. onFailure func(lint.Failure)
  51. inALoop bool
  52. inADefer bool
  53. inAFuncLit bool
  54. allow map[string]bool
  55. }
  56. func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
  57. switch n := node.(type) {
  58. case *ast.ForStmt:
  59. w.visitSubtree(n.Body, w.inADefer, true, w.inAFuncLit)
  60. return nil
  61. case *ast.RangeStmt:
  62. w.visitSubtree(n.Body, w.inADefer, true, w.inAFuncLit)
  63. return nil
  64. case *ast.FuncLit:
  65. w.visitSubtree(n.Body, w.inADefer, false, true)
  66. return nil
  67. case *ast.ReturnStmt:
  68. if len(n.Results) != 0 && w.inADefer && w.inAFuncLit {
  69. w.newFailure("return in a defer function has no effect", n, 1.0, "logic", "return")
  70. }
  71. case *ast.CallExpr:
  72. if isIdent(n.Fun, "recover") && !w.inADefer {
  73. // confidence is not 1 because recover can be in a function that is deferred elsewhere
  74. w.newFailure("recover must be called inside a deferred function", n, 0.8, "logic", "recover")
  75. }
  76. case *ast.DeferStmt:
  77. w.visitSubtree(n.Call.Fun, true, false, false)
  78. if w.inALoop {
  79. w.newFailure("prefer not to defer inside loops", n, 1.0, "bad practice", "loop")
  80. }
  81. switch fn := n.Call.Fun.(type) {
  82. case *ast.CallExpr:
  83. w.newFailure("prefer not to defer chains of function calls", fn, 1.0, "bad practice", "call-chain")
  84. case *ast.SelectorExpr:
  85. if id, ok := fn.X.(*ast.Ident); ok {
  86. isMethodCall := id != nil && id.Obj != nil && id.Obj.Kind == ast.Typ
  87. if isMethodCall {
  88. w.newFailure("be careful when deferring calls to methods without pointer receiver", fn, 0.8, "bad practice", "method-call")
  89. }
  90. }
  91. }
  92. return nil
  93. }
  94. return w
  95. }
  96. func (w lintDeferRule) visitSubtree(n ast.Node, inADefer, inALoop, inAFuncLit bool) {
  97. nw := &lintDeferRule{
  98. onFailure: w.onFailure,
  99. inADefer: inADefer,
  100. inALoop: inALoop,
  101. inAFuncLit: inAFuncLit,
  102. allow: w.allow}
  103. ast.Walk(nw, n)
  104. }
  105. func (w lintDeferRule) newFailure(msg string, node ast.Node, confidence float64, cat string, subcase string) {
  106. if !w.allow[subcase] {
  107. return
  108. }
  109. w.onFailure(lint.Failure{
  110. Confidence: confidence,
  111. Node: node,
  112. Category: cat,
  113. Failure: msg,
  114. })
  115. }