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  }