Merge pull request #31 from golangci/feature/use-gocyclo-prepared-for-upstreaming

use gocyclo with changes for upstreaming, also speedup it 10x when pr…
This commit is contained in:
golangci 2018-05-28 17:46:49 +03:00 committed by GitHub
commit 7a97c3ee78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 178 additions and 249 deletions

6
Gopkg.lock generated
View File

@ -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

View File

@ -109,6 +109,6 @@
branch = "master"
name = "github.com/golangci/ineffassign"
[[constraint]]
[[override]]
branch = "master"
name = "github.com/golangci/lint"

71
pkg/astcache/astcache.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 [<flag> ...] <Go file or directory> ...
Examples:
$ gocyclo .
$ gocyclo main.go
$ gocyclo -top 10 src/
$ gocyclo -over 25 docker
$ gocyclo -avg .
The output fields for each line are:
<complexity> <package> <function> <file:row:column>

View File

@ -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 [<flag> ...] <Go file or directory> ...
//
// 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:
// <complexity> <package> <function> <file:row:column>
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] <Go file or directory> ...
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:
<complexity> <package> <function> <file:row:column>
`
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
}

View File

@ -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
}