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