From f6b51b933e360adab4cc426956be8975c94ec3e0 Mon Sep 17 00:00:00 2001 From: golangci Date: Mon, 28 May 2018 17:36:16 +0300 Subject: [PATCH] use gocyclo with changes for upstreaming, also speedup it 10x when program is loaded --- Gopkg.lock | 6 +- Gopkg.toml | 2 +- pkg/astcache/astcache.go | 71 ++++++ pkg/commands/run.go | 9 + pkg/golinters/context.go | 2 + pkg/golinters/gocyclo.go | 12 +- vendor/github.com/golangci/gocyclo/README.md | 31 --- vendor/github.com/golangci/gocyclo/gocyclo.go | 212 ------------------ .../golangci/gocyclo/pkg/gocyclo/gocyclo.go | 82 +++++++ 9 files changed, 178 insertions(+), 249 deletions(-) create mode 100644 pkg/astcache/astcache.go delete mode 100644 vendor/github.com/golangci/gocyclo/README.md delete mode 100644 vendor/github.com/golangci/gocyclo/gocyclo.go create mode 100644 vendor/github.com/golangci/gocyclo/pkg/gocyclo/gocyclo.go diff --git a/Gopkg.lock b/Gopkg.lock index 4c024f28..0d484792 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -102,8 +102,8 @@ [[projects]] branch = "master" name = "github.com/golangci/gocyclo" - packages = ["."] - revision = "687488c898ab673ee751943f7bcab53ce2371985" + packages = ["pkg/gocyclo"] + revision = "2becd97e67eef01fc0bf8d7b9ddfce97da10a423" [[projects]] branch = "master" @@ -410,6 +410,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "86deafc3c3aea3a6a1437dbf35545c9d4e1f9b510f5fe21b9ef434a465d377dc" + inputs-digest = "8cfc9eb6d890be05e9a019a4537c3fba5bcaf25a396437d0a8b402f6cc244d2e" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 4680ee97..4ca2cf7b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -109,6 +109,6 @@ branch = "master" name = "github.com/golangci/ineffassign" -[[constraint]] +[[override]] branch = "master" name = "github.com/golangci/lint" diff --git a/pkg/astcache/astcache.go b/pkg/astcache/astcache.go new file mode 100644 index 00000000..0f44151c --- /dev/null +++ b/pkg/astcache/astcache.go @@ -0,0 +1,71 @@ +package astcache + +import ( + "go/ast" + "go/parser" + "go/token" + + "golang.org/x/tools/go/loader" +) + +type File struct { + F *ast.File + Fset *token.FileSet + err error +} + +type Cache struct { + m map[string]*File + s []*File +} + +func (c Cache) GetAllValidFiles() []*File { + return c.s +} + +func (c *Cache) prepareValidFiles() { + files := make([]*File, 0, len(c.m)) + for _, f := range c.m { + if f.err != nil || f.F == nil { + continue + } + files = append(files, f) + } + c.s = files +} + +func LoadFromProgram(prog *loader.Program) *Cache { + c := &Cache{ + m: map[string]*File{}, + } + for _, pkg := range prog.InitialPackages() { + for _, f := range pkg.Files { + pos := prog.Fset.Position(0) + c.m[pos.Filename] = &File{ + F: f, + Fset: prog.Fset, + } + } + } + + c.prepareValidFiles() + return c +} + +func LoadFromFiles(files []string) *Cache { + c := &Cache{ + m: map[string]*File{}, + } + fset := token.NewFileSet() + for _, filePath := range files { + f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) // comments needed by e.g. golint + c.m[filePath] = &File{ + F: f, + Fset: fset, + err: err, + } + } + + c.prepareValidFiles() + return c +} diff --git a/pkg/commands/run.go b/pkg/commands/run.go index ef489f8c..b5a5ea0d 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -16,6 +16,7 @@ import ( "github.com/golangci/go-tools/ssa" "github.com/golangci/go-tools/ssa/ssautil" "github.com/golangci/golangci-lint/pkg" + "github.com/golangci/golangci-lint/pkg/astcache" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/fsutils" "github.com/golangci/golangci-lint/pkg/golinters" @@ -195,12 +196,20 @@ func buildLintCtx(ctx context.Context, linters []pkg.Linter, cfg *config.Config) ssaProg = buildSSAProgram(ctx, prog) } + var astCache *astcache.Cache + if prog != nil { + astCache = astcache.LoadFromProgram(prog) + } else { + astCache = astcache.LoadFromFiles(paths.Files) + } + return &golinters.Context{ Paths: paths, Cfg: cfg, Program: prog, SSAProgram: ssaProg, LoaderConfig: loaderConfig, + ASTCache: astCache, }, nil } diff --git a/pkg/golinters/context.go b/pkg/golinters/context.go index 11ae1612..749debed 100644 --- a/pkg/golinters/context.go +++ b/pkg/golinters/context.go @@ -2,6 +2,7 @@ package golinters import ( "github.com/golangci/go-tools/ssa" + "github.com/golangci/golangci-lint/pkg/astcache" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/fsutils" "golang.org/x/tools/go/loader" @@ -13,6 +14,7 @@ type Context struct { Program *loader.Program SSAProgram *ssa.Program LoaderConfig *loader.Config + ASTCache *astcache.Cache } func (c *Context) Settings() *config.LintersSettings { diff --git a/pkg/golinters/gocyclo.go b/pkg/golinters/gocyclo.go index 2abeaeaa..63fa1d47 100644 --- a/pkg/golinters/gocyclo.go +++ b/pkg/golinters/gocyclo.go @@ -3,8 +3,9 @@ package golinters import ( "context" "fmt" + "sort" - gocycloAPI "github.com/golangci/gocyclo" + gocycloAPI "github.com/golangci/gocyclo/pkg/gocyclo" "github.com/golangci/golangci-lint/pkg/result" ) @@ -19,7 +20,14 @@ func (Gocyclo) Desc() string { } func (g Gocyclo) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { - stats := gocycloAPI.Run(lintCtx.Paths.MixedPaths()) + var stats []gocycloAPI.Stat + for _, f := range lintCtx.ASTCache.GetAllValidFiles() { + stats = gocycloAPI.BuildStats(f.F, f.Fset, stats) + } + + sort.Slice(stats, func(i, j int) bool { + return stats[i].Complexity > stats[j].Complexity + }) var res []result.Issue for _, s := range stats { diff --git a/vendor/github.com/golangci/gocyclo/README.md b/vendor/github.com/golangci/gocyclo/README.md deleted file mode 100644 index b2ec36a7..00000000 --- a/vendor/github.com/golangci/gocyclo/README.md +++ /dev/null @@ -1,31 +0,0 @@ -Gocyclo calculates cyclomatic complexities of functions in Go source code. - -The cyclomatic complexity of a function is calculated according to the -following rules: - - 1 is the base complexity of a function - +1 for each 'if', 'for', 'case', '&&' or '||' - -To install, run - - $ go get github.com/fzipp/gocyclo - -and put the resulting binary in one of your PATH directories if -`$GOPATH/bin` isn't already in your PATH. - -Usage: - - $ gocyclo [ ...] ... - -Examples: - - $ gocyclo . - $ gocyclo main.go - $ gocyclo -top 10 src/ - $ gocyclo -over 25 docker - $ gocyclo -avg . - -The output fields for each line are: - - - diff --git a/vendor/github.com/golangci/gocyclo/gocyclo.go b/vendor/github.com/golangci/gocyclo/gocyclo.go deleted file mode 100644 index c376f21e..00000000 --- a/vendor/github.com/golangci/gocyclo/gocyclo.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2013 Frederik Zipp. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Gocyclo calculates the cyclomatic complexities of functions and -// methods in Go source code. -// -// Usage: -// gocyclo [ ...] ... -// -// Flags: -// -over N show functions with complexity > N only and -// return exit code 1 if the output is non-empty -// -top N show the top N most complex functions only -// -avg show the average complexity -// -// The output fields for each line are: -// -package gocyclo - -import ( - "flag" - "fmt" - "go/ast" - "go/parser" - "go/token" - "io" - "os" - "path/filepath" - "sort" -) - -const usageDoc = `Calculate cyclomatic complexities of Go functions. -Usage: - gocyclo [flags] ... - -Flags: - -over N show functions with complexity > N only and - return exit code 1 if the set is non-empty - -top N show the top N most complex functions only - -avg show the average complexity over all functions, - not depending on whether -over or -top are set - -The output fields for each line are: - -` - -func usage() { - fmt.Fprintf(os.Stderr, usageDoc) - os.Exit(2) -} - -var ( - over = flag.Int("over", 0, "show functions with complexity > N only") - top = flag.Int("top", -1, "show the top N most complex functions only") - avg = flag.Bool("avg", false, "show the average complexity") -) - -func Run(paths []string) []Stat { - stats := analyze(paths) - sort.Sort(byComplexity(stats)) - - var retStats []Stat - for _, s := range stats { - retStats = append(retStats, Stat(s)) - } - - return retStats -} - -func analyze(paths []string) []stat { - var stats []stat - for _, path := range paths { - if isDir(path) { - stats = analyzeDir(path, stats) - } else { - stats = analyzeFile(path, stats) - } - } - return stats -} - -func isDir(filename string) bool { - fi, err := os.Stat(filename) - return err == nil && fi.IsDir() -} - -func analyzeFile(fname string, stats []stat) []stat { - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, fname, nil, 0) - if err != nil { - panic(err) - } - return buildStats(f, fset, stats) -} - -func analyzeDir(dirname string, stats []stat) []stat { - files, _ := filepath.Glob(filepath.Join(dirname, "*.go")) - for _, f := range files { - stats = analyzeFile(f, stats) - } - return stats -} - -func writeStats(w io.Writer, sortedStats []stat) int { - for i, stat := range sortedStats { - if i == *top { - return i - } - if stat.Complexity <= *over { - return i - } - fmt.Fprintln(w, stat) - } - return len(sortedStats) -} - -func showAverage(stats []stat) { - fmt.Printf("Average: %.3g\n", average(stats)) -} - -func average(stats []stat) float64 { - total := 0 - for _, s := range stats { - total += s.Complexity - } - return float64(total) / float64(len(stats)) -} - -type stat struct { - PkgName string - FuncName string - Complexity int - Pos token.Position -} - -type Stat stat - -func (s stat) String() string { - return fmt.Sprintf("%d %s %s %s", s.Complexity, s.PkgName, s.FuncName, s.Pos) -} - -type byComplexity []stat - -func (s byComplexity) Len() int { return len(s) } -func (s byComplexity) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s byComplexity) Less(i, j int) bool { - return s[i].Complexity >= s[j].Complexity -} - -func buildStats(f *ast.File, fset *token.FileSet, stats []stat) []stat { - for _, decl := range f.Decls { - if fn, ok := decl.(*ast.FuncDecl); ok { - stats = append(stats, stat{ - PkgName: f.Name.Name, - FuncName: funcName(fn), - Complexity: complexity(fn), - Pos: fset.Position(fn.Pos()), - }) - } - } - return stats -} - -// funcName returns the name representation of a function or method: -// "(Type).Name" for methods or simply "Name" for functions. -func funcName(fn *ast.FuncDecl) string { - if fn.Recv != nil { - if fn.Recv.NumFields() > 0 { - typ := fn.Recv.List[0].Type - return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name) - } - } - return fn.Name.Name -} - -// recvString returns a string representation of recv of the -// form "T", "*T", or "BADRECV" (if not a proper receiver type). -func recvString(recv ast.Expr) string { - switch t := recv.(type) { - case *ast.Ident: - return t.Name - case *ast.StarExpr: - return "*" + recvString(t.X) - } - return "BADRECV" -} - -// complexity calculates the cyclomatic complexity of a function. -func complexity(fn *ast.FuncDecl) int { - v := complexityVisitor{} - ast.Walk(&v, fn) - return v.Complexity -} - -type complexityVisitor struct { - // Complexity is the cyclomatic complexity - Complexity int -} - -// Visit implements the ast.Visitor interface. -func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor { - switch n := n.(type) { - case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause: - v.Complexity++ - case *ast.BinaryExpr: - if n.Op == token.LAND || n.Op == token.LOR { - v.Complexity++ - } - } - return v -} diff --git a/vendor/github.com/golangci/gocyclo/pkg/gocyclo/gocyclo.go b/vendor/github.com/golangci/gocyclo/pkg/gocyclo/gocyclo.go new file mode 100644 index 00000000..a1f33310 --- /dev/null +++ b/vendor/github.com/golangci/gocyclo/pkg/gocyclo/gocyclo.go @@ -0,0 +1,82 @@ +package gocyclo + +import ( + "fmt" + "go/token" + + "go/ast" +) + +type Stat struct { + PkgName string + FuncName string + Complexity int + Pos token.Position +} + +func (s Stat) String() string { + return fmt.Sprintf("%d %s %s %s", s.Complexity, s.PkgName, s.FuncName, s.Pos) +} + +func BuildStats(f *ast.File, fset *token.FileSet, stats []Stat) []Stat { + for _, decl := range f.Decls { + if fn, ok := decl.(*ast.FuncDecl); ok { + stats = append(stats, Stat{ + PkgName: f.Name.Name, + FuncName: funcName(fn), + Complexity: complexity(fn), + Pos: fset.Position(fn.Pos()), + }) + } + } + return stats +} + +// funcName returns the name representation of a function or method: +// "(Type).Name" for methods or simply "Name" for functions. +func funcName(fn *ast.FuncDecl) string { + if fn.Recv != nil { + if fn.Recv.NumFields() > 0 { + typ := fn.Recv.List[0].Type + return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name) + } + } + return fn.Name.Name +} + +// recvString returns a string representation of recv of the +// form "T", "*T", or "BADRECV" (if not a proper receiver type). +func recvString(recv ast.Expr) string { + switch t := recv.(type) { + case *ast.Ident: + return t.Name + case *ast.StarExpr: + return "*" + recvString(t.X) + } + return "BADRECV" +} + +// complexity calculates the cyclomatic complexity of a function. +func complexity(fn *ast.FuncDecl) int { + v := complexityVisitor{} + ast.Walk(&v, fn) + return v.Complexity +} + +type complexityVisitor struct { + // Complexity is the cyclomatic complexity + Complexity int +} + +// Visit implements the ast.Visitor interface. +func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor { + switch n := n.(type) { + case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause: + v.Complexity++ + case *ast.BinaryExpr: + if n.Op == token.LAND || n.Op == token.LOR { + v.Complexity++ + } + } + return v +}