From ef81b998eda14afe70a952984fb7d4dee16743e6 Mon Sep 17 00:00:00 2001
From: golangci <dev@golangci.com>
Date: Wed, 30 May 2018 19:54:29 +0300
Subject: [PATCH] #45: fix no results for gocyclo

---
 pkg/astcache/astcache.go    | 32 ++++++++++++--
 pkg/commands/root.go        | 88 +++++++++++++++++++------------------
 pkg/commands/run.go         |  5 ++-
 pkg/commands/run_test.go    | 54 +++++++++++++++++++++++
 pkg/enabled_linters.go      | 20 ++++++---
 pkg/enabled_linters_test.go | 46 +++++++++++++++++++
 6 files changed, 193 insertions(+), 52 deletions(-)
 create mode 100644 pkg/commands/run_test.go

diff --git a/pkg/astcache/astcache.go b/pkg/astcache/astcache.go
index 0f44151c..c9fcd3a6 100644
--- a/pkg/astcache/astcache.go
+++ b/pkg/astcache/astcache.go
@@ -1,16 +1,21 @@
 package astcache
 
 import (
+	"fmt"
 	"go/ast"
 	"go/parser"
 	"go/token"
+	"os"
+	"path/filepath"
 
+	"github.com/sirupsen/logrus"
 	"golang.org/x/tools/go/loader"
 )
 
 type File struct {
 	F    *ast.File
 	Fset *token.FileSet
+	Name string
 	err  error
 }
 
@@ -34,22 +39,40 @@ func (c *Cache) prepareValidFiles() {
 	c.s = files
 }
 
-func LoadFromProgram(prog *loader.Program) *Cache {
+func LoadFromProgram(prog *loader.Program) (*Cache, error) {
 	c := &Cache{
 		m: map[string]*File{},
 	}
+
+	root, err := os.Getwd()
+	if err != nil {
+		return nil, fmt.Errorf("can't get working dir: %s", err)
+	}
+
 	for _, pkg := range prog.InitialPackages() {
 		for _, f := range pkg.Files {
-			pos := prog.Fset.Position(0)
-			c.m[pos.Filename] = &File{
+			pos := prog.Fset.Position(f.Pos())
+			if pos.Filename == "" {
+				continue
+			}
+
+			relPath, err := filepath.Rel(root, pos.Filename)
+			if err != nil {
+				logrus.Warnf("Can't get relative path for %s and %s: %s",
+					root, pos.Filename, err)
+				continue
+			}
+
+			c.m[relPath] = &File{
 				F:    f,
 				Fset: prog.Fset,
+				Name: relPath,
 			}
 		}
 	}
 
 	c.prepareValidFiles()
-	return c
+	return c, nil
 }
 
 func LoadFromFiles(files []string) *Cache {
@@ -63,6 +86,7 @@ func LoadFromFiles(files []string) *Cache {
 			F:    f,
 			Fset: fset,
 			err:  err,
+			Name: filePath,
 		}
 	}
 
diff --git a/pkg/commands/root.go b/pkg/commands/root.go
index 56814298..44517cbb 100644
--- a/pkg/commands/root.go
+++ b/pkg/commands/root.go
@@ -12,6 +12,50 @@ import (
 	"github.com/spf13/cobra"
 )
 
+func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
+	if e.cfg.Run.PrintVersion {
+		fmt.Fprintf(printers.StdOut, "golangci-lint has version %s built from %s on %s\n", e.version, e.commit, e.date)
+		os.Exit(0)
+	}
+
+	runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
+
+	log.SetFlags(0) // don't print time
+	if e.cfg.Run.IsVerbose {
+		logrus.SetLevel(logrus.InfoLevel)
+	} else {
+		logrus.SetLevel(logrus.WarnLevel)
+	}
+
+	if e.cfg.Run.CPUProfilePath != "" {
+		f, err := os.Create(e.cfg.Run.CPUProfilePath)
+		if err != nil {
+			logrus.Fatal(err)
+		}
+		if err := pprof.StartCPUProfile(f); err != nil {
+			logrus.Fatal(err)
+		}
+	}
+}
+
+func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
+	if e.cfg.Run.CPUProfilePath != "" {
+		pprof.StopCPUProfile()
+	}
+	if e.cfg.Run.MemProfilePath != "" {
+		f, err := os.Create(e.cfg.Run.MemProfilePath)
+		if err != nil {
+			logrus.Fatal(err)
+		}
+		runtime.GC() // get up-to-date statistics
+		if err := pprof.WriteHeapProfile(f); err != nil {
+			logrus.Fatal("could not write memory profile: ", err)
+		}
+	}
+
+	os.Exit(e.exitCode)
+}
+
 func (e *Executor) initRoot() {
 	rootCmd := &cobra.Command{
 		Use:   "golangci-lint",
@@ -22,48 +66,8 @@ func (e *Executor) initRoot() {
 				logrus.Fatal(err)
 			}
 		},
-		PersistentPreRun: func(cmd *cobra.Command, args []string) {
-			if e.cfg.Run.PrintVersion {
-				fmt.Fprintf(printers.StdOut, "golangci-lint has version %s built from %s on %s\n", e.version, e.commit, e.date)
-				os.Exit(0)
-			}
-
-			runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
-
-			log.SetFlags(0) // don't print time
-			if e.cfg.Run.IsVerbose {
-				logrus.SetLevel(logrus.InfoLevel)
-			} else {
-				logrus.SetLevel(logrus.WarnLevel)
-			}
-
-			if e.cfg.Run.CPUProfilePath != "" {
-				f, err := os.Create(e.cfg.Run.CPUProfilePath)
-				if err != nil {
-					logrus.Fatal(err)
-				}
-				if err := pprof.StartCPUProfile(f); err != nil {
-					logrus.Fatal(err)
-				}
-			}
-		},
-		PersistentPostRun: func(cmd *cobra.Command, args []string) {
-			if e.cfg.Run.CPUProfilePath != "" {
-				pprof.StopCPUProfile()
-			}
-			if e.cfg.Run.MemProfilePath != "" {
-				f, err := os.Create(e.cfg.Run.MemProfilePath)
-				if err != nil {
-					logrus.Fatal(err)
-				}
-				runtime.GC() // get up-to-date statistics
-				if err := pprof.WriteHeapProfile(f); err != nil {
-					logrus.Fatal("could not write memory profile: ", err)
-				}
-			}
-
-			os.Exit(e.exitCode)
-		},
+		PersistentPreRun:  e.persistentPostRun,
+		PersistentPostRun: e.persistentPreRun,
 	}
 	rootCmd.PersistentFlags().BoolVarP(&e.cfg.Run.IsVerbose, "verbose", "v", false, "verbose output")
 	rootCmd.PersistentFlags().StringVar(&e.cfg.Run.CPUProfilePath, "cpu-profile-path", "", "Path to CPU profile output file")
