gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/cyclo/cyclo.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 cyclo 20 21 import ( 22 "fmt" 23 "go/ast" 24 "go/parser" 25 "go/token" 26 "io" 27 "log" 28 "os" 29 "path/filepath" 30 "sort" 31 "strings" 32 ) 33 34 const usageDoc = `Calculate cyclomatic complexities of Go functions. 35 usage: 36 gocyclo [<flag> ...] <Go file or directory> ... 37 38 Flags 39 -over N show functions with complexity > N only and 40 return exit code 1 if the set is non-empty 41 -top N show the top N most complex functions only 42 -avg show the average complexity over all functions, 43 not depending on whether -over or -top are set 44 45 The output fields for each line are: 46 <complexity> <package> <function> <file:row:column> 47 ` 48 49 func usage() { 50 fmt.Fprintf(os.Stderr, usageDoc) 51 os.Exit(2) 52 } 53 54 var ( 55 over = 0 56 top = 10 57 avg = true 58 ) 59 60 func Cyclo(packagePath, except string) ([]string, string) { 61 args := []string{packagePath} 62 if len(args) == 0 { 63 usage() 64 } 65 66 stats := analyze(args, except) 67 sort.Sort(byComplexity(stats)) 68 // written := writeStats(os.Stdout, stats) 69 packageAvg := "0" 70 if avg { 71 packageAvg = getAverage(stats) 72 } 73 result := make([]string, 0) 74 75 if over > 0 { 76 return result, packageAvg 77 } 78 79 for _, stat := range stats { 80 result = append(result, stat.String()) 81 } 82 83 return result, packageAvg 84 } 85 86 func analyze(paths []string, except string) []stat { 87 stats := make([]stat, 0) 88 for _, path := range paths { 89 if isDir(path) && !checkExcept(path, except) { 90 stats = analyzeDir(path, stats) 91 } else if !checkExcept(path, except) { 92 stats = analyzeFile(path, stats) 93 } 94 } 95 return stats 96 } 97 98 func checkExcept(path, except string) bool { 99 if except == "" || except == " " { 100 return false 101 } 102 excepts := strings.Split(except, ",") 103 for _, val := range excepts { 104 if val != "" && val != " " { 105 if strings.Contains(path, val) { 106 return true 107 } 108 } 109 } 110 return false 111 } 112 113 func isDir(filename string) bool { 114 fi, err := os.Stat(filename) 115 return err == nil && fi.IsDir() 116 } 117 118 func analyzeFile(fname string, stats []stat) []stat { 119 fset := token.NewFileSet() 120 f, err := parser.ParseFile(fset, fname, nil, 0) 121 if err != nil { 122 exitError(err) 123 } 124 return buildStats(f, fset, stats) 125 } 126 127 func analyzeDir(dirname string, stats []stat) []stat { 128 files, _ := filepath.Glob(filepath.Join(dirname, "*.go")) 129 for _, file := range files { 130 stats = analyzeFile(file, stats) 131 } 132 return stats 133 } 134 135 func exitError(err error) { 136 fmt.Fprintln(os.Stderr, err) 137 // os.Exit(1) 138 } 139 140 func writeStats(w io.Writer, sortedStats []stat) int { 141 for i, stat := range sortedStats { 142 if i == top { 143 return i 144 } 145 if stat.Complexity <= over { 146 return i 147 } 148 fmt.Fprintln(w, stat) 149 } 150 return len(sortedStats) 151 } 152 153 func showAverage(stats []stat) { 154 log.Printf("Average: %.3g\n", average(stats)) 155 } 156 157 func getAverage(stats []stat) string { 158 return fmt.Sprintf("%.2f", average(stats)) 159 } 160 161 func average(stats []stat) float64 { 162 total := 0 163 for _, s := range stats { 164 total += s.Complexity 165 } 166 return float64(total) / float64(len(stats)) 167 } 168 169 type stat struct { 170 PkgName string 171 FuncName string 172 Complexity int 173 Pos token.Position 174 } 175 176 func (s stat) String() string { 177 return fmt.Sprintf("%d %s %s %s", s.Complexity, s.PkgName, s.FuncName, s.Pos) 178 } 179 180 type byComplexity []stat 181 182 func (s byComplexity) Len() int { return len(s) } 183 func (s byComplexity) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 184 func (s byComplexity) Less(i, j int) bool { 185 return s[i].Complexity >= s[j].Complexity 186 } 187 188 func buildStats(f *ast.File, fset *token.FileSet, stats []stat) []stat { 189 for _, decl := range f.Decls { 190 if fn, ok := decl.(*ast.FuncDecl); ok { 191 stats = append(stats, stat{ 192 PkgName: f.Name.Name, 193 FuncName: funcName(fn), 194 Complexity: complexity(fn), 195 Pos: fset.Position(fn.Pos()), 196 }) 197 } 198 } 199 return stats 200 } 201 202 // funcName returns the name representation of a function or method: 203 // "(Type).Name" for methods or simply "Name" for functions. 204 func funcName(fn *ast.FuncDecl) string { 205 if fn.Recv != nil { 206 if fn.Recv.NumFields() > 0 { 207 typ := fn.Recv.List[0].Type 208 return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name) 209 } 210 } 211 return fn.Name.Name 212 } 213 214 // recvString returns a string representation of recv of the 215 // form "T", "*T", or "BADRECV" (if not a proper receiver type). 216 func recvString(recv ast.Expr) string { 217 switch t := recv.(type) { 218 case *ast.Ident: 219 return t.Name 220 case *ast.StarExpr: 221 return "*" + recvString(t.X) 222 } 223 return "BADRECV" 224 } 225 226 // complexity calculates the cyclomatic complexity of a function. 227 func complexity(fn *ast.FuncDecl) int { 228 v := complexityVisitor{} 229 ast.Walk(&v, fn) 230 return v.Complexity 231 } 232 233 type complexityVisitor struct { 234 // Complexity is the cyclomatic complexity 235 Complexity int 236 } 237 238 // Visit implements the ast.Visitor interface. 239 func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor { 240 switch n := n.(type) { 241 case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause: 242 v.Complexity++ 243 case *ast.BinaryExpr: 244 if n.Op == token.LAND || n.Op == token.LOR { 245 v.Complexity++ 246 } 247 } 248 return v 249 }