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.

cyclomatic.go 2.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package rule
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "go/token"
  6. "github.com/mgechev/revive/lint"
  7. )
  8. // Based on https://github.com/fzipp/gocyclo
  9. // CyclomaticRule lints given else constructs.
  10. type CyclomaticRule struct{}
  11. // Apply applies the rule to given file.
  12. func (r *CyclomaticRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
  13. var failures []lint.Failure
  14. complexity, ok := arguments[0].(int64) // Alt. non panicking version
  15. if !ok {
  16. panic("invalid argument for cyclomatic complexity")
  17. }
  18. fileAst := file.AST
  19. walker := lintCyclomatic{
  20. file: file,
  21. complexity: int(complexity),
  22. onFailure: func(failure lint.Failure) {
  23. failures = append(failures, failure)
  24. },
  25. }
  26. ast.Walk(walker, fileAst)
  27. return failures
  28. }
  29. // Name returns the rule name.
  30. func (r *CyclomaticRule) Name() string {
  31. return "cyclomatic"
  32. }
  33. type lintCyclomatic struct {
  34. file *lint.File
  35. complexity int
  36. onFailure func(lint.Failure)
  37. }
  38. func (w lintCyclomatic) Visit(_ ast.Node) ast.Visitor {
  39. f := w.file
  40. for _, decl := range f.AST.Decls {
  41. if fn, ok := decl.(*ast.FuncDecl); ok {
  42. c := complexity(fn)
  43. if c > w.complexity {
  44. w.onFailure(lint.Failure{
  45. Confidence: 1,
  46. Category: "maintenance",
  47. Failure: fmt.Sprintf("function %s has cyclomatic complexity %d", funcName(fn), c),
  48. Node: fn,
  49. })
  50. }
  51. }
  52. }
  53. return nil
  54. }
  55. // funcName returns the name representation of a function or method:
  56. // "(Type).Name" for methods or simply "Name" for functions.
  57. func funcName(fn *ast.FuncDecl) string {
  58. if fn.Recv != nil {
  59. if fn.Recv.NumFields() > 0 {
  60. typ := fn.Recv.List[0].Type
  61. return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name)
  62. }
  63. }
  64. return fn.Name.Name
  65. }
  66. // recvString returns a string representation of recv of the
  67. // form "T", "*T", or "BADRECV" (if not a proper receiver type).
  68. func recvString(recv ast.Expr) string {
  69. switch t := recv.(type) {
  70. case *ast.Ident:
  71. return t.Name
  72. case *ast.StarExpr:
  73. return "*" + recvString(t.X)
  74. }
  75. return "BADRECV"
  76. }
  77. // complexity calculates the cyclomatic complexity of a function.
  78. func complexity(fn *ast.FuncDecl) int {
  79. v := complexityVisitor{}
  80. ast.Walk(&v, fn)
  81. return v.Complexity
  82. }
  83. type complexityVisitor struct {
  84. // Complexity is the cyclomatic complexity
  85. Complexity int
  86. }
  87. // Visit implements the ast.Visitor interface.
  88. func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor {
  89. switch n := n.(type) {
  90. case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause:
  91. v.Complexity++
  92. case *ast.BinaryExpr:
  93. if n.Op == token.LAND || n.Op == token.LOR {
  94. v.Complexity++
  95. }
  96. }
  97. return v
  98. }