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  }