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.

if-return.go 2.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package rule
  2. import (
  3. "go/ast"
  4. "go/token"
  5. "strings"
  6. "github.com/mgechev/revive/lint"
  7. )
  8. // IfReturnRule lints given else constructs.
  9. type IfReturnRule struct{}
  10. // Apply applies the rule to given file.
  11. func (r *IfReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
  12. var failures []lint.Failure
  13. onFailure := func(failure lint.Failure) {
  14. failures = append(failures, failure)
  15. }
  16. astFile := file.AST
  17. w := &lintElseError{astFile, onFailure}
  18. ast.Walk(w, astFile)
  19. return failures
  20. }
  21. // Name returns the rule name.
  22. func (r *IfReturnRule) Name() string {
  23. return "if-return"
  24. }
  25. type lintElseError struct {
  26. file *ast.File
  27. onFailure func(lint.Failure)
  28. }
  29. func (w *lintElseError) Visit(node ast.Node) ast.Visitor {
  30. switch v := node.(type) {
  31. case *ast.BlockStmt:
  32. for i := 0; i < len(v.List)-1; i++ {
  33. // if var := whatever; var != nil { return var }
  34. s, ok := v.List[i].(*ast.IfStmt)
  35. if !ok || s.Body == nil || len(s.Body.List) != 1 || s.Else != nil {
  36. continue
  37. }
  38. assign, ok := s.Init.(*ast.AssignStmt)
  39. if !ok || len(assign.Lhs) != 1 || !(assign.Tok == token.DEFINE || assign.Tok == token.ASSIGN) {
  40. continue
  41. }
  42. id, ok := assign.Lhs[0].(*ast.Ident)
  43. if !ok {
  44. continue
  45. }
  46. expr, ok := s.Cond.(*ast.BinaryExpr)
  47. if !ok || expr.Op != token.NEQ {
  48. continue
  49. }
  50. if lhs, ok := expr.X.(*ast.Ident); !ok || lhs.Name != id.Name {
  51. continue
  52. }
  53. if rhs, ok := expr.Y.(*ast.Ident); !ok || rhs.Name != "nil" {
  54. continue
  55. }
  56. r, ok := s.Body.List[0].(*ast.ReturnStmt)
  57. if !ok || len(r.Results) != 1 {
  58. continue
  59. }
  60. if r, ok := r.Results[0].(*ast.Ident); !ok || r.Name != id.Name {
  61. continue
  62. }
  63. // return nil
  64. r, ok = v.List[i+1].(*ast.ReturnStmt)
  65. if !ok || len(r.Results) != 1 {
  66. continue
  67. }
  68. if r, ok := r.Results[0].(*ast.Ident); !ok || r.Name != "nil" {
  69. continue
  70. }
  71. // check if there are any comments explaining the construct, don't emit an error if there are some.
  72. if containsComments(s.Pos(), r.Pos(), w.file) {
  73. continue
  74. }
  75. w.onFailure(lint.Failure{
  76. Confidence: .9,
  77. Node: v.List[i],
  78. Failure: "redundant if ...; err != nil check, just return error instead.",
  79. })
  80. }
  81. }
  82. return w
  83. }
  84. func containsComments(start, end token.Pos, f *ast.File) bool {
  85. for _, cgroup := range f.Comments {
  86. comments := cgroup.List
  87. if comments[0].Slash >= end {
  88. // All comments starting with this group are after end pos.
  89. return false
  90. }
  91. if comments[len(comments)-1].Slash < start {
  92. // Comments group ends before start pos.
  93. continue
  94. }
  95. for _, c := range comments {
  96. if start <= c.Slash && c.Slash < end && !strings.HasPrefix(c.Text, "// MATCH ") {
  97. return true
  98. }
  99. }
  100. }
  101. return false
  102. }