gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/depth/godepth.go (about) 1 package depth 2 3 // godepth calculates maximum depth of go methods in Go source code. 4 // 5 // This work was mainly inspired by github.com/fzipp/gocyclo 6 // 7 // Usage: 8 // godepth [<flag> ...] <Go file or directory> ... 9 // 10 // Flags: 11 // -over N show functions with depth > N only and 12 // return exit code 1 if the output is non-empty 13 // -top N show the top N most complex functions only 14 // -avg show the average depth 15 // 16 // The output fields for each line are: 17 // <depth> <package> <function> <file:row:column> 18 19 import ( 20 "fmt" 21 "go/ast" 22 "go/parser" 23 "go/token" 24 "os" 25 "path/filepath" 26 "sort" 27 ) 28 29 const usageDoc = `Calculate maximum depth of Go functions. 30 Usage: 31 godepth [flags...] <Go file or directory> ... 32 33 Flags: 34 -over N show functions with depth > N only and 35 return exit code 1 if the set is non-empty 36 -top N show the top N most complex functions only 37 -avg show the average depth over all functions, 38 not depending on whether -over or -top are set 39 40 The output fields for each line are: 41 <depth> <package> <function> <file:row:column> 42 ` 43 44 func usage() { 45 fmt.Fprintf(os.Stderr, usageDoc) 46 os.Exit(2) 47 } 48 49 var ( 50 over = 0 51 top = 10 52 avg = false 53 ) 54 55 func Depth(packagePath string) ([]string, string) { 56 args := []string{packagePath} 57 if len(args) == 0 { 58 usage() 59 } 60 61 stats := analyze(args) 62 sort.Sort(byDepth(stats)) 63 64 packageAvg := "0" 65 if avg { 66 packageAvg = getAverage(stats) 67 } 68 69 result := make([]string, 0) 70 for _, stat := range stats { 71 result = append(result, stat.String()) 72 } 73 74 return result, packageAvg 75 } 76 77 func analyze(paths []string) []stat { 78 stats := []stat{} 79 for _, path := range paths { 80 if isDir(path) { 81 stats = analyzeDir(path, stats) 82 } else { 83 stats = analyzeFile(path, stats) 84 } 85 } 86 return stats 87 } 88 89 func isDir(filename string) bool { 90 fi, err := os.Stat(filename) 91 return err == nil && fi.IsDir() 92 } 93 94 func analyzeDir(dirname string, stats []stat) []stat { 95 files, _ := filepath.Glob(filepath.Join(dirname, "*.go")) 96 for _, file := range files { 97 stats = analyzeFile(file, stats) 98 } 99 return stats 100 } 101 102 func analyzeFile(fname string, stats []stat) []stat { 103 fset := token.NewFileSet() 104 f, err := parser.ParseFile(fset, fname, nil, 0) 105 if err != nil { 106 exitError(err) 107 } 108 return buildStats(f, fset, stats) 109 } 110 111 func exitError(err error) { 112 fmt.Fprintln(os.Stderr, err) 113 // os.Exit(1) 114 } 115 116 func getAverage(stats []stat) string { 117 return fmt.Sprintf("%.2f", average(stats)) 118 } 119 120 func average(stats []stat) float64 { 121 total := 0 122 for _, s := range stats { 123 total += s.Depth 124 } 125 return float64(total) / float64(len(stats)) 126 } 127 128 type stat struct { 129 PkgName string 130 FuncName string 131 Depth int 132 Pos token.Position 133 } 134 135 func (s stat) String() string { 136 return fmt.Sprintf("%d %s %s %s", s.Depth, s.PkgName, s.FuncName, s.Pos) 137 } 138 139 type byDepth []stat 140 141 func (s byDepth) Len() int { return len(s) } 142 func (s byDepth) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 143 func (s byDepth) Less(i, j int) bool { 144 return s[i].Depth >= s[j].Depth 145 } 146 147 func buildStats(f *ast.File, fset *token.FileSet, stats []stat) []stat { 148 for _, decl := range f.Decls { 149 if fn, ok := decl.(*ast.FuncDecl); ok { 150 stats = append(stats, stat{ 151 PkgName: f.Name.Name, 152 FuncName: funcName(fn), 153 Depth: getdepth(fn), 154 Pos: fset.Position(fn.Pos()), 155 }) 156 } 157 } 158 return stats 159 } 160 161 // funcName returns the name representation of a function or method: 162 // "(Type).Name" for methods or simply "Name" for functions. 163 func funcName(fn *ast.FuncDecl) string { 164 if fn.Recv != nil { 165 if fn.Recv.NumFields() > 0 { 166 typ := fn.Recv.List[0].Type 167 return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name) 168 } 169 } 170 return fn.Name.Name 171 } 172 173 // recvString returns a string representation of recv of the 174 // form "T", "*T", or "BADRECV" (if not a proper receiver type). 175 func recvString(recv ast.Expr) string { 176 switch t := recv.(type) { 177 case *ast.Ident: 178 return t.Name 179 case *ast.StarExpr: 180 return "*" + recvString(t.X) 181 } 182 return "BADRECV" 183 } 184 185 func max(s []int) (m int) { 186 for _, value := range s { 187 if value > m { 188 m = value 189 } 190 } 191 return 192 } 193 194 // getdepth calculates the depth of a function 195 func getdepth(fn *ast.FuncDecl) int { 196 allDepth := []int{} 197 if fn.Body == nil { 198 return 0 199 } 200 for _, lvl := range fn.Body.List { 201 v := maxDepthVisitor{} 202 ast.Walk(&v, lvl) 203 allDepth = append(allDepth, max(v.NodeDepth)) 204 } 205 return max(allDepth) 206 } 207 208 type maxDepthVisitor struct { 209 Depth int 210 NodeDepth []int 211 Lbrace token.Pos 212 Rbrace token.Pos 213 } 214 215 // Visit implements the ast.Visitor interface. 216 // Basically, it counts the number of consecutive brackets 217 // Each time Visit is called, we store the current depth in a slice 218 // When it encounters a sibling eg: if {} followed by if{}, 219 // it saves the current depth and decreases the depth counter 220 func (v *maxDepthVisitor) Visit(node ast.Node) ast.Visitor { 221 switch n := node.(type) { 222 case *ast.BlockStmt: 223 if v.Rbrace == 0 && v.Lbrace == 0 { 224 v.Lbrace = n.Lbrace 225 v.Rbrace = n.Rbrace 226 } 227 228 if n.Lbrace > v.Lbrace && n.Rbrace > v.Rbrace { 229 v.Depth-- 230 } 231 232 v.Lbrace = n.Lbrace 233 v.Rbrace = n.Rbrace 234 v.Depth++ 235 v.NodeDepth = append(v.NodeDepth, v.Depth) 236 } 237 238 return v 239 }