123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- package rule
-
- import (
- "fmt"
- "go/ast"
- "go/token"
-
- "github.com/mgechev/revive/lint"
- )
-
- // Based on https://github.com/fzipp/gocyclo
-
- // CyclomaticRule lints given else constructs.
- type CyclomaticRule struct{}
-
- // Apply applies the rule to given file.
- func (r *CyclomaticRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
- var failures []lint.Failure
-
- complexity, ok := arguments[0].(int64) // Alt. non panicking version
- if !ok {
- panic("invalid argument for cyclomatic complexity")
- }
-
- fileAst := file.AST
- walker := lintCyclomatic{
- file: file,
- complexity: int(complexity),
- onFailure: func(failure lint.Failure) {
- failures = append(failures, failure)
- },
- }
-
- ast.Walk(walker, fileAst)
-
- return failures
- }
-
- // Name returns the rule name.
- func (r *CyclomaticRule) Name() string {
- return "cyclomatic"
- }
-
- type lintCyclomatic struct {
- file *lint.File
- complexity int
- onFailure func(lint.Failure)
- }
-
- func (w lintCyclomatic) Visit(_ ast.Node) ast.Visitor {
- f := w.file
- for _, decl := range f.AST.Decls {
- if fn, ok := decl.(*ast.FuncDecl); ok {
- c := complexity(fn)
- if c > w.complexity {
- w.onFailure(lint.Failure{
- Confidence: 1,
- Category: "maintenance",
- Failure: fmt.Sprintf("function %s has cyclomatic complexity %d", funcName(fn), c),
- Node: fn,
- })
- }
- }
- }
- return nil
- }
-
- // funcName returns the name representation of a function or method:
- // "(Type).Name" for methods or simply "Name" for functions.
- func funcName(fn *ast.FuncDecl) string {
- if fn.Recv != nil {
- if fn.Recv.NumFields() > 0 {
- typ := fn.Recv.List[0].Type
- return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name)
- }
- }
- return fn.Name.Name
- }
-
- // recvString returns a string representation of recv of the
- // form "T", "*T", or "BADRECV" (if not a proper receiver type).
- func recvString(recv ast.Expr) string {
- switch t := recv.(type) {
- case *ast.Ident:
- return t.Name
- case *ast.StarExpr:
- return "*" + recvString(t.X)
- }
- return "BADRECV"
- }
-
- // complexity calculates the cyclomatic complexity of a function.
- func complexity(fn *ast.FuncDecl) int {
- v := complexityVisitor{}
- ast.Walk(&v, fn)
- return v.Complexity
- }
-
- type complexityVisitor struct {
- // Complexity is the cyclomatic complexity
- Complexity int
- }
-
- // Visit implements the ast.Visitor interface.
- func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor {
- switch n := n.(type) {
- case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause:
- v.Complexity++
- case *ast.BinaryExpr:
- if n.Op == token.LAND || n.Op == token.LOR {
- v.Complexity++
- }
- }
- return v
- }
|