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  }