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