From d02ef1b6335cd65f4978b47dd6bcdc423e83ec84 Mon Sep 17 00:00:00 2001
From: golangci <dev@golangci.com>
Date: Sun, 6 May 2018 15:24:45 +0300
Subject: [PATCH] support gocyclo

---
 Gopkg.lock                                    |   8 +-
 internal/commands/run.go                      |   5 +-
 pkg/config/config.go                          |   3 +
 pkg/golinters/enabled_linters.go              |   1 +
 pkg/golinters/gocyclo.go                      |  38 ++++
 pkg/golinters/testdata/gocyclo.go             |  15 ++
 .../github.com/golangci/gocyclo/CONTRIBUTORS  |   7 +
 vendor/github.com/golangci/gocyclo/LICENSE    |  27 +++
 vendor/github.com/golangci/gocyclo/README.md  |  31 +++
 vendor/github.com/golangci/gocyclo/gocyclo.go | 212 ++++++++++++++++++
 10 files changed, 345 insertions(+), 2 deletions(-)
 create mode 100644 pkg/golinters/gocyclo.go
 create mode 100644 pkg/golinters/testdata/gocyclo.go
 create mode 100644 vendor/github.com/golangci/gocyclo/CONTRIBUTORS
 create mode 100644 vendor/github.com/golangci/gocyclo/LICENSE
 create mode 100644 vendor/github.com/golangci/gocyclo/README.md
 create mode 100644 vendor/github.com/golangci/gocyclo/gocyclo.go

diff --git a/Gopkg.lock b/Gopkg.lock
index 93fe1c20..66d55f1f 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -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
diff --git a/internal/commands/run.go b/internal/commands/run.go
index 70362ef7..c4c5702e 100644
--- a/internal/commands/run.go
+++ b/internal/commands/run.go
@@ -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(),
 			},
 		}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 9afdf2c4..5fd6e116 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -41,6 +41,9 @@ type Run struct {
 	Gofmt struct {
 		Simplify bool
 	}
+	Gocyclo struct {
+		MinComplexity int
+	}
 
 	EnabledLinters    []string
 	DisabledLinters   []string
diff --git a/pkg/golinters/enabled_linters.go b/pkg/golinters/enabled_linters.go
index 72f131fe..fe38e415 100644
--- a/pkg/golinters/enabled_linters.go
+++ b/pkg/golinters/enabled_linters.go
@@ -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"),
diff --git a/pkg/golinters/gocyclo.go b/pkg/golinters/gocyclo.go
new file mode 100644
index 00000000..4c73d828
--- /dev/null
+++ b/pkg/golinters/gocyclo.go
@@ -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
+}
diff --git a/pkg/golinters/testdata/gocyclo.go b/pkg/golinters/testdata/gocyclo.go
new file mode 100644
index 00000000..e0775031
--- /dev/null
+++ b/pkg/golinters/testdata/gocyclo.go
@@ -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
+	}
+}
diff --git a/vendor/github.com/golangci/gocyclo/CONTRIBUTORS b/vendor/github.com/golangci/gocyclo/CONTRIBUTORS
new file mode 100644
index 00000000..1c09f1a0
--- /dev/null
+++ b/vendor/github.com/golangci/gocyclo/CONTRIBUTORS
@@ -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>
diff --git a/vendor/github.com/golangci/gocyclo/LICENSE b/vendor/github.com/golangci/gocyclo/LICENSE
new file mode 100644
index 00000000..45f88d6c
--- /dev/null
+++ b/vendor/github.com/golangci/gocyclo/LICENSE
@@ -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.
diff --git a/vendor/github.com/golangci/gocyclo/README.md b/vendor/github.com/golangci/gocyclo/README.md
new file mode 100644
index 00000000..b2ec36a7
--- /dev/null
+++ b/vendor/github.com/golangci/gocyclo/README.md
@@ -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>
+
diff --git a/vendor/github.com/golangci/gocyclo/gocyclo.go b/vendor/github.com/golangci/gocyclo/gocyclo.go
new file mode 100644
index 00000000..c376f21e
--- /dev/null
+++ b/vendor/github.com/golangci/gocyclo/gocyclo.go
@@ -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
+}