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