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  }