aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/bkielbasa/cyclop/pkg/analyzer/analyzer.go
blob: 1972379df473685b45dc8eacc7f1753e46badfa3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package analyzer

import (
	"flag"
	"go/ast"
	"go/token"
	"strings"

	"golang.org/x/tools/go/analysis"
)

//nolint:gochecknoglobals
var flagSet flag.FlagSet

//nolint:gochecknoglobals
var (
	maxComplexity  int
	packageAverage float64
	skipTests      bool
)

const (
	defaultMaxComplexity = 10
)

//nolint:gochecknoinits
func init() {
	flagSet.IntVar(&maxComplexity, "maxComplexity", defaultMaxComplexity, "max complexity the function can have")
	flagSet.Float64Var(&packageAverage, "packageAverage", 0, "max average complexity in package")
	flagSet.BoolVar(&skipTests, "skipTests", false, "should the linter execute on test files as well")
}

func NewAnalyzer() *analysis.Analyzer {
	return &analysis.Analyzer{
		Name:  "cyclop",
		Doc:   "checks function and package cyclomatic complexity",
		Run:   run,
		Flags: flagSet,
	}
}

func run(pass *analysis.Pass) (interface{}, error) {
	var sum, count float64
	var pkgName string
	var pkgPos token.Pos

	for _, file := range pass.Files {
		ast.Inspect(file, func(node ast.Node) bool {
			funcDecl, ok := node.(*ast.FuncDecl)
			if !ok {
				if node == nil {
					return true
				}
				if file, ok := node.(*ast.File); ok {
					pkgName = file.Name.Name
					pkgPos = node.Pos()
				}
				// we check function by function
				return true
			}

			if skipTests && testFunc(funcDecl) {
				return true
			}

			count++
			comp := complexity(funcDecl)
			sum += float64(comp)
			if comp > maxComplexity {
				pass.Reportf(node.Pos(), "calculated cyclomatic complexity for function %s is %d, max is %d", funcDecl.Name.Name, comp, maxComplexity)
			}

			return true
		})
	}

	if packageAverage > 0 {
		avg := sum / count
		if avg > packageAverage {
			pass.Reportf(pkgPos, "the average complexity for the package %s is %f, max is %f", pkgName, avg, packageAverage)
		}
	}

	return nil, nil
}

func testFunc(f *ast.FuncDecl) bool {
	return strings.HasPrefix(f.Name.Name, "Test")
}

func complexity(fn *ast.FuncDecl) int {
	v := complexityVisitor{}
	ast.Walk(&v, fn)
	return v.Complexity
}

type complexityVisitor struct {
	Complexity int
}

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
}