gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/flen/flen.go (about) 1 package flen 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/parser" 7 "go/token" 8 "os" 9 "sort" 10 "strconv" 11 "strings" 12 ) 13 14 type funcLen struct { 15 Name string 16 Size int // len of function 17 Filepath string 18 Lbrace, Rbrace int 19 Type funcType 20 } 21 22 // Options provides the user of this package to configre it 23 // for according to his/her needs. 24 type Options struct { 25 IncludeTests bool 26 BucketSize int 27 } 28 29 type funcType int 30 31 const ( 32 // Sentinel defines max length of func this pkg can handle 33 Sentinel = 1000000 34 defaultBucketSize = 5 35 defaultIncludeTests = false 36 streakChar = "∎" 37 lenLowerLimit = 0 38 lenUpperLimit = Sentinel 39 implemented funcType = iota 40 implementedAtRuntime 41 ) 42 43 var ( 44 goroot = os.Getenv("GOROOT") 45 pkgpath string 46 gopath = os.Getenv("GOPATH") 47 opts *Options 48 ) 49 50 func rangeAsked(ll, ul int) bool { return ll != 0 || ul != Sentinel } 51 52 func FuncLen(packageName string) (lengAll []int, data [][]string) { 53 if packageName == "" { 54 return 55 } 56 57 flenOptions := &Options{ 58 IncludeTests: false, 59 BucketSize: 5, 60 } 61 flens, _, err := GenerateFuncLens(packageName, flenOptions) 62 if err != nil { 63 fmt.Println(err) 64 return 65 } 66 67 lengAll = flens.computeHistogram() 68 69 for i, f := range flens { 70 data = append(data, []string{strconv.Itoa(i), f.Name, f.Filepath, strconv.Itoa(f.Lbrace), strconv.Itoa(f.Size)}) 71 } 72 return 73 } 74 75 // FuncLens is the main object which flen pkg exposes to client. 76 // All the operation are done on this. 77 type FuncLens []funcLen 78 79 // Sort interface is implemented for FuncLens type. 80 // Sorting is needed for computing percentiles. 81 func (flens *FuncLens) Len() int { return len(*flens) } 82 func (flens *FuncLens) Less(i, j int) bool { 83 switch { 84 case (*flens)[i].Size < (*flens)[j].Size: 85 return true 86 case (*flens)[i].Size > (*flens)[j].Size: 87 return false 88 case strings.Compare((*flens)[i].Name, (*flens)[j].Name) == -1: 89 return true 90 case strings.Compare((*flens)[i].Name, (*flens)[j].Name) == 1: 91 return false 92 } 93 return false 94 } 95 func (flens *FuncLens) Swap(i, j int) { (*flens)[i], (*flens)[j] = (*flens)[j], (*flens)[i] } 96 97 // createHistogram computes and returns slice of histogram data points. 98 func (flens *FuncLens) computeHistogram() []int { 99 var hg []int 100 var x int 101 if len(*flens) == 0 { 102 return nil 103 } 104 // find max func len 105 var maxFlen int = -Sentinel 106 for _, flen := range *flens { 107 if flen.Size > maxFlen { 108 maxFlen = flen.Size 109 } 110 } 111 hglen := maxFlen / opts.BucketSize 112 hg = make([]int, hglen+1) 113 for _, v := range *flens { 114 if v.Size > 0 { 115 x = v.Size % opts.BucketSize 116 if x == 0 { 117 x = v.Size/opts.BucketSize - 1 118 } else { 119 x = v.Size / opts.BucketSize 120 } 121 hg[x]++ 122 } 123 } 124 return hg 125 } 126 127 // GenerateFuncLens generates FuncLens for the given package. If options.InclTests is true, 128 // functions in tests are also evaluated. For ease in readibility of func lens in table, 129 // result is sorted. 130 func GenerateFuncLens(pkg string, options *Options) (FuncLens, string, error) { 131 opts = options 132 if opts == nil { 133 opts = &Options{ 134 IncludeTests: defaultIncludeTests, 135 BucketSize: defaultBucketSize, 136 } 137 } 138 pkgpath, err := getPkgPath(pkg) 139 if err != nil { 140 return nil, pkgpath, err 141 } 142 143 fset := token.NewFileSet() 144 pkgs, ferr := parser.ParseDir(fset, pkgpath, func(f os.FileInfo) bool { 145 if opts.IncludeTests { 146 return true 147 } 148 return !strings.HasSuffix(f.Name(), "_test.go") 149 }, parser.AllErrors) 150 if ferr != nil { 151 panic(ferr) 152 } 153 flens := make(FuncLens, 0) 154 for _, v := range pkgs { 155 for filepath, astf := range v.Files { 156 for _, decl := range astf.Decls { 157 ast.Inspect(decl, func(node ast.Node) bool { 158 var ( 159 funcname string 160 diff int 161 lb, rb token.Pos 162 rln, lln int 163 ftype funcType 164 ) 165 166 if x, ok := node.(*ast.FuncDecl); ok { 167 ftype = implemented 168 funcname = x.Name.Name 169 if x.Body == nil { 170 ftype = implementedAtRuntime // externally implemented 171 } else { 172 lb = x.Body.Lbrace 173 rb = x.Body.Rbrace 174 if !lb.IsValid() || !rb.IsValid() { 175 return false 176 } 177 rln = fset.Position(rb).Line 178 lln = fset.Position(lb).Line 179 diff = rln - lln - 1 180 if diff == -1 { 181 diff = 1 // single line func 182 } 183 } 184 flens = append(flens, funcLen{ 185 Name: funcname, 186 Size: diff, 187 Filepath: filepath, 188 Lbrace: lln, 189 Rbrace: rln, 190 Type: ftype, 191 }) 192 } 193 return false 194 195 }) 196 } 197 } 198 } 199 sort.Sort(&flens) 200 return flens, pkgpath, nil 201 } 202 203 // getPkgPath tries to get path of pkg. Path is platform dependent. 204 // First pkg is checked in GOPATH, then in GOROOT, then err. 205 func getPkgPath(pkgname string) (string, *os.PathError) { 206 var ppath string 207 if gopath != "" { 208 for _, godir := range strings.Split(gopath, string(os.PathListSeparator)) { 209 ppath = strings.Join([]string{godir, "src", pkgname}, string(os.PathSeparator)) 210 _, err := os.Stat(ppath) 211 if err != nil { 212 continue 213 } 214 return ppath, nil 215 } 216 } 217 ppath = strings.Join([]string{goroot, "src", pkgname}, string(os.PathSeparator)) 218 _, err := os.Stat(ppath) 219 if err != nil { 220 return "", err.(*os.PathError) 221 } 222 return ppath, nil 223 }