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.

errorf.go 2.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. package rule
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "regexp"
  6. "strings"
  7. "github.com/mgechev/revive/lint"
  8. )
  9. // ErrorfRule lints given else constructs.
  10. type ErrorfRule struct{}
  11. // Apply applies the rule to given file.
  12. func (r *ErrorfRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
  13. var failures []lint.Failure
  14. fileAst := file.AST
  15. walker := lintErrorf{
  16. file: file,
  17. fileAst: fileAst,
  18. onFailure: func(failure lint.Failure) {
  19. failures = append(failures, failure)
  20. },
  21. }
  22. file.Pkg.TypeCheck()
  23. ast.Walk(walker, fileAst)
  24. return failures
  25. }
  26. // Name returns the rule name.
  27. func (r *ErrorfRule) Name() string {
  28. return "errorf"
  29. }
  30. type lintErrorf struct {
  31. file *lint.File
  32. fileAst *ast.File
  33. onFailure func(lint.Failure)
  34. }
  35. func (w lintErrorf) Visit(n ast.Node) ast.Visitor {
  36. ce, ok := n.(*ast.CallExpr)
  37. if !ok || len(ce.Args) != 1 {
  38. return w
  39. }
  40. isErrorsNew := isPkgDot(ce.Fun, "errors", "New")
  41. var isTestingError bool
  42. se, ok := ce.Fun.(*ast.SelectorExpr)
  43. if ok && se.Sel.Name == "Error" {
  44. if typ := w.file.Pkg.TypeOf(se.X); typ != nil {
  45. isTestingError = typ.String() == "*testing.T"
  46. }
  47. }
  48. if !isErrorsNew && !isTestingError {
  49. return w
  50. }
  51. arg := ce.Args[0]
  52. ce, ok = arg.(*ast.CallExpr)
  53. if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") {
  54. return w
  55. }
  56. errorfPrefix := "fmt"
  57. if isTestingError {
  58. errorfPrefix = w.file.Render(se.X)
  59. }
  60. failure := lint.Failure{
  61. Category: "errors",
  62. Node: n,
  63. Confidence: 1,
  64. Failure: fmt.Sprintf("should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)", w.file.Render(se), errorfPrefix),
  65. }
  66. m := srcLineWithMatch(w.file, ce, `^(.*)`+w.file.Render(se)+`\(fmt\.Sprintf\((.*)\)\)(.*)$`)
  67. if m != nil {
  68. failure.ReplacementLine = m[1] + errorfPrefix + ".Errorf(" + m[2] + ")" + m[3]
  69. }
  70. w.onFailure(failure)
  71. return w
  72. }
  73. func srcLineWithMatch(file *lint.File, node ast.Node, pattern string) (m []string) {
  74. line := srcLine(file.Content(), file.ToPosition(node.Pos()))
  75. line = strings.TrimSuffix(line, "\n")
  76. rx := regexp.MustCompile(pattern)
  77. return rx.FindStringSubmatch(line)
  78. }