1
0
mirror of https://github.com/mgechev/revive.git synced 2024-12-04 10:24:49 +02:00
revive/rule/cyclomatic.go

138 lines
3.1 KiB
Go
Raw Normal View History

package rule
import (
"fmt"
"go/ast"
"go/token"
2022-04-10 09:06:59 +02:00
"sync"
"github.com/mgechev/revive/lint"
)
// Based on https://github.com/fzipp/gocyclo
2024-12-01 17:44:41 +02:00
// CyclomaticRule sets restriction for maximum cyclomatic complexity.
2021-10-17 20:34:48 +02:00
type CyclomaticRule struct {
maxComplexity int
configureOnce sync.Once
2021-10-17 20:34:48 +02:00
}
const defaultMaxCyclomaticComplexity = 10
2022-04-10 09:06:59 +02:00
func (r *CyclomaticRule) configure(arguments lint.Arguments) {
2024-10-01 12:14:02 +02:00
if len(arguments) < 1 {
r.maxComplexity = defaultMaxCyclomaticComplexity
return
}
complexity, ok := arguments[0].(int64) // Alt. non panicking version
if !ok {
panic(fmt.Sprintf("invalid argument for cyclomatic complexity; expected int but got %T", arguments[0]))
}
2024-10-01 12:14:02 +02:00
r.maxComplexity = int(complexity)
2022-04-10 09:06:59 +02:00
}
// Apply applies the rule to given file.
func (r *CyclomaticRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
r.configureOnce.Do(func() { r.configure(arguments) })
var failures []lint.Failure
fileAst := file.AST
2022-04-10 09:06:59 +02:00
walker := lintCyclomatic{
file: file,
2021-10-17 20:34:48 +02:00
complexity: r.maxComplexity,
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
ast.Walk(walker, fileAst)
return failures
}
// Name returns the rule name.
2022-04-10 11:55:13 +02:00
func (*CyclomaticRule) Name() string {
2018-01-27 06:45:17 +02:00
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 {
2024-10-01 12:14:02 +02:00
fn, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
c := complexity(fn)
if c > w.complexity {
w.onFailure(lint.Failure{
Confidence: 1,
Category: "maintenance",
Failure: fmt.Sprintf("function %s has cyclomatic complexity %d (> max enabled %d)",
funcName(fn), c, w.complexity),
Node: fn,
})
}
}
2024-10-01 12:14:02 +02:00
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 {
2024-10-01 12:14:02 +02:00
declarationHasReceiver := fn.Recv != nil && fn.Recv.NumFields() > 0
if declarationHasReceiver {
typ := fn.Recv.List[0].Type
return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name)
}
2024-10-01 12:14:02 +02:00
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
}