support gocyclo
This commit is contained in:
parent
b3f856af80
commit
d02ef1b633
8
Gopkg.lock
generated
8
Gopkg.lock
generated
@ -64,6 +64,12 @@
|
|||||||
packages = ["deadcode"]
|
packages = ["deadcode"]
|
||||||
revision = "a82b63c685e730fbc1efbade9ce6316ac85cceb7"
|
revision = "a82b63c685e730fbc1efbade9ce6316ac85cceb7"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/golangci/gocyclo"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "687488c898ab673ee751943f7bcab53ce2371985"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/golangci/gofmt"
|
name = "github.com/golangci/gofmt"
|
||||||
@ -237,6 +243,6 @@
|
|||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "fd69e23afecb188e33d824ca597d116e47e913418eb1f0842ccb4f9bb44c7c7c"
|
inputs-digest = "27116578a35d2f40d043e02943a6f64062ec09365a37e5b10a6d8c7710461bf8"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
@ -49,6 +49,9 @@ func (e *Executor) initRun() {
|
|||||||
|
|
||||||
runCmd.Flags().BoolVar(&rc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code")
|
runCmd.Flags().BoolVar(&rc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code")
|
||||||
|
|
||||||
|
runCmd.Flags().IntVar(&rc.Gocyclo.MinComplexity, "gocyclo.min-complexity",
|
||||||
|
20, "Minimal complexity of function to report it")
|
||||||
|
|
||||||
runCmd.Flags().StringSliceVarP(&rc.EnabledLinters, "enable", "E", []string{}, "Enable specific linter")
|
runCmd.Flags().StringSliceVarP(&rc.EnabledLinters, "enable", "E", []string{}, "Enable specific linter")
|
||||||
runCmd.Flags().StringSliceVarP(&rc.DisabledLinters, "disable", "D", []string{}, "Disable specific linter")
|
runCmd.Flags().StringSliceVarP(&rc.DisabledLinters, "disable", "D", []string{}, "Disable specific linter")
|
||||||
runCmd.Flags().BoolVar(&rc.EnableAllLinters, "enable-all", false, "Enable all linters")
|
runCmd.Flags().BoolVar(&rc.EnableAllLinters, "enable-all", false, "Enable all linters")
|
||||||
@ -94,8 +97,8 @@ func (e Executor) executeRun(cmd *cobra.Command, args []string) {
|
|||||||
runner := pkg.SimpleRunner{
|
runner := pkg.SimpleRunner{
|
||||||
Processors: []processors.Processor{
|
Processors: []processors.Processor{
|
||||||
processors.MaxLinterIssuesPerFile{},
|
processors.MaxLinterIssuesPerFile{},
|
||||||
processors.UniqByLineProcessor{},
|
|
||||||
processors.NewExcludeProcessor(fmt.Sprintf("(%s)", strings.Join(e.cfg.Run.ExcludePatterns, "|"))),
|
processors.NewExcludeProcessor(fmt.Sprintf("(%s)", strings.Join(e.cfg.Run.ExcludePatterns, "|"))),
|
||||||
|
processors.UniqByLineProcessor{},
|
||||||
processors.NewPathPrettifier(),
|
processors.NewPathPrettifier(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,9 @@ type Run struct {
|
|||||||
Gofmt struct {
|
Gofmt struct {
|
||||||
Simplify bool
|
Simplify bool
|
||||||
}
|
}
|
||||||
|
Gocyclo struct {
|
||||||
|
MinComplexity int
|
||||||
|
}
|
||||||
|
|
||||||
EnabledLinters []string
|
EnabledLinters []string
|
||||||
DisabledLinters []string
|
DisabledLinters []string
|
||||||
|
@ -38,6 +38,7 @@ func GetAllSupportedLinterConfigs() []LinterConfig {
|
|||||||
enabledByDefault(errcheck{}, "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases"),
|
enabledByDefault(errcheck{}, "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases"),
|
||||||
enabledByDefault(golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes"),
|
enabledByDefault(golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes"),
|
||||||
enabledByDefault(deadcode{}, "Finds unused code"),
|
enabledByDefault(deadcode{}, "Finds unused code"),
|
||||||
|
enabledByDefault(gocyclo{}, "Computes and checks the cyclomatic complexity of functions"),
|
||||||
|
|
||||||
disabledByDefault(gofmt{}, "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification"),
|
disabledByDefault(gofmt{}, "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification"),
|
||||||
disabledByDefault(gofmt{useGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports"),
|
disabledByDefault(gofmt{useGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports"),
|
||||||
|
38
pkg/golinters/gocyclo.go
Normal file
38
pkg/golinters/gocyclo.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package golinters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
gocycloAPI "github.com/golangci/gocyclo"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
|
"github.com/golangci/golangci-shared/pkg/executors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gocyclo struct{}
|
||||||
|
|
||||||
|
func (gocyclo) Name() string {
|
||||||
|
return "gocyclo"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gocyclo) Run(ctx context.Context, exec executors.Executor, cfg *config.Run) (*result.Result, error) {
|
||||||
|
stats := gocycloAPI.Run(cfg.Paths.MixedPaths())
|
||||||
|
|
||||||
|
res := &result.Result{}
|
||||||
|
for _, s := range stats {
|
||||||
|
if s.Complexity < cfg.Gocyclo.MinComplexity {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Issues = append(res.Issues, result.Issue{
|
||||||
|
File: s.Pos.Filename,
|
||||||
|
LineNumber: s.Pos.Line,
|
||||||
|
Text: fmt.Sprintf("cyclomatic complexity %d of func %s is high (> %d)",
|
||||||
|
s.Complexity, formatCode(s.FuncName, cfg), cfg.Gocyclo.MinComplexity),
|
||||||
|
FromLinter: g.Name(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
15
pkg/golinters/testdata/gocyclo.go
vendored
Normal file
15
pkg/golinters/testdata/gocyclo.go
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package testdata
|
||||||
|
|
||||||
|
func GocycloBigComplexity(s string) { // ERROR "cyclomatic complexity .* of func .* is high .*"
|
||||||
|
if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
7
vendor/github.com/golangci/gocyclo/CONTRIBUTORS
generated
vendored
Normal file
7
vendor/github.com/golangci/gocyclo/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Names should be added to this file like so:
|
||||||
|
# Name <email address>
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Frederik Zipp <fzipp@gmx.de>
|
||||||
|
Harshavardhana <harsha@harshavardhana.net>
|
27
vendor/github.com/golangci/gocyclo/LICENSE
generated
vendored
Normal file
27
vendor/github.com/golangci/gocyclo/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2013 Frederik Zipp. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of the copyright owner nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
31
vendor/github.com/golangci/gocyclo/README.md
generated
vendored
Normal file
31
vendor/github.com/golangci/gocyclo/README.md
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
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>
|
||||||
|
|
212
vendor/github.com/golangci/gocyclo/gocyclo.go
generated
vendored
Normal file
212
vendor/github.com/golangci/gocyclo/gocyclo.go
generated
vendored
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
// 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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user