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.

deep-exit.go 1.9KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package rule
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "github.com/mgechev/revive/lint"
  6. )
  7. // DeepExitRule lints program exit at functions other than main or init.
  8. type DeepExitRule struct{}
  9. // Apply applies the rule to given file.
  10. func (r *DeepExitRule) 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. var exitFunctions = map[string]map[string]bool{
  16. "os": map[string]bool{"Exit": true},
  17. "syscall": map[string]bool{"Exit": true},
  18. "log": map[string]bool{
  19. "Fatal": true,
  20. "Fatalf": true,
  21. "Fatalln": true,
  22. "Panic": true,
  23. "Panicf": true,
  24. "Panicln": true,
  25. },
  26. }
  27. w := lintDeepExit{onFailure, exitFunctions, file.IsTest()}
  28. ast.Walk(w, file.AST)
  29. return failures
  30. }
  31. // Name returns the rule name.
  32. func (r *DeepExitRule) Name() string {
  33. return "deep-exit"
  34. }
  35. type lintDeepExit struct {
  36. onFailure func(lint.Failure)
  37. exitFunctions map[string]map[string]bool
  38. isTestFile bool
  39. }
  40. func (w lintDeepExit) Visit(node ast.Node) ast.Visitor {
  41. if fd, ok := node.(*ast.FuncDecl); ok {
  42. if w.mustIgnore(fd) {
  43. return nil // skip analysis of this function
  44. }
  45. return w
  46. }
  47. se, ok := node.(*ast.ExprStmt)
  48. if !ok {
  49. return w
  50. }
  51. ce, ok := se.X.(*ast.CallExpr)
  52. if !ok {
  53. return w
  54. }
  55. fc, ok := ce.Fun.(*ast.SelectorExpr)
  56. if !ok {
  57. return w
  58. }
  59. id, ok := fc.X.(*ast.Ident)
  60. if !ok {
  61. return w
  62. }
  63. fn := fc.Sel.Name
  64. pkg := id.Name
  65. if w.exitFunctions[pkg] != nil && w.exitFunctions[pkg][fn] { // it's a call to an exit function
  66. w.onFailure(lint.Failure{
  67. Confidence: 1,
  68. Node: ce,
  69. Category: "bad practice",
  70. Failure: fmt.Sprintf("calls to %s.%s only in main() or init() functions", pkg, fn),
  71. })
  72. }
  73. return w
  74. }
  75. func (w *lintDeepExit) mustIgnore(fd *ast.FuncDecl) bool {
  76. fn := fd.Name.Name
  77. return fn == "init" || fn == "main" || (w.isTestFile && fn == "TestMain")
  78. }