support gocyclo

This commit is contained in:
golangci 2018-05-06 15:24:45 +03:00
parent b3f856af80
commit d02ef1b633
10 changed files with 345 additions and 2 deletions

8
Gopkg.lock generated
View File

@ -64,6 +64,12 @@
packages = ["deadcode"]
revision = "a82b63c685e730fbc1efbade9ce6316ac85cceb7"
[[projects]]
branch = "master"
name = "github.com/golangci/gocyclo"
packages = ["."]
revision = "687488c898ab673ee751943f7bcab53ce2371985"
[[projects]]
branch = "master"
name = "github.com/golangci/gofmt"
@ -237,6 +243,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "fd69e23afecb188e33d824ca597d116e47e913418eb1f0842ccb4f9bb44c7c7c"
inputs-digest = "27116578a35d2f40d043e02943a6f64062ec09365a37e5b10a6d8c7710461bf8"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -49,6 +49,9 @@ func (e *Executor) initRun() {
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.DisabledLinters, "disable", "D", []string{}, "Disable specific linter")
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{
Processors: []processors.Processor{
processors.MaxLinterIssuesPerFile{},
processors.UniqByLineProcessor{},
processors.NewExcludeProcessor(fmt.Sprintf("(%s)", strings.Join(e.cfg.Run.ExcludePatterns, "|"))),
processors.UniqByLineProcessor{},
processors.NewPathPrettifier(),
},
}

View File

@ -41,6 +41,9 @@ type Run struct {
Gofmt struct {
Simplify bool
}
Gocyclo struct {
MinComplexity int
}
EnabledLinters []string
DisabledLinters []string

View File

@ -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(golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes"),
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{useGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports"),

38
pkg/golinters/gocyclo.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
}