116 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			116 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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
 | |
| }
 |