gitee.com/lonely0422/gometalinter.git@v3.0.1-0.20190307123442-32416ab75314+incompatible/_linters/src/github.com/alecthomas/gocyclo/gocyclo.go (about) 1 // Copyright 2013 Frederik Zipp. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Gocyclo calculates the cyclomatic complexities of functions and 6 // methods in Go source code. 7 // 8 // Usage: 9 // gocyclo [<flag> ...] <Go file or directory> ... 10 // 11 // Flags 12 // -over N show functions with complexity > N only and 13 // return exit code 1 if the output is non-empty 14 // -top N show the top N most complex functions only 15 // -avg show the average complexity 16 // 17 // The output fields for each line are: 18 // <complexity> <package> <function> <file:row:column> 19 package main 20 21 import ( 22 "flag" 23 "fmt" 24 "go/ast" 25 "go/parser" 26 "go/token" 27 "io" 28 "os" 29 "path/filepath" 30 "sort" 31 ) 32 33 const usageDoc = `Calculate cyclomatic complexities of Go functions. 34 usage: 35 gocyclo [<flag> ...] <Go file or directory> ... 36 37 Flags 38 -over N show functions with complexity > N only and 39 return exit code 1 if the set is non-empty 40 -top N show the top N most complex functions only 41 -avg show the average complexity over all functions, 42 not depending on whether -over or -top are set 43 44 The output fields for each line are: 45 <complexity> <package> <function> <file:row:column> 46 ` 47 48 func usage() { 49 fmt.Fprintf(os.Stderr, usageDoc) 50 os.Exit(2) 51 } 52 53 var ( 54 over = flag.Int("over", 0, "show functions with complexity > N only") 55 top = flag.Int("top", -1, "show the top N most complex functions only") 56 avg = flag.Bool("avg", false, "show the average complexity") 57 ) 58 59 func main() { 60 flag.Usage = usage 61 flag.Parse() 62 args := flag.Args() 63 if len(args) == 0 { 64 usage() 65 } 66 67 stats := analyze(args) 68 sort.Sort(byComplexity(stats)) 69 written := writeStats(os.Stdout, stats) 70 71 if *avg { 72 showAverage(stats) 73 } 74 75 if *over > 0 && written > 0 { 76 os.Exit(1) 77 } 78 } 79 80 func analyze(paths []string) []stat { 81 stats := make([]stat, 0) 82 for _, path := range paths { 83 if isDir(path) { 84 stats = analyzeDir(path, stats) 85 } else { 86 stats = analyzeFile(path, stats) 87 } 88 } 89 return stats 90 } 91 92 func isDir(filename string) bool { 93 fi, err := os.Stat(filename) 94 return err == nil && fi.IsDir() 95 } 96 97 func analyzeFile(fname string, stats []stat) []stat { 98 fset := token.NewFileSet() 99 f, err := parser.ParseFile(fset, fname, nil, 0) 100 if err != nil { 101 exitError(err) 102 } 103 return buildStats(f, fset, stats) 104 } 105 106 func analyzeDir(dirname string, stats []stat) []stat { 107 files, _ := filepath.Glob(filepath.Join(dirname, "*.go")) 108 for _, file := range files { 109 stats = analyzeFile(file, stats) 110 } 111 return stats 112 } 113 114 func exitError(err error) { 115 fmt.Fprintln(os.Stderr, err) 116 os.Exit(1) 117 } 118 119 func writeStats(w io.Writer, sortedStats []stat) int { 120 for i, stat := range sortedStats { 121 if i == *top { 122 return i 123 } 124 if stat.Complexity <= *over { 125 return i 126 } 127 fmt.Fprintln(w, stat) 128 } 129 return len(sortedStats) 130 } 131 132 func showAverage(stats []stat) { 133 fmt.Printf("Average: %.3g\n", average(stats)) 134 } 135 136 func average(stats []stat) float64 { 137 total := 0 138 for _, s := range stats { 139 total += s.Complexity 140 } 141 return float64(total) / float64(len(stats)) 142 } 143 144 type stat struct { 145 PkgName string 146 FuncName string 147 Complexity int 148 Pos token.Position 149 } 150 151 func (s stat) String() string { 152 return fmt.Sprintf("%d %s %s %s", s.Complexity, s.PkgName, s.FuncName, s.Pos) 153 } 154 155 type byComplexity []stat 156 157 func (s byComplexity) Len() int { return len(s) } 158 func (s byComplexity) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 159 func (s byComplexity) Less(i, j int) bool { 160 return s[i].Complexity >= s[j].Complexity 161 } 162 163 func buildStats(f *ast.File, fset *token.FileSet, stats []stat) []stat { 164 for _, decl := range f.Decls { 165 if fn, ok := decl.(*ast.FuncDecl); ok { 166 stats = append(stats, stat{ 167 PkgName: f.Name.Name, 168 FuncName: funcName(fn), 169 Complexity: complexity(fn), 170 Pos: fset.Position(fn.Pos()), 171 }) 172 } 173 } 174 return stats 175 } 176 177 // funcName returns the name representation of a function or method: 178 // "(Type).Name" for methods or simply "Name" for functions. 179 func funcName(fn *ast.FuncDecl) string { 180 if fn.Recv != nil { 181 typ := fn.Recv.List[0].Type 182 return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name) 183 } 184 return fn.Name.Name 185 } 186 187 // recvString returns a string representation of recv of the 188 // form "T", "*T", or "BADRECV" (if not a proper receiver type). 189 func recvString(recv ast.Expr) string { 190 switch t := recv.(type) { 191 case *ast.Ident: 192 return t.Name 193 case *ast.StarExpr: 194 return "*" + recvString(t.X) 195 } 196 return "BADRECV" 197 } 198 199 // complexity calculates the cyclomatic complexity of a function. 200 func complexity(fn *ast.FuncDecl) int { 201 v := complexityVisitor{} 202 ast.Walk(&v, fn) 203 return v.Complexity 204 } 205 206 type complexityVisitor struct { 207 // Complexity is the cyclomatic complexity 208 Complexity int 209 } 210 211 // Visit implements the ast.Visitor interface. 212 func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor { 213 switch n := n.(type) { 214 case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause: 215 v.Complexity++ 216 case *ast.BinaryExpr: 217 if n.Op == token.LAND || n.Op == token.LOR { 218 v.Complexity++ 219 } 220 } 221 return v 222 }