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