diff --git a/pkg/commands/run.go b/pkg/commands/run.go
index acf75ace..a2a9c177 100644
--- a/pkg/commands/run.go
+++ b/pkg/commands/run.go
@@ -254,7 +254,10 @@ func buildLintCtx(ctx context.Context, linters []pkg.Linter, cfg *config.Config)
 
 	var astCache *astcache.Cache
 	if prog != nil {
-		astCache = astcache.LoadFromProgram(prog)
+		astCache, err = astcache.LoadFromProgram(prog)
+		if err != nil {
+			return nil, err
+		}
 	} else {
 		astCache = astcache.LoadFromFiles(paths.Files)
 	}
diff --git a/pkg/commands/run_test.go b/pkg/commands/run_test.go
new file mode 100644
index 00000000..53024ca0
--- /dev/null
+++ b/pkg/commands/run_test.go
@@ -0,0 +1,54 @@
+package commands
+
+import (
+	"context"
+	"testing"
+
+	"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"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestASTCacheLoading(t *testing.T) {
+	ctx := context.Background()
+	linters := []pkg.Linter{golinters.Errcheck{}}
+
+	inputPaths := []string{"./...", "./", "./run.go", "run.go"}
+	for _, inputPath := range inputPaths {
+		paths, err := fsutils.GetPathsForAnalysis(ctx, []string{inputPath}, true)
+		assert.NoError(t, err)
+		assert.NotEmpty(t, paths.Files)
+
+		prog, _, err := loadWholeAppIfNeeded(ctx, linters, &config.Run{
+			AnalyzeTests: true,
+		}, paths)
+		assert.NoError(t, err)
+
+		astCacheFromProg, err := astcache.LoadFromProgram(prog)
+		assert.NoError(t, err)
+
+		astCacheFromFiles := astcache.LoadFromFiles(paths.Files)
+
+		filesFromProg := astCacheFromProg.GetAllValidFiles()
+		filesFromFiles := astCacheFromFiles.GetAllValidFiles()
+		if len(filesFromProg) != len(filesFromFiles) {
+			t.Logf("files: %s", paths.Files)
+			t.Logf("from prog:")
+			for _, f := range filesFromProg {
+				t.Logf("%+v", *f)
+			}
+			t.Logf("from files:")
+			for _, f := range filesFromFiles {
+				t.Logf("%+v", *f)
+			}
+			t.Fatalf("lengths differ")
+		}
+
+		if len(filesFromProg) != len(paths.Files) {
+			t.Fatalf("filesFromProg differ from path.Files")
+		}
+	}
+}
diff --git a/pkg/enabled_linters.go b/pkg/enabled_linters.go
index a7932880..ddb016b5 100644
--- a/pkg/enabled_linters.go
+++ b/pkg/enabled_linters.go
@@ -294,9 +294,7 @@ func GetAllLintersForPreset(p string) []Linter {
 	return ret
 }
 
-func getEnabledLintersSet(cfg *config.Config) map[string]Linter { // nolint:gocyclo
-	lcfg := &cfg.Linters
-
+func getEnabledLintersSet(lcfg *config.Linters, enabledByDefaultLinters []Linter) map[string]Linter { // nolint:gocyclo
 	resultLintersSet := map[string]Linter{}
 	switch {
 	case len(lcfg.Presets) != 0:
@@ -306,7 +304,7 @@ func getEnabledLintersSet(cfg *config.Config) map[string]Linter { // nolint:gocy
 	case lcfg.DisableAll:
 		break
 	default:
-		resultLintersSet = lintersToMap(getAllEnabledByDefaultLinters())
+		resultLintersSet = lintersToMap(enabledByDefaultLinters)
 	}
 
 	// --presets can only add linters to default set
@@ -332,12 +330,24 @@ func getEnabledLintersSet(cfg *config.Config) map[string]Linter { // nolint:gocy
 	}
 
 	for _, name := range lcfg.Disable {
+		if name == "megacheck" {
+			for _, ln := range getAllMegacheckSubLinterNames() {
+				delete(resultLintersSet, ln)
+			}
+		}
 		delete(resultLintersSet, name)
 	}
 
 	return resultLintersSet
 }
 
+func getAllMegacheckSubLinterNames() []string {
+	unusedName := golinters.Megacheck{UnusedEnabled: true}.Name()
+	gosimpleName := golinters.Megacheck{GosimpleEnabled: true}.Name()
+	staticcheckName := golinters.Megacheck{StaticcheckEnabled: true}.Name()
+	return []string{unusedName, gosimpleName, staticcheckName}
+}
+
 func optimizeLintersSet(linters map[string]Linter) {
 	unusedName := golinters.Megacheck{UnusedEnabled: true}.Name()
 	gosimpleName := golinters.Megacheck{GosimpleEnabled: true}.Name()
@@ -375,7 +385,7 @@ func GetEnabledLinters(cfg *config.Config) ([]Linter, error) {
 		return nil, err
 	}
 
-	resultLintersSet := getEnabledLintersSet(cfg)
+	resultLintersSet := getEnabledLintersSet(&cfg.Linters, getAllEnabledByDefaultLinters())
 	optimizeLintersSet(resultLintersSet)
 
 	var resultLinters []Linter
diff --git a/pkg/enabled_linters_test.go b/pkg/enabled_linters_test.go
index 2ebf38ec..2d026d46 100644
--- a/pkg/enabled_linters_test.go
+++ b/pkg/enabled_linters_test.go
@@ -8,6 +8,7 @@ import (
 	"os/exec"
 	"path/filepath"
 	"runtime"
+	"sort"
 	"strconv"
 	"strings"
 	"sync"
@@ -370,3 +371,48 @@ func BenchmarkWithGometalinter(b *testing.B) {
 		compare(b, runGometalinter, runGolangciLint, bc.name, "", lc/1000)
 	}
 }
+
+func TestGetEnabledLintersSet(t *testing.T) {
+	type cs struct {
+		cfg  config.Linters
+		name string   // test case name
+		def  []string // enabled by default linters
+		exp  []string // alphabetically ordered enabled linter names
+	}
+	cases := []cs{
+		{
+			cfg: config.Linters{
+				Disable: []string{"megacheck"},
+			},
+			name: "disable all linters from megacheck",
+			def:  getAllMegacheckSubLinterNames(),
+		},
+		{
+			cfg: config.Linters{
+				Disable: []string{"staticcheck"},
+			},
+			name: "disable only staticcheck",
+			def:  getAllMegacheckSubLinterNames(),
+			exp:  []string{"gosimple", "unused"},
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			defaultLinters := []Linter{}
+			for _, ln := range c.def {
+				defaultLinters = append(defaultLinters, getLinterByName(ln))
+			}
+			els := getEnabledLintersSet(&c.cfg, defaultLinters)
+			var enabledLinters []string
+			for ln := range els {
+				enabledLinters = append(enabledLinters, ln)
+			}
+
+			sort.Strings(enabledLinters)
+			sort.Strings(c.exp)
+
+			assert.Equal(t, c.exp, enabledLinters)
+		})
+	}
+}