1package analyzer
2
3import (
4	"flag"
5	"go/ast"
6	"go/token"
7	"strings"
8
9	"golang.org/x/tools/go/analysis"
10)
11
12var (
13	flagSet flag.FlagSet
14)
15
16var maxComplexity int
17var packageAverage float64
18var skipTests bool
19
20func NewAnalyzer() *analysis.Analyzer {
21	return &analysis.Analyzer{
22		Name:  "cyclop",
23		Doc:   "calculates cyclomatic complexity",
24		Run:   run,
25		Flags: flagSet,
26	}
27}
28
29func init() {
30	flagSet.IntVar(&maxComplexity, "maxComplexity", 10, "max complexity the function can have")
31	flagSet.Float64Var(&packageAverage, "packageAverage", 0, "max avarage complexity in package")
32	flagSet.BoolVar(&skipTests, "skipTests", false, "should the linter execute on test files as well")
33}
34
35func run(pass *analysis.Pass) (interface{}, error) {
36	var sum, count float64
37	var pkgName string
38	var pkgPos token.Pos
39
40	for _, f := range pass.Files {
41		ast.Inspect(f, func(node ast.Node) bool {
42			f, ok := node.(*ast.FuncDecl)
43			if !ok {
44				if node == nil {
45					return true
46				}
47				if file, ok := node.(*ast.File); ok {
48					pkgName = file.Name.Name
49					pkgPos = node.Pos()
50				}
51				// we check function by function
52				return true
53			}
54
55			if skipTests && testFunc(f) {
56				return true
57			}
58
59			count++
60			comp := complexity(f)
61			sum += float64(comp)
62			if comp > maxComplexity {
63				pass.Reportf(node.Pos(), "calculated cyclomatic complexity for function %s is %d, max is %d", f.Name.Name, comp, maxComplexity)
64			}
65
66			return true
67		})
68	}
69
70	if packageAverage > 0 {
71		avg := sum / count
72		if avg > packageAverage {
73			pass.Reportf(pkgPos, "the avarage complexity for the package %s is %f, max is %f", pkgName, avg, packageAverage)
74		}
75	}
76
77	return nil, nil
78}
79
80func testFunc(f *ast.FuncDecl) bool {
81	return strings.HasPrefix(f.Name.Name, "Test")
82}
83
84func complexity(fn *ast.FuncDecl) int {
85	v := complexityVisitor{}
86	ast.Walk(&v, fn)
87	return v.Complexity
88}
89
90type complexityVisitor struct {
91	Complexity int
92}
93
94func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor {
95	switch n := n.(type) {
96	case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause:
97		v.Complexity++
98	case *ast.BinaryExpr:
99		if n.Op == token.LAND || n.Op == token.LOR {
100			v.Complexity++
101		}
102	}
103	return v
104}
105