// Copyright 2013 Frederik Zipp. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Gocyclo calculates the cyclomatic complexities of functions and // methods in Go source code. // // Usage: // gocyclo [ ...] ... // // Flags: // -over N show functions with complexity > N only and // return exit code 1 if the output is non-empty // -top N show the top N most complex functions only // -avg show the average complexity // // The output fields for each line are: // package gocyclo import ( "flag" "fmt" "go/ast" "go/parser" "go/token" "io" "os" "path/filepath" "sort" ) const usageDoc = `Calculate cyclomatic complexities of Go functions. Usage: gocyclo [flags] ... Flags: -over N show functions with complexity > N only and return exit code 1 if the set is non-empty -top N show the top N most complex functions only -avg show the average complexity over all functions, not depending on whether -over or -top are set The output fields for each line are: ` func usage() { fmt.Fprintf(os.Stderr, usageDoc) os.Exit(2) } var ( over = flag.Int("over", 0, "show functions with complexity > N only") top = flag.Int("top", -1, "show the top N most complex functions only") avg = flag.Bool("avg", false, "show the average complexity") ) func Run(paths []string) []Stat { stats := analyze(paths) sort.Sort(byComplexity(stats)) var retStats []Stat for _, s := range stats { retStats = append(retStats, Stat(s)) } return retStats } func analyze(paths []string) []stat { var stats []stat for _, path := range paths { if isDir(path) { stats = analyzeDir(path, stats) } else { stats = analyzeFile(path, stats) } } return stats } func isDir(filename string) bool { fi, err := os.Stat(filename) return err == nil && fi.IsDir() } func analyzeFile(fname string, stats []stat) []stat { fset := token.NewFileSet() f, err := parser.ParseFile(fset, fname, nil, 0) if err != nil { panic(err) } return buildStats(f, fset, stats) } func analyzeDir(dirname string, stats []stat) []stat { files, _ := filepath.Glob(filepath.Join(dirname, "*.go")) for _, f := range files { stats = analyzeFile(f, stats) } return stats } func writeStats(w io.Writer, sortedStats []stat) int { for i, stat := range sortedStats { if i == *top { return i } if stat.Complexity <= *over { return i } fmt.Fprintln(w, stat) } return len(sortedStats) } func showAverage(stats []stat) { fmt.Printf("Average: %.3g\n", average(stats)) } func average(stats []stat) float64 { total := 0 for _, s := range stats { total += s.Complexity } return float64(total) / float64(len(stats)) } type stat struct { PkgName string FuncName string Complexity int Pos token.Position } type Stat stat func (s stat) String() string { return fmt.Sprintf("%d %s %s %s", s.Complexity, s.PkgName, s.FuncName, s.Pos) } type byComplexity []stat func (s byComplexity) Len() int { return len(s) } func (s byComplexity) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s byComplexity) Less(i, j int) bool { return s[i].Complexity >= s[j].Complexity } func buildStats(f *ast.File, fset *token.FileSet, stats []stat) []stat { for _, decl := range f.Decls { if fn, ok := decl.(*ast.FuncDecl); ok { stats = append(stats, stat{ PkgName: f.Name.Name, FuncName: funcName(fn), Complexity: complexity(fn), Pos: fset.Position(fn.Pos()), }) } } return stats } // 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 }