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