mirror of
https://github.com/scratchfoundation/golangci-lint.git
synced 2025-08-28 22:28:43 -04:00
Fix #78: log all warnings
1. Log all warnings, don't hide none of them 2. Write fatal messages (stop analysis) with error log level 3. Remove ugly timestamp counter from logrus output 4. Print nested module prefix in log 5. Make logger abstraction: no global logging anymore 6. Refactor config reading to config.FileReader struct to avoid passing logger into every function 7. Replace exit codes hardcoding with constants in exitcodes package 8. Fail test if any warning was logged 9. Fix calculation of relative path if we analyze parent dir ../ 10. Move Runner initialization from Executor to NewRunner func 11. Log every AST parsing error 12. Properly print used config file path in verbose mode 13. Print package files if only 1 package is analyzedin verbose mode, print not compiling packages in verbose mode 14. Forbid usage of github.com/sirupsen/logrus by DepGuard linter 15. Add default ignore pattern to folint: "comment on exported const"
This commit is contained in:
parent
219a5479c8
commit
9181ca7175
40 changed files with 694 additions and 432 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
/*.pprof
|
/*.pprof
|
||||||
/dist/
|
/dist/
|
||||||
/.idea/
|
/.idea/
|
||||||
|
/test/path
|
||||||
|
|
|
@ -12,6 +12,12 @@ linters-settings:
|
||||||
goconst:
|
goconst:
|
||||||
min-len: 2
|
min-len: 2
|
||||||
min-occurrences: 2
|
min-occurrences: 2
|
||||||
|
depguard:
|
||||||
|
list-type: blacklist
|
||||||
|
packages:
|
||||||
|
# logging is allowed only by logutils.Log, logrus
|
||||||
|
# is allowed to use only in logutils package
|
||||||
|
- github.com/sirupsen/logrus
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable-all: true
|
enable-all: true
|
||||||
|
|
10
Makefile
10
Makefile
|
@ -1,9 +1,9 @@
|
||||||
test:
|
test:
|
||||||
go install ./cmd/... # needed for govet and golint
|
go install ./cmd/... # needed for govet and golint if go < 1.10
|
||||||
golangci-lint run -v
|
GL_TEST_RUN=1 golangci-lint run -v
|
||||||
golangci-lint run --fast --no-config -v
|
GL_TEST_RUN=1 golangci-lint run --fast --no-config -v
|
||||||
golangci-lint run --no-config -v
|
GL_TEST_RUN=1 golangci-lint run --no-config -v
|
||||||
go test -v ./...
|
GL_TEST_RUN=1 go test -v ./...
|
||||||
|
|
||||||
assets:
|
assets:
|
||||||
svg-term --cast=183662 --out docs/demo.svg --window --width 110 --height 30 --from 2000 --to 20000 --profile Dracula --term iterm2
|
svg-term --cast=183662 --out docs/demo.svg --window --width 110 --height 30 --from 2000 --to 20000 --profile Dracula --term iterm2
|
||||||
|
|
|
@ -255,7 +255,7 @@ Flags:
|
||||||
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked
|
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked
|
||||||
|
|
||||||
# golint: Annoying issue about not having a comment. The rare codebase has such comments
|
# golint: Annoying issue about not having a comment. The rare codebase has such comments
|
||||||
- (comment on exported (method|function|type)|should have( a package)? comment|comment should be of the form)
|
- (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)
|
||||||
|
|
||||||
# golint: False positive when tests are defined in package 'test'
|
# golint: False positive when tests are defined in package 'test'
|
||||||
- func name will be used as test\.Test.* by other packages, and that stutters; consider calling this
|
- func name will be used as test\.Test.* by other packages, and that stutters; consider calling this
|
||||||
|
@ -329,6 +329,12 @@ linters-settings:
|
||||||
goconst:
|
goconst:
|
||||||
min-len: 2
|
min-len: 2
|
||||||
min-occurrences: 2
|
min-occurrences: 2
|
||||||
|
depguard:
|
||||||
|
list-type: blacklist
|
||||||
|
packages:
|
||||||
|
# logging is allowed only by logutils.Log, logrus
|
||||||
|
# is allowed to use only in logutils package
|
||||||
|
- github.com/sirupsen/logrus
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable-all: true
|
enable-all: true
|
||||||
|
|
|
@ -2,7 +2,7 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ type Executor struct {
|
||||||
exitCode int
|
exitCode int
|
||||||
|
|
||||||
version, commit, date string
|
version, commit, date string
|
||||||
|
|
||||||
|
log logutils.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExecutor(version, commit, date string) *Executor {
|
func NewExecutor(version, commit, date string) *Executor {
|
||||||
|
@ -22,10 +24,9 @@ func NewExecutor(version, commit, date string) *Executor {
|
||||||
version: version,
|
version: version,
|
||||||
commit: commit,
|
commit: commit,
|
||||||
date: date,
|
date: date,
|
||||||
|
log: logutils.NewStderrLog(""),
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.SetLevel(logrus.WarnLevel)
|
|
||||||
|
|
||||||
e.initRoot()
|
e.initRoot()
|
||||||
e.initRun()
|
e.initRun()
|
||||||
e.initLinters()
|
e.initLinters()
|
||||||
|
|
|
@ -2,7 +2,6 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
@ -10,20 +9,10 @@ import (
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/printers"
|
"github.com/golangci/golangci-lint/pkg/printers"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupLog(isVerbose bool) {
|
|
||||||
log.SetFlags(0) // don't print time
|
|
||||||
if logutils.IsDebugEnabled() {
|
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
|
||||||
} else if isVerbose {
|
|
||||||
logrus.SetLevel(logrus.InfoLevel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
|
func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
|
||||||
if e.cfg.Run.PrintVersion {
|
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)
|
fmt.Fprintf(printers.StdOut, "golangci-lint has version %s built from %s on %s\n", e.version, e.commit, e.date)
|
||||||
|
@ -32,15 +21,15 @@ func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
|
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
|
||||||
|
|
||||||
setupLog(e.cfg.Run.IsVerbose)
|
logutils.SetupVerboseLog(e.log, e.cfg.Run.IsVerbose)
|
||||||
|
|
||||||
if e.cfg.Run.CPUProfilePath != "" {
|
if e.cfg.Run.CPUProfilePath != "" {
|
||||||
f, err := os.Create(e.cfg.Run.CPUProfilePath)
|
f, err := os.Create(e.cfg.Run.CPUProfilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
e.log.Fatalf("Can't create file %s: %s", e.cfg.Run.CPUProfilePath, err)
|
||||||
}
|
}
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
logrus.Fatal(err)
|
e.log.Fatalf("Can't start CPU profiling: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,11 +41,11 @@ func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
|
||||||
if e.cfg.Run.MemProfilePath != "" {
|
if e.cfg.Run.MemProfilePath != "" {
|
||||||
f, err := os.Create(e.cfg.Run.MemProfilePath)
|
f, err := os.Create(e.cfg.Run.MemProfilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
e.log.Fatalf("Can't create file %s: %s", e.cfg.Run.MemProfilePath, err)
|
||||||
}
|
}
|
||||||
runtime.GC() // get up-to-date statistics
|
runtime.GC() // get up-to-date statistics
|
||||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||||
logrus.Fatal("could not write memory profile: ", err)
|
e.log.Fatalf("Can't write heap profile: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +67,7 @@ func (e *Executor) initRoot() {
|
||||||
Long: `Smart, fast linters runner. Run it in cloud for every GitHub pull request on https://golangci.com`,
|
Long: `Smart, fast linters runner. Run it in cloud for every GitHub pull request on https://golangci.com`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if err := cmd.Help(); err != nil {
|
if err := cmd.Help(); err != nil {
|
||||||
logrus.Fatal(err)
|
e.log.Fatalf("Can't run help: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PersistentPreRun: e.persistentPreRun,
|
PersistentPreRun: e.persistentPreRun,
|
||||||
|
@ -89,6 +78,10 @@ func (e *Executor) initRoot() {
|
||||||
e.rootCmd = rootCmd
|
e.rootCmd = rootCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Executor) needVersionOption() bool {
|
||||||
|
return e.date != ""
|
||||||
|
}
|
||||||
|
|
||||||
func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bool) {
|
func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bool) {
|
||||||
fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
|
fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
|
||||||
fs.StringVar(&cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
|
fs.StringVar(&cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
|
||||||
|
|
|
@ -12,22 +12,16 @@ import (
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint"
|
"github.com/golangci/golangci-lint/pkg/lint"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/printers"
|
"github.com/golangci/golangci-lint/pkg/printers"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
"github.com/golangci/golangci-lint/pkg/result/processors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
exitCodeIfFailure = 3
|
|
||||||
exitCodeIfTimeout = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
func getDefaultExcludeHelp() string {
|
func getDefaultExcludeHelp() string {
|
||||||
parts := []string{"Use or not use default excludes:"}
|
parts := []string{"Use or not use default excludes:"}
|
||||||
for _, ep := range config.DefaultExcludePatterns {
|
for _, ep := range config.DefaultExcludePatterns {
|
||||||
|
@ -64,7 +58,7 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
|
||||||
// Run config
|
// Run config
|
||||||
rc := &cfg.Run
|
rc := &cfg.Run
|
||||||
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
|
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
|
||||||
1, wh("Exit code when issues were found"))
|
exitcodes.IssuesFound, wh("Exit code when issues were found"))
|
||||||
fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags"))
|
fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags"))
|
||||||
fs.DurationVar(&rc.Deadline, "deadline", time.Minute, wh("Deadline for total work"))
|
fs.DurationVar(&rc.Deadline, "deadline", time.Minute, wh("Deadline for total work"))
|
||||||
fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)"))
|
fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)"))
|
||||||
|
@ -165,65 +159,76 @@ func (e *Executor) initRun() {
|
||||||
// init e.cfg by values from config: flags parse will see these values
|
// init e.cfg by values from config: flags parse will see these values
|
||||||
// like the default ones. It will overwrite them only if the same option
|
// like the default ones. It will overwrite them only if the same option
|
||||||
// is found in command-line: it's ok, command-line has higher priority.
|
// is found in command-line: it's ok, command-line has higher priority.
|
||||||
e.parseConfig()
|
|
||||||
|
|
||||||
// Slice options must be explicitly set for properly merging.
|
r := config.NewFileReader(e.cfg, e.log.Child("config_reader"), func(fs *pflag.FlagSet, cfg *config.Config) {
|
||||||
|
// Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations:
|
||||||
|
// `changed` variable inside string slice vars will be shared.
|
||||||
|
// Use another config variable here, not e.cfg, to not
|
||||||
|
// affect main parsing by this parsing of only config option.
|
||||||
|
initFlagSet(fs, cfg)
|
||||||
|
|
||||||
|
// Parse max options, even force version option: don't want
|
||||||
|
// to get access to Executor here: it's error-prone to use
|
||||||
|
// cfg vs e.cfg.
|
||||||
|
initRootFlagSet(fs, cfg, true)
|
||||||
|
})
|
||||||
|
if err := r.Read(); err != nil {
|
||||||
|
e.log.Fatalf("Can't read config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice options must be explicitly set for proper merging of config and command-line options.
|
||||||
fixSlicesFlags(fs)
|
fixSlicesFlags(fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fixSlicesFlags(fs *pflag.FlagSet) {
|
||||||
|
// It's a dirty hack to set flag.Changed to true for every string slice flag.
|
||||||
|
// It's necessary to merge config and command-line slices: otherwise command-line
|
||||||
|
// flags will always overwrite ones from the config.
|
||||||
|
fs.VisitAll(func(f *pflag.Flag) {
|
||||||
|
if f.Value.Type() != "stringSlice" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := fs.GetStringSlice(f.Name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == nil { // assume that every string slice flag has nil as the default
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// calling Set sets Changed to true: next Set calls will append, not overwrite
|
||||||
|
_ = f.Value.Set(strings.Join(s, ","))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) {
|
func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) {
|
||||||
e.cfg.Run.Args = args
|
e.cfg.Run.Args = args
|
||||||
|
|
||||||
linters, err := lintersdb.GetEnabledLinters(e.cfg)
|
linters, err := lintersdb.GetEnabledLinters(e.cfg, e.log.Child("lintersdb"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lintCtx, err := lint.LoadContext(ctx, linters, e.cfg)
|
lintCtx, err := lint.LoadContext(ctx, linters, e.cfg, e.log.Child("load"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
excludePatterns := e.cfg.Issues.ExcludePatterns
|
runner, err := lint.NewRunner(lintCtx.ASTCache, e.cfg, e.log.Child("runner"))
|
||||||
if e.cfg.Issues.UseDefaultExcludes {
|
|
||||||
excludePatterns = append(excludePatterns, config.GetDefaultExcludePatternsStrings()...)
|
|
||||||
}
|
|
||||||
var excludeTotalPattern string
|
|
||||||
if len(excludePatterns) != 0 {
|
|
||||||
excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(excludePatterns, "|"))
|
|
||||||
}
|
|
||||||
|
|
||||||
skipFilesProcessor, err := processors.NewSkipFiles(e.cfg.Run.SkipFiles)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
runner := lint.SimpleRunner{
|
|
||||||
Processors: []processors.Processor{
|
|
||||||
processors.NewPathPrettifier(), // must be before diff, nolint and exclude autogenerated processor at least
|
|
||||||
processors.NewCgo(),
|
|
||||||
skipFilesProcessor,
|
|
||||||
|
|
||||||
processors.NewAutogeneratedExclude(lintCtx.ASTCache),
|
|
||||||
processors.NewExclude(excludeTotalPattern),
|
|
||||||
processors.NewNolint(lintCtx.ASTCache),
|
|
||||||
|
|
||||||
processors.NewUniqByLine(),
|
|
||||||
processors.NewDiff(e.cfg.Issues.Diff, e.cfg.Issues.DiffFromRevision, e.cfg.Issues.DiffPatchFilePath),
|
|
||||||
processors.NewMaxPerFileFromLinter(),
|
|
||||||
processors.NewMaxSameIssues(e.cfg.Issues.MaxSameIssues),
|
|
||||||
processors.NewMaxFromLinter(e.cfg.Issues.MaxIssuesPerLinter),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return runner.Run(ctx, linters, lintCtx), nil
|
return runner.Run(ctx, linters, lintCtx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setOutputToDevNull() (savedStdout, savedStderr *os.File) {
|
func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
|
||||||
savedStdout, savedStderr = os.Stdout, os.Stderr
|
savedStdout, savedStderr = os.Stdout, os.Stderr
|
||||||
devNull, err := os.Open(os.DevNull)
|
devNull, err := os.Open(os.DevNull)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("can't open null device %q: %s", os.DevNull, err)
|
e.log.Warnf("Can't open null device %q: %s", os.DevNull, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +240,7 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
|
||||||
if !logutils.HaveDebugTag("linters_output") {
|
if !logutils.HaveDebugTag("linters_output") {
|
||||||
// Don't allow linters and loader to print anything
|
// Don't allow linters and loader to print anything
|
||||||
log.SetOutput(ioutil.Discard)
|
log.SetOutput(ioutil.Discard)
|
||||||
savedStdout, savedStderr := setOutputToDevNull()
|
savedStdout, savedStderr := e.setOutputToDevNull()
|
||||||
defer func() {
|
defer func() {
|
||||||
os.Stdout, os.Stderr = savedStdout, savedStderr
|
os.Stdout, os.Stderr = savedStdout, savedStderr
|
||||||
}()
|
}()
|
||||||
|
@ -253,9 +258,11 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
|
||||||
p = printers.NewJSON()
|
p = printers.NewJSON()
|
||||||
case config.OutFormatColoredLineNumber, config.OutFormatLineNumber:
|
case config.OutFormatColoredLineNumber, config.OutFormatLineNumber:
|
||||||
p = printers.NewText(e.cfg.Output.PrintIssuedLine,
|
p = printers.NewText(e.cfg.Output.PrintIssuedLine,
|
||||||
format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName)
|
format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName,
|
||||||
|
e.log.Child("text_printer"))
|
||||||
case config.OutFormatTab:
|
case config.OutFormatTab:
|
||||||
p = printers.NewTab(e.cfg.Output.PrintLinterName)
|
p = printers.NewTab(e.cfg.Output.PrintLinterName,
|
||||||
|
e.log.Child("tab_printer"))
|
||||||
case config.OutFormatCheckstyle:
|
case config.OutFormatCheckstyle:
|
||||||
p = printers.NewCheckstyle()
|
p = printers.NewCheckstyle()
|
||||||
default:
|
default:
|
||||||
|
@ -288,22 +295,22 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if needTrackResources {
|
if needTrackResources {
|
||||||
go watchResources(ctx, trackResourcesEndCh)
|
go watchResources(ctx, trackResourcesEndCh, e.log)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.runAndPrint(ctx, args); err != nil {
|
if err := e.runAndPrint(ctx, args); err != nil {
|
||||||
logrus.Warnf("running error: %s", err)
|
e.log.Errorf("Running error: %s", err)
|
||||||
if e.exitCode == 0 {
|
if e.exitCode == exitcodes.Success {
|
||||||
e.exitCode = exitCodeIfFailure
|
e.exitCode = exitcodes.Failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.exitCode == 0 && ctx.Err() != nil {
|
if e.exitCode == exitcodes.Success && ctx.Err() != nil {
|
||||||
e.exitCode = exitCodeIfTimeout
|
e.exitCode = exitcodes.Timeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func watchResources(ctx context.Context, done chan struct{}) {
|
func watchResources(ctx context.Context, done chan struct{}, log logutils.Log) {
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
|
|
||||||
rssValues := []uint64{}
|
rssValues := []uint64{}
|
||||||
|
@ -339,8 +346,8 @@ func watchResources(ctx context.Context, done chan struct{}) {
|
||||||
|
|
||||||
const MB = 1024 * 1024
|
const MB = 1024 * 1024
|
||||||
maxMB := float64(max) / MB
|
maxMB := float64(max) / MB
|
||||||
logrus.Infof("Memory: %d samples, avg is %.1fMB, max is %.1fMB",
|
log.Infof("Memory: %d samples, avg is %.1fMB, max is %.1fMB",
|
||||||
len(rssValues), float64(avg)/MB, maxMB)
|
len(rssValues), float64(avg)/MB, maxMB)
|
||||||
logrus.Infof("Execution took %s", time.Since(startedAt))
|
log.Infof("Execution took %s", time.Since(startedAt))
|
||||||
close(done)
|
close(done)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,217 +0,0 @@
|
||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
|
||||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
|
||||||
"github.com/golangci/golangci-lint/pkg/printers"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e *Executor) parseConfigImpl() {
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
|
||||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logrus.Fatalf("Can't read viper config: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
usedConfigFile := viper.ConfigFileUsed()
|
|
||||||
if usedConfigFile == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logrus.Infof("Used config file %s", getRelPath(usedConfigFile))
|
|
||||||
|
|
||||||
if err := viper.Unmarshal(&e.cfg); err != nil {
|
|
||||||
logrus.Fatalf("Can't unmarshal config by viper: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := e.validateConfig(); err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.cfg.InternalTest { // just for testing purposes: to detect config file usage
|
|
||||||
fmt.Fprintln(printers.StdOut, "test")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) validateConfig() error {
|
|
||||||
c := e.cfg
|
|
||||||
if len(c.Run.Args) != 0 {
|
|
||||||
return errors.New("option run.args in config isn't supported now")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Run.CPUProfilePath != "" {
|
|
||||||
return errors.New("option run.cpuprofilepath in config isn't allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Run.MemProfilePath != "" {
|
|
||||||
return errors.New("option run.memprofilepath in config isn't allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Run.IsVerbose {
|
|
||||||
return errors.New("can't set run.verbose option with config: only on command-line")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupConfigFileSearch(args []string) {
|
|
||||||
// skip all args ([golangci-lint, run/linters]) before files/dirs list
|
|
||||||
for len(args) != 0 {
|
|
||||||
if args[0] == "run" {
|
|
||||||
args = args[1:]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
args = args[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// find first file/dir arg
|
|
||||||
firstArg := "./..."
|
|
||||||
if len(args) != 0 {
|
|
||||||
firstArg = args[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
absStartPath, err := filepath.Abs(firstArg)
|
|
||||||
if err != nil {
|
|
||||||
logutils.HiddenWarnf("Can't make abs path for %q: %s", firstArg, err)
|
|
||||||
absStartPath = filepath.Clean(firstArg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// start from it
|
|
||||||
var curDir string
|
|
||||||
if fsutils.IsDir(absStartPath) {
|
|
||||||
curDir = absStartPath
|
|
||||||
} else {
|
|
||||||
curDir = filepath.Dir(absStartPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// find all dirs from it up to the root
|
|
||||||
configSearchPaths := []string{"./"}
|
|
||||||
for {
|
|
||||||
configSearchPaths = append(configSearchPaths, curDir)
|
|
||||||
newCurDir := filepath.Dir(curDir)
|
|
||||||
if curDir == newCurDir || newCurDir == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
curDir = newCurDir
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Infof("Config search paths: %s", configSearchPaths)
|
|
||||||
viper.SetConfigName(".golangci")
|
|
||||||
for _, p := range configSearchPaths {
|
|
||||||
viper.AddConfigPath(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRelPath(p string) string {
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
logutils.HiddenWarnf("Can't get wd: %s", err)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := filepath.Rel(wd, p)
|
|
||||||
if err != nil {
|
|
||||||
logutils.HiddenWarnf("Can't make path %s relative to %s: %s", p, wd, err)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) needVersionOption() bool {
|
|
||||||
return e.date != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseConfigOption() (string, []string, error) {
|
|
||||||
// We use another pflag.FlagSet here to not set `changed` flag
|
|
||||||
// on cmd.Flags() options. Otherwise string slice options will be duplicated.
|
|
||||||
fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError)
|
|
||||||
|
|
||||||
// Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations:
|
|
||||||
// `changed` variable inside string slice vars will be shared.
|
|
||||||
// Use another config variable here, not e.cfg, to not
|
|
||||||
// affect main parsing by this parsing of only config option.
|
|
||||||
var cfg config.Config
|
|
||||||
initFlagSet(fs, &cfg)
|
|
||||||
|
|
||||||
// Parse max options, even force version option: don't want
|
|
||||||
// to get access to Executor here: it's error-prone to use
|
|
||||||
// cfg vs e.cfg.
|
|
||||||
initRootFlagSet(fs, &cfg, true)
|
|
||||||
|
|
||||||
fs.Usage = func() {} // otherwise help text will be printed twice
|
|
||||||
if err := fs.Parse(os.Args); err != nil {
|
|
||||||
if err == pflag.ErrHelp {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Fatalf("Can't parse args: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
setupLog(cfg.Run.IsVerbose) // for `-v` to work until running of preRun function
|
|
||||||
|
|
||||||
configFile := cfg.Run.Config
|
|
||||||
if cfg.Run.NoConfig && configFile != "" {
|
|
||||||
logrus.Fatal("can't combine option --config and --no-config")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Run.NoConfig {
|
|
||||||
return "", nil, fmt.Errorf("no need to use config")
|
|
||||||
}
|
|
||||||
|
|
||||||
return configFile, fs.Args(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) parseConfig() {
|
|
||||||
// XXX: hack with double parsing for 2 purposes:
|
|
||||||
// 1. to access "config" option here.
|
|
||||||
// 2. to give config less priority than command line.
|
|
||||||
|
|
||||||
configFile, restArgs, err := parseConfigOption()
|
|
||||||
if err != nil {
|
|
||||||
return // skippable error, e.g. --no-config
|
|
||||||
}
|
|
||||||
|
|
||||||
if configFile != "" {
|
|
||||||
viper.SetConfigFile(configFile)
|
|
||||||
} else {
|
|
||||||
setupConfigFileSearch(restArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
e.parseConfigImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixSlicesFlags(fs *pflag.FlagSet) {
|
|
||||||
// It's a dirty hack to set flag.Changed to true for every string slice flag.
|
|
||||||
// It's necessary to merge config and command-line slices: otherwise command-line
|
|
||||||
// flags will always overwrite ones from the config.
|
|
||||||
fs.VisitAll(func(f *pflag.Flag) {
|
|
||||||
if f.Value.Type() != "stringSlice" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := fs.GetStringSlice(f.Name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if s == nil { // assume that every string slice flag has nil as the default
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// calling Set sets Changed to true: next Set calls will append, not overwrite
|
|
||||||
_ = f.Value.Set(strings.Join(s, ","))
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -35,7 +35,7 @@ var DefaultExcludePatterns = []ExcludePattern{
|
||||||
Why: "Almost all programs ignore errors on these functions and in most cases it's ok",
|
Why: "Almost all programs ignore errors on these functions and in most cases it's ok",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pattern: "(comment on exported (method|function|type)|should have( a package)? comment|comment should be of the form)",
|
Pattern: "(comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)",
|
||||||
Linter: "golint",
|
Linter: "golint",
|
||||||
Why: "Annoying issue about not having a comment. The rare codebase has such comments",
|
Why: "Annoying issue about not having a comment. The rare codebase has such comments",
|
||||||
},
|
},
|
||||||
|
|
193
pkg/config/reader.go
Normal file
193
pkg/config/reader.go
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/printers"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlagSetInit func(fs *pflag.FlagSet, cfg *Config)
|
||||||
|
|
||||||
|
type FileReader struct {
|
||||||
|
log logutils.Log
|
||||||
|
cfg *Config
|
||||||
|
flagSetInit FlagSetInit
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileReader(toCfg *Config, log logutils.Log, flagSetInit FlagSetInit) *FileReader {
|
||||||
|
return &FileReader{
|
||||||
|
log: log,
|
||||||
|
cfg: toCfg,
|
||||||
|
flagSetInit: flagSetInit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileReader) Read() error {
|
||||||
|
// XXX: hack with double parsing for 2 purposes:
|
||||||
|
// 1. to access "config" option here.
|
||||||
|
// 2. to give config less priority than command line.
|
||||||
|
|
||||||
|
configFile, restArgs, err := r.parseConfigOption()
|
||||||
|
if err != nil {
|
||||||
|
if err == errConfigDisabled || err == pflag.ErrHelp {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("can't parse --config option: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configFile != "" {
|
||||||
|
viper.SetConfigFile(configFile)
|
||||||
|
} else {
|
||||||
|
r.setupConfigFileSearch(restArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.parseConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileReader) parseConfig() error {
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("can't read viper config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
usedConfigFile := viper.ConfigFileUsed()
|
||||||
|
if usedConfigFile == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
usedConfigFile, err := fsutils.ShortestRelPath(usedConfigFile, "")
|
||||||
|
if err != nil {
|
||||||
|
r.log.Warnf("Can't pretty print config file path: %s", err)
|
||||||
|
}
|
||||||
|
r.log.Infof("Used config file %s", usedConfigFile)
|
||||||
|
|
||||||
|
if err := viper.Unmarshal(r.cfg); err != nil {
|
||||||
|
return fmt.Errorf("can't unmarshal config by viper: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.validateConfig(); err != nil {
|
||||||
|
return fmt.Errorf("can't validate config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.cfg.InternalTest { // just for testing purposes: to detect config file usage
|
||||||
|
fmt.Fprintln(printers.StdOut, "test")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileReader) validateConfig() error {
|
||||||
|
c := r.cfg
|
||||||
|
if len(c.Run.Args) != 0 {
|
||||||
|
return errors.New("option run.args in config isn't supported now")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Run.CPUProfilePath != "" {
|
||||||
|
return errors.New("option run.cpuprofilepath in config isn't allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Run.MemProfilePath != "" {
|
||||||
|
return errors.New("option run.memprofilepath in config isn't allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Run.IsVerbose {
|
||||||
|
return errors.New("can't set run.verbose option with config: only on command-line")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileReader) setupConfigFileSearch(args []string) {
|
||||||
|
// skip all args ([golangci-lint, run/linters]) before files/dirs list
|
||||||
|
for len(args) != 0 {
|
||||||
|
if args[0] == "run" {
|
||||||
|
args = args[1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// find first file/dir arg
|
||||||
|
firstArg := "./..."
|
||||||
|
if len(args) != 0 {
|
||||||
|
firstArg = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
absStartPath, err := filepath.Abs(firstArg)
|
||||||
|
if err != nil {
|
||||||
|
r.log.Warnf("Can't make abs path for %q: %s", firstArg, err)
|
||||||
|
absStartPath = filepath.Clean(firstArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// start from it
|
||||||
|
var curDir string
|
||||||
|
if fsutils.IsDir(absStartPath) {
|
||||||
|
curDir = absStartPath
|
||||||
|
} else {
|
||||||
|
curDir = filepath.Dir(absStartPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all dirs from it up to the root
|
||||||
|
configSearchPaths := []string{"./"}
|
||||||
|
for {
|
||||||
|
configSearchPaths = append(configSearchPaths, curDir)
|
||||||
|
newCurDir := filepath.Dir(curDir)
|
||||||
|
if curDir == newCurDir || newCurDir == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
curDir = newCurDir
|
||||||
|
}
|
||||||
|
|
||||||
|
r.log.Infof("Config search paths: %s", configSearchPaths)
|
||||||
|
viper.SetConfigName(".golangci")
|
||||||
|
for _, p := range configSearchPaths {
|
||||||
|
viper.AddConfigPath(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errConfigDisabled = errors.New("config is disabled by --no-config")
|
||||||
|
|
||||||
|
func (r *FileReader) parseConfigOption() (string, []string, error) {
|
||||||
|
// We use another pflag.FlagSet here to not set `changed` flag
|
||||||
|
// on cmd.Flags() options. Otherwise string slice options will be duplicated.
|
||||||
|
fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError)
|
||||||
|
|
||||||
|
var cfg Config
|
||||||
|
r.flagSetInit(fs, &cfg)
|
||||||
|
|
||||||
|
fs.Usage = func() {} // otherwise help text will be printed twice
|
||||||
|
if err := fs.Parse(os.Args); err != nil {
|
||||||
|
if err == pflag.ErrHelp {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil, fmt.Errorf("can't parse args: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for `-v` to work until running of preRun function
|
||||||
|
logutils.SetupVerboseLog(r.log, cfg.Run.IsVerbose)
|
||||||
|
|
||||||
|
configFile := cfg.Run.Config
|
||||||
|
if cfg.Run.NoConfig && configFile != "" {
|
||||||
|
return "", nil, fmt.Errorf("can't combine option --config and --no-config")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Run.NoConfig {
|
||||||
|
return "", nil, errConfigDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
return configFile, fs.Args(), nil
|
||||||
|
}
|
9
pkg/exitcodes/exitcodes.go
Normal file
9
pkg/exitcodes/exitcodes.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package exitcodes
|
||||||
|
|
||||||
|
const (
|
||||||
|
Success = 0
|
||||||
|
IssuesFound = 1
|
||||||
|
WarningInTest = 2
|
||||||
|
Failure = 3
|
||||||
|
Timeout = 4
|
||||||
|
)
|
|
@ -1,10 +1,40 @@
|
||||||
package fsutils
|
package fsutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsDir(filename string) bool {
|
func IsDir(filename string) bool {
|
||||||
fi, err := os.Stat(filename)
|
fi, err := os.Stat(filename)
|
||||||
return err == nil && fi.IsDir()
|
return err == nil && fi.IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ShortestRelPath(path string, wd string) (string, error) {
|
||||||
|
if wd == "" { // get it if user don't have cached working dir
|
||||||
|
var err error
|
||||||
|
wd, err = os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("can't get working directory: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make path absolute and then relative to be able to fix this case:
|
||||||
|
// we'are in /test dir, we want to normalize ../test, and have file file.go in this dir;
|
||||||
|
// it must have normalized path file.go, not ../test/file.go,
|
||||||
|
var absPath string
|
||||||
|
if filepath.IsAbs(path) {
|
||||||
|
absPath = path
|
||||||
|
} else {
|
||||||
|
absPath = filepath.Join(wd, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
relPath, err := filepath.Rel(wd, absPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("can't get relative path for path %s and root %s: %s",
|
||||||
|
absPath, wd, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return relPath, nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/GoASTScanner/gas"
|
"github.com/GoASTScanner/gas"
|
||||||
"github.com/GoASTScanner/gas/rules"
|
"github.com/GoASTScanner/gas/rules"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ func (lint Gas) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issu
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r = &result.Range{}
|
r = &result.Range{}
|
||||||
if n, rerr := fmt.Sscanf(i.Line, "%d-%d", &r.From, &r.To); rerr != nil || n != 2 {
|
if n, rerr := fmt.Sscanf(i.Line, "%d-%d", &r.From, &r.To); rerr != nil || n != 2 {
|
||||||
logutils.HiddenWarnf("Can't convert gas line number %q of %v to int: %s", i.Line, i, err)
|
lintCtx.Log.Warnf("Can't convert gas line number %q of %v to int: %s", i.Line, i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
line = r.From
|
line = r.From
|
||||||
|
|
|
@ -9,8 +9,8 @@ import (
|
||||||
gofmtAPI "github.com/golangci/gofmt/gofmt"
|
gofmtAPI "github.com/golangci/gofmt/gofmt"
|
||||||
goimportsAPI "github.com/golangci/gofmt/goimports"
|
goimportsAPI "github.com/golangci/gofmt/goimports"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"sourcegraph.com/sourcegraph/go-diff/diff"
|
"sourcegraph.com/sourcegraph/go-diff/diff"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ func getFirstDeletedAndAddedLineNumberInHunk(h *diff.Hunk) (int, int, error) {
|
||||||
return 0, firstAddedLineNumber, fmt.Errorf("didn't find deletion line in hunk %s", string(h.Body))
|
return 0, firstAddedLineNumber, fmt.Errorf("didn't find deletion line in hunk %s", string(h.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Gofmt) extractIssuesFromPatch(patch string) ([]result.Issue, error) {
|
func (g Gofmt) extractIssuesFromPatch(patch string, log logutils.Log) ([]result.Issue, error) {
|
||||||
diffs, err := diff.ParseMultiFileDiff([]byte(patch))
|
diffs, err := diff.ParseMultiFileDiff([]byte(patch))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't parse patch: %s", err)
|
return nil, fmt.Errorf("can't parse patch: %s", err)
|
||||||
|
@ -68,7 +68,7 @@ func (g Gofmt) extractIssuesFromPatch(patch string) ([]result.Issue, error) {
|
||||||
issues := []result.Issue{}
|
issues := []result.Issue{}
|
||||||
for _, d := range diffs {
|
for _, d := range diffs {
|
||||||
if len(d.Hunks) == 0 {
|
if len(d.Hunks) == 0 {
|
||||||
logrus.Warnf("Got no hunks in diff %+v", d)
|
log.Warnf("Got no hunks in diff %+v", d)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ func (g Gofmt) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
is, err := g.extractIssuesFromPatch(string(diff))
|
is, err := g.extractIssuesFromPatch(string(diff), lintCtx.Log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't extract issues from gofmt diff output %q: %s", string(diff), err)
|
return nil, fmt.Errorf("can't extract issues from gofmt diff output %q: %s", string(diff), err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
lintAPI "github.com/golangci/lint-1"
|
lintAPI "github.com/golangci/lint-1"
|
||||||
)
|
)
|
||||||
|
@ -39,7 +38,7 @@ func (g Golint) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issu
|
||||||
issues = append(issues, i...)
|
issues = append(issues, i...)
|
||||||
}
|
}
|
||||||
if lintErr != nil {
|
if lintErr != nil {
|
||||||
logutils.HiddenWarnf("golint: %s", lintErr)
|
lintCtx.Log.Warnf("Golint: %s", lintErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return issues, nil
|
return issues, nil
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
megacheckAPI "github.com/golangci/go-tools/cmd/megacheck"
|
megacheckAPI "github.com/golangci/go-tools/cmd/megacheck"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Megacheck struct {
|
type Megacheck struct {
|
||||||
|
@ -57,7 +56,7 @@ func (m Megacheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.I
|
||||||
for _, p := range lintCtx.NotCompilingPackages {
|
for _, p := range lintCtx.NotCompilingPackages {
|
||||||
packages = append(packages, p.String())
|
packages = append(packages, p.String())
|
||||||
}
|
}
|
||||||
logrus.Warnf("Can't run megacheck because of compilation errors in packages "+
|
lintCtx.Log.Warnf("Can't run megacheck because of compilation errors in packages "+
|
||||||
"%s: run `typecheck` linter to see errors", packages)
|
"%s: run `typecheck` linter to see errors", packages)
|
||||||
// megacheck crashes if there are not compiling packages
|
// megacheck crashes if there are not compiling packages
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -35,7 +35,7 @@ func getASTFilesForPkg(ctx *linter.Context, pkg *packages.Package) ([]*ast.File,
|
||||||
for _, filename := range filenames {
|
for _, filename := range filenames {
|
||||||
f := ctx.ASTCache.Get(filename)
|
f := ctx.ASTCache.Get(filename)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return nil, nil, fmt.Errorf("no AST for file %s in cache", filename)
|
return nil, nil, fmt.Errorf("no AST for file %s in cache: %+v", filename, *ctx.ASTCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Err != nil {
|
if f.Err != nil {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,13 +20,15 @@ type File struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
m map[string]*File
|
m map[string]*File
|
||||||
s []*File
|
s []*File
|
||||||
|
log logutils.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCache() *Cache {
|
func NewCache(log logutils.Log) *Cache {
|
||||||
return &Cache{
|
return &Cache{
|
||||||
m: map[string]*File{},
|
m: map[string]*File{},
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +42,7 @@ func (c Cache) GetOrParse(filename string) *File {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Infof("Parse AST for file %s on demand", filename)
|
c.log.Infof("Parse AST for file %s on demand", filename)
|
||||||
c.parseFile(filename, nil)
|
c.parseFile(filename, nil)
|
||||||
return c.m[filename]
|
return c.m[filename]
|
||||||
}
|
}
|
||||||
|
@ -79,7 +81,7 @@ func LoadFromProgram(prog *loader.Program) (*Cache, error) {
|
||||||
|
|
||||||
relPath, err := filepath.Rel(root, pos.Filename)
|
relPath, err := filepath.Rel(root, pos.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("Can't get relative path for %s and %s: %s",
|
c.log.Warnf("Can't get relative path for %s and %s: %s",
|
||||||
root, pos.Filename, err)
|
root, pos.Filename, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -108,6 +110,9 @@ func (c *Cache) parseFile(filePath string, fset *token.FileSet) {
|
||||||
Err: err,
|
Err: err,
|
||||||
Name: filePath,
|
Name: filePath,
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.log.Warnf("Can't parse AST of %s: %s", filePath, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadFromFiles(files []string) (*Cache, error) {
|
func LoadFromFiles(files []string) (*Cache, error) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/golangci/go-tools/ssa"
|
"github.com/golangci/go-tools/ssa"
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/packages"
|
"github.com/golangci/golangci-lint/pkg/packages"
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
)
|
)
|
||||||
|
@ -16,6 +17,7 @@ type Context struct {
|
||||||
LoaderConfig *loader.Config
|
LoaderConfig *loader.Config
|
||||||
ASTCache *astcache.Cache
|
ASTCache *astcache.Cache
|
||||||
NotCompilingPackages []*loader.PackageInfo
|
NotCompilingPackages []*loader.PackageInfo
|
||||||
|
Log logutils.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) Settings() *config.LintersSettings {
|
func (c *Context) Settings() *config.LintersSettings {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
"github.com/golangci/golangci-lint/pkg/golinters"
|
"github.com/golangci/golangci-lint/pkg/golinters"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AllPresets() []string {
|
func AllPresets() []string {
|
||||||
|
@ -398,7 +398,7 @@ func optimizeLintersSet(linters map[string]*linter.Config) {
|
||||||
linters[m.Name()] = &lc
|
linters[m.Name()] = &lc
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEnabledLinters(cfg *config.Config) ([]linter.Config, error) {
|
func GetEnabledLinters(cfg *config.Config, log logutils.Log) ([]linter.Config, error) {
|
||||||
if err := validateEnabledDisabledLintersConfig(&cfg.Linters); err != nil {
|
if err := validateEnabledDisabledLintersConfig(&cfg.Linters); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -410,21 +410,21 @@ func GetEnabledLinters(cfg *config.Config) ([]linter.Config, error) {
|
||||||
resultLinters = append(resultLinters, *lc)
|
resultLinters = append(resultLinters, *lc)
|
||||||
}
|
}
|
||||||
|
|
||||||
verbosePrintLintersStatus(cfg, resultLinters)
|
verbosePrintLintersStatus(cfg, resultLinters, log)
|
||||||
|
|
||||||
return resultLinters, nil
|
return resultLinters, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config) {
|
func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config, log logutils.Log) {
|
||||||
var linterNames []string
|
var linterNames []string
|
||||||
for _, lc := range lcs {
|
for _, lc := range lcs {
|
||||||
linterNames = append(linterNames, lc.Linter.Name())
|
linterNames = append(linterNames, lc.Linter.Name())
|
||||||
}
|
}
|
||||||
sort.StringSlice(linterNames).Sort()
|
sort.StringSlice(linterNames).Sort()
|
||||||
logrus.Infof("Active %d linters: %s", len(linterNames), linterNames)
|
log.Infof("Active %d linters: %s", len(linterNames), linterNames)
|
||||||
|
|
||||||
if len(cfg.Linters.Presets) != 0 {
|
if len(cfg.Linters.Presets) != 0 {
|
||||||
sort.StringSlice(cfg.Linters.Presets).Sort()
|
sort.StringSlice(cfg.Linters.Presets).Sort()
|
||||||
logrus.Infof("Active presets: %s", cfg.Linters.Presets)
|
log.Infof("Active presets: %s", cfg.Linters.Presets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
"github.com/golangci/golangci-lint/pkg/packages"
|
"github.com/golangci/golangci-lint/pkg/packages"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -105,7 +104,7 @@ func isLocalProjectAnalysis(args []string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *packages.Program) func(string) bool {
|
func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *packages.Program, log logutils.Log) func(string) bool {
|
||||||
if !isLocalProjectAnalysis(cfg.Args) {
|
if !isLocalProjectAnalysis(cfg.Args) {
|
||||||
loadDebugf("analysis in nonlocal, don't optimize loading by not typechecking func bodies")
|
loadDebugf("analysis in nonlocal, don't optimize loading by not typechecking func bodies")
|
||||||
return nil
|
return nil
|
||||||
|
@ -123,7 +122,7 @@ func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *p
|
||||||
|
|
||||||
projPath, err := getCurrentProjectImportPath()
|
projPath, err := getCurrentProjectImportPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Infof("can't get cur project path: %s", err)
|
log.Infof("Can't get cur project path: %s", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,14 +150,14 @@ func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Run, pkgProg *packages.Program) (*loader.Program, *loader.Config, error) {
|
func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Run, pkgProg *packages.Program, log logutils.Log) (*loader.Program, *loader.Config, error) {
|
||||||
if !isFullImportNeeded(linters) {
|
if !isFullImportNeeded(linters) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
logrus.Infof("Program loading took %s", time.Since(startedAt))
|
log.Infof("Program loading took %s", time.Since(startedAt))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
bctx := pkgProg.BuildContext()
|
bctx := pkgProg.BuildContext()
|
||||||
|
@ -166,7 +165,7 @@ func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *con
|
||||||
Build: bctx,
|
Build: bctx,
|
||||||
AllowErrors: true, // Try to analyze partially
|
AllowErrors: true, // Try to analyze partially
|
||||||
ParserMode: parser.ParseComments, // AST will be reused by linters
|
ParserMode: parser.ParseComments, // AST will be reused by linters
|
||||||
TypeCheckFuncBodies: getTypeCheckFuncBodies(cfg, linters, pkgProg),
|
TypeCheckFuncBodies: getTypeCheckFuncBodies(cfg, linters, pkgProg, log),
|
||||||
}
|
}
|
||||||
|
|
||||||
var loaderArgs []string
|
var loaderArgs []string
|
||||||
|
@ -195,13 +194,22 @@ func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *con
|
||||||
return nil, nil, fmt.Errorf("can't load program from paths %v: %s", loaderArgs, err)
|
return nil, nil, fmt.Errorf("can't load program from paths %v: %s", loaderArgs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(prog.InitialPackages()) == 1 {
|
||||||
|
pkg := prog.InitialPackages()[0]
|
||||||
|
var files []string
|
||||||
|
for _, f := range pkg.Files {
|
||||||
|
files = append(files, prog.Fset.Position(f.Pos()).Filename)
|
||||||
|
}
|
||||||
|
log.Infof("pkg %s files: %s", pkg, files)
|
||||||
|
}
|
||||||
|
|
||||||
return prog, loadcfg, nil
|
return prog, loadcfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSSAProgram(ctx context.Context, lprog *loader.Program) *ssa.Program {
|
func buildSSAProgram(ctx context.Context, lprog *loader.Program, log logutils.Log) *ssa.Program {
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
logrus.Infof("SSA repr building took %s", time.Since(startedAt))
|
log.Infof("SSA repr building took %s", time.Since(startedAt))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ssaProg := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
ssaProg := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
||||||
|
@ -249,10 +257,14 @@ func separateNotCompilingPackages(lintCtx *linter.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(lintCtx.NotCompilingPackages) != 0 {
|
||||||
|
lintCtx.Log.Infof("Not compiling packages: %+v", lintCtx.NotCompilingPackages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Config) (*linter.Context, error) {
|
func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Config, log logutils.Log) (*linter.Context, error) {
|
||||||
// Set GOROOT to have working cross-compilation: cross-compiled binaries
|
// Set GOROOT to have working cross-compilation: cross-compiled binaries
|
||||||
// have invalid GOROOT. XXX: can't use runtime.GOROOT().
|
// have invalid GOROOT. XXX: can't use runtime.GOROOT().
|
||||||
goroot, err := discoverGoRoot()
|
goroot, err := discoverGoRoot()
|
||||||
|
@ -269,7 +281,7 @@ func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Confi
|
||||||
|
|
||||||
skipDirs := append([]string{}, packages.StdExcludeDirRegexps...)
|
skipDirs := append([]string{}, packages.StdExcludeDirRegexps...)
|
||||||
skipDirs = append(skipDirs, cfg.Run.SkipDirs...)
|
skipDirs = append(skipDirs, cfg.Run.SkipDirs...)
|
||||||
r, err := packages.NewResolver(cfg.Run.BuildTags, skipDirs)
|
r, err := packages.NewResolver(cfg.Run.BuildTags, skipDirs, log.Child("path_resolver"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -279,14 +291,14 @@ func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Confi
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
prog, loaderConfig, err := loadWholeAppIfNeeded(ctx, linters, &cfg.Run, pkgProg)
|
prog, loaderConfig, err := loadWholeAppIfNeeded(ctx, linters, &cfg.Run, pkgProg, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ssaProg *ssa.Program
|
var ssaProg *ssa.Program
|
||||||
if prog != nil && isSSAReprNeeded(linters) {
|
if prog != nil && isSSAReprNeeded(linters) {
|
||||||
ssaProg = buildSSAProgram(ctx, prog)
|
ssaProg = buildSSAProgram(ctx, prog, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
var astCache *astcache.Cache
|
var astCache *astcache.Cache
|
||||||
|
@ -306,6 +318,7 @@ func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Confi
|
||||||
SSAProgram: ssaProg,
|
SSAProgram: ssaProg,
|
||||||
LoaderConfig: loaderConfig,
|
LoaderConfig: loaderConfig,
|
||||||
ASTCache: astCache,
|
ASTCache: astCache,
|
||||||
|
Log: log,
|
||||||
}
|
}
|
||||||
|
|
||||||
if prog != nil {
|
if prog != nil {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
|
@ -19,7 +21,7 @@ func TestASTCacheLoading(t *testing.T) {
|
||||||
|
|
||||||
inputPaths := []string{"./...", "./", "./load.go", "load.go"}
|
inputPaths := []string{"./...", "./", "./load.go", "load.go"}
|
||||||
for _, inputPath := range inputPaths {
|
for _, inputPath := range inputPaths {
|
||||||
r, err := packages.NewResolver(nil, nil)
|
r, err := packages.NewResolver(nil, nil, logutils.NewStderrLog(""))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
pkgProg, err := r.Resolve(inputPath)
|
pkgProg, err := r.Resolve(inputPath)
|
||||||
|
@ -30,7 +32,7 @@ func TestASTCacheLoading(t *testing.T) {
|
||||||
|
|
||||||
prog, _, err := loadWholeAppIfNeeded(ctx, linters, &config.Run{
|
prog, _, err := loadWholeAppIfNeeded(ctx, linters, &config.Run{
|
||||||
AnalyzeTests: true,
|
AnalyzeTests: true,
|
||||||
}, pkgProg)
|
}, pkgProg, logutils.NewStderrLog(""))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
astCacheFromProg, err := astcache.LoadFromProgram(prog)
|
astCacheFromProg, err := astcache.LoadFromProgram(prog)
|
||||||
|
|
|
@ -9,16 +9,55 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
"github.com/golangci/golangci-lint/pkg/result/processors"
|
"github.com/golangci/golangci-lint/pkg/result/processors"
|
||||||
"github.com/golangci/golangci-lint/pkg/timeutils"
|
"github.com/golangci/golangci-lint/pkg/timeutils"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SimpleRunner struct {
|
type Runner struct {
|
||||||
Processors []processors.Processor
|
Processors []processors.Processor
|
||||||
|
Log logutils.Log
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log) (*Runner, error) {
|
||||||
|
icfg := cfg.Issues
|
||||||
|
excludePatterns := icfg.ExcludePatterns
|
||||||
|
if icfg.UseDefaultExcludes {
|
||||||
|
excludePatterns = append(excludePatterns, config.GetDefaultExcludePatternsStrings()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var excludeTotalPattern string
|
||||||
|
if len(excludePatterns) != 0 {
|
||||||
|
excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(excludePatterns, "|"))
|
||||||
|
}
|
||||||
|
|
||||||
|
skipFilesProcessor, err := processors.NewSkipFiles(cfg.Run.SkipFiles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Runner{
|
||||||
|
Processors: []processors.Processor{
|
||||||
|
processors.NewPathPrettifier(), // must be before diff, nolint and exclude autogenerated processor at least
|
||||||
|
processors.NewCgo(),
|
||||||
|
skipFilesProcessor,
|
||||||
|
|
||||||
|
processors.NewAutogeneratedExclude(astCache),
|
||||||
|
processors.NewExclude(excludeTotalPattern),
|
||||||
|
processors.NewNolint(astCache),
|
||||||
|
|
||||||
|
processors.NewUniqByLine(),
|
||||||
|
processors.NewDiff(icfg.Diff, icfg.DiffFromRevision, icfg.DiffPatchFilePath),
|
||||||
|
processors.NewMaxPerFileFromLinter(),
|
||||||
|
processors.NewMaxSameIssues(icfg.MaxSameIssues, log.Child("max_same_issues")),
|
||||||
|
processors.NewMaxFromLinter(icfg.MaxIssuesPerLinter, log.Child("max_from_linter")),
|
||||||
|
},
|
||||||
|
Log: log,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type lintRes struct {
|
type lintRes struct {
|
||||||
|
@ -27,15 +66,17 @@ type lintRes struct {
|
||||||
issues []result.Issue
|
issues []result.Issue
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLinterSafe(ctx context.Context, lintCtx *linter.Context, lc linter.Config) (ret []result.Issue, err error) {
|
func (r Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context, lc linter.Config) (ret []result.Issue, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if panicData := recover(); panicData != nil {
|
if panicData := recover(); panicData != nil {
|
||||||
err = fmt.Errorf("panic occured: %s", panicData)
|
err = fmt.Errorf("panic occured: %s", panicData)
|
||||||
logutils.HiddenWarnf("Panic stack trace: %s", debug.Stack())
|
r.Log.Warnf("Panic stack trace: %s", debug.Stack())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
issues, err := lc.Linter.Run(ctx, lintCtx)
|
specificLintCtx := *lintCtx
|
||||||
|
specificLintCtx.Log = lintCtx.Log.Child(lc.Linter.Name())
|
||||||
|
issues, err := lc.Linter.Run(ctx, &specificLintCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -47,8 +88,8 @@ func runLinterSafe(ctx context.Context, lintCtx *linter.Context, lc linter.Confi
|
||||||
return issues, nil
|
return issues, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runWorker(ctx context.Context, lintCtx *linter.Context, tasksCh <-chan linter.Config, lintResultsCh chan<- lintRes, name string) {
|
func (r Runner) runWorker(ctx context.Context, lintCtx *linter.Context, tasksCh <-chan linter.Config, lintResultsCh chan<- lintRes, name string) {
|
||||||
sw := timeutils.NewStopwatch(name)
|
sw := timeutils.NewStopwatch(name, r.Log)
|
||||||
defer sw.Print()
|
defer sw.Print()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -67,7 +108,7 @@ func runWorker(ctx context.Context, lintCtx *linter.Context, tasksCh <-chan lint
|
||||||
var issues []result.Issue
|
var issues []result.Issue
|
||||||
var err error
|
var err error
|
||||||
sw.TrackStage(lc.Linter.Name(), func() {
|
sw.TrackStage(lc.Linter.Name(), func() {
|
||||||
issues, err = runLinterSafe(ctx, lintCtx, lc)
|
issues, err = r.runLinterSafe(ctx, lintCtx, lc)
|
||||||
})
|
})
|
||||||
lintResultsCh <- lintRes{
|
lintResultsCh <- lintRes{
|
||||||
linter: lc,
|
linter: lc,
|
||||||
|
@ -78,7 +119,7 @@ func runWorker(ctx context.Context, lintCtx *linter.Context, tasksCh <-chan lint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func logWorkersStat(workersFinishTimes []time.Time) {
|
func (r Runner) logWorkersStat(workersFinishTimes []time.Time) {
|
||||||
lastFinishTime := workersFinishTimes[0]
|
lastFinishTime := workersFinishTimes[0]
|
||||||
for _, t := range workersFinishTimes {
|
for _, t := range workersFinishTimes {
|
||||||
if t.After(lastFinishTime) {
|
if t.After(lastFinishTime) {
|
||||||
|
@ -95,7 +136,7 @@ func logWorkersStat(workersFinishTimes []time.Time) {
|
||||||
logStrings = append(logStrings, fmt.Sprintf("#%d: %s", i+1, lastFinishTime.Sub(t)))
|
logStrings = append(logStrings, fmt.Sprintf("#%d: %s", i+1, lastFinishTime.Sub(t)))
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Infof("Workers idle times: %s", strings.Join(logStrings, ", "))
|
r.Log.Infof("Workers idle times: %s", strings.Join(logStrings, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSortedLintersConfigs(linters []linter.Config) []linter.Config {
|
func getSortedLintersConfigs(linters []linter.Config) []linter.Config {
|
||||||
|
@ -109,7 +150,7 @@ func getSortedLintersConfigs(linters []linter.Config) []linter.Config {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SimpleRunner) runWorkers(ctx context.Context, lintCtx *linter.Context, linters []linter.Config) <-chan lintRes {
|
func (r *Runner) runWorkers(ctx context.Context, lintCtx *linter.Context, linters []linter.Config) <-chan lintRes {
|
||||||
tasksCh := make(chan linter.Config, len(linters))
|
tasksCh := make(chan linter.Config, len(linters))
|
||||||
lintResultsCh := make(chan lintRes, len(linters))
|
lintResultsCh := make(chan lintRes, len(linters))
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
@ -121,7 +162,7 @@ func (r *SimpleRunner) runWorkers(ctx context.Context, lintCtx *linter.Context,
|
||||||
go func(i int) {
|
go func(i int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
name := fmt.Sprintf("worker.%d", i+1)
|
name := fmt.Sprintf("worker.%d", i+1)
|
||||||
runWorker(ctx, lintCtx, tasksCh, lintResultsCh, name)
|
r.runWorker(ctx, lintCtx, tasksCh, lintResultsCh, name)
|
||||||
workersFinishTimes[i] = time.Now()
|
workersFinishTimes[i] = time.Now()
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
@ -136,23 +177,23 @@ func (r *SimpleRunner) runWorkers(ctx context.Context, lintCtx *linter.Context,
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(lintResultsCh)
|
close(lintResultsCh)
|
||||||
|
|
||||||
logWorkersStat(workersFinishTimes)
|
r.logWorkersStat(workersFinishTimes)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return lintResultsCh
|
return lintResultsCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r SimpleRunner) processLintResults(ctx context.Context, inCh <-chan lintRes) <-chan lintRes {
|
func (r Runner) processLintResults(ctx context.Context, inCh <-chan lintRes) <-chan lintRes {
|
||||||
outCh := make(chan lintRes, 64)
|
outCh := make(chan lintRes, 64)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
sw := timeutils.NewStopwatch("processing")
|
sw := timeutils.NewStopwatch("processing", r.Log)
|
||||||
|
|
||||||
defer close(outCh)
|
defer close(outCh)
|
||||||
|
|
||||||
for res := range inCh {
|
for res := range inCh {
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
logutils.HiddenWarnf("Can't run linter %s: %s", res.linter.Linter.Name(), res.err)
|
r.Log.Warnf("Can't run linter %s: %s", res.linter.Linter.Name(), res.err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +236,7 @@ func collectIssues(ctx context.Context, resCh <-chan lintRes) <-chan result.Issu
|
||||||
return retIssues
|
return retIssues
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r SimpleRunner) Run(ctx context.Context, linters []linter.Config, lintCtx *linter.Context) <-chan result.Issue {
|
func (r Runner) Run(ctx context.Context, linters []linter.Config, lintCtx *linter.Context) <-chan result.Issue {
|
||||||
lintResultsCh := r.runWorkers(ctx, lintCtx, linters)
|
lintResultsCh := r.runWorkers(ctx, lintCtx, linters)
|
||||||
processedLintResultsCh := r.processLintResults(ctx, lintResultsCh)
|
processedLintResultsCh := r.processLintResults(ctx, lintResultsCh)
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
|
@ -205,14 +246,14 @@ func (r SimpleRunner) Run(ctx context.Context, linters []linter.Config, lintCtx
|
||||||
finishedLintersN++
|
finishedLintersN++
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Warnf("%d/%d linters finished: deadline exceeded: try increase it by passing --deadline option",
|
r.Log.Errorf("%d/%d linters finished: deadline exceeded: try increase it by passing --deadline option",
|
||||||
finishedLintersN, len(linters))
|
finishedLintersN, len(linters))
|
||||||
}
|
}
|
||||||
|
|
||||||
return collectIssues(ctx, processedLintResultsCh)
|
return collectIssues(ctx, processedLintResultsCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SimpleRunner) processIssues(ctx context.Context, issues []result.Issue, sw *timeutils.Stopwatch) []result.Issue {
|
func (r *Runner) processIssues(ctx context.Context, issues []result.Issue, sw *timeutils.Stopwatch) []result.Issue {
|
||||||
for _, p := range r.Processors {
|
for _, p := range r.Processors {
|
||||||
var newIssues []result.Issue
|
var newIssues []result.Issue
|
||||||
var err error
|
var err error
|
||||||
|
@ -221,7 +262,7 @@ func (r *SimpleRunner) processIssues(ctx context.Context, issues []result.Issue,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logutils.HiddenWarnf("Can't process result by %s processor: %s", p.Name(), err)
|
r.Log.Warnf("Can't process result by %s processor: %s", p.Name(), err)
|
||||||
} else {
|
} else {
|
||||||
issues = newIssues
|
issues = newIssues
|
||||||
}
|
}
|
||||||
|
|
30
pkg/logutils/log.go
Normal file
30
pkg/logutils/log.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package logutils
|
||||||
|
|
||||||
|
type Log interface {
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
Warnf(format string, args ...interface{})
|
||||||
|
Infof(format string, args ...interface{})
|
||||||
|
|
||||||
|
Child(name string) Log
|
||||||
|
SetLevel(level LogLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogLevel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// debug message, write to debug logs only by logutils.Debug
|
||||||
|
LogLevelDebug LogLevel = 0
|
||||||
|
|
||||||
|
// information messages, don't write too much messages,
|
||||||
|
// only useful ones: they are shown when running with -v
|
||||||
|
LogLevelInfo LogLevel = 1
|
||||||
|
|
||||||
|
// hidden errors: non critical errors: work can be continued, no need to fail whole program;
|
||||||
|
// tests will crash if any warning occured.
|
||||||
|
LogLevelWarn LogLevel = 2
|
||||||
|
|
||||||
|
// only not hidden from user errors: whole program failing, usually
|
||||||
|
// error logging happens in 1-2 places: in the "main" function.
|
||||||
|
LogLevelError LogLevel = 3
|
||||||
|
)
|
|
@ -3,20 +3,8 @@ package logutils
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var isGolangCIRun = os.Getenv("GOLANGCI_COM_RUN") == "1"
|
|
||||||
|
|
||||||
func HiddenWarnf(format string, args ...interface{}) {
|
|
||||||
if isGolangCIRun {
|
|
||||||
logrus.Warnf(format, args...)
|
|
||||||
} else {
|
|
||||||
logrus.Infof(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEnabledDebugs() map[string]bool {
|
func getEnabledDebugs() map[string]bool {
|
||||||
ret := map[string]bool{}
|
ret := map[string]bool{}
|
||||||
debugVar := os.Getenv("GL_DEBUG")
|
debugVar := os.Getenv("GL_DEBUG")
|
||||||
|
@ -43,8 +31,9 @@ func Debug(tag string) DebugFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(format string, args ...interface{}) {
|
return func(format string, args ...interface{}) {
|
||||||
newArgs := append([]interface{}{tag}, args...)
|
logger := NewStderrLog(tag)
|
||||||
logrus.Debugf("%s: "+format, newArgs...)
|
logger.SetLevel(LogLevelDebug)
|
||||||
|
logger.Debugf(format, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,3 +44,9 @@ func IsDebugEnabled() bool {
|
||||||
func HaveDebugTag(tag string) bool {
|
func HaveDebugTag(tag string) bool {
|
||||||
return enabledDebugs[tag]
|
return enabledDebugs[tag]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetupVerboseLog(log Log, isVerbose bool) {
|
||||||
|
if isVerbose {
|
||||||
|
log.SetLevel(LogLevelInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
106
pkg/logutils/stderr_log.go
Normal file
106
pkg/logutils/stderr_log.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package logutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||||
|
"github.com/sirupsen/logrus" //nolint:depguard
|
||||||
|
)
|
||||||
|
|
||||||
|
var isTestRun = os.Getenv("GL_TEST_RUN") == "1"
|
||||||
|
|
||||||
|
type StderrLog struct {
|
||||||
|
name string
|
||||||
|
logger *logrus.Logger
|
||||||
|
level LogLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Log = NewStderrLog("")
|
||||||
|
|
||||||
|
func NewStderrLog(name string) *StderrLog {
|
||||||
|
sl := &StderrLog{
|
||||||
|
name: name,
|
||||||
|
logger: logrus.New(),
|
||||||
|
level: LogLevelWarn,
|
||||||
|
}
|
||||||
|
|
||||||
|
// control log level in logutils, not in logrus
|
||||||
|
sl.logger.SetLevel(logrus.DebugLevel)
|
||||||
|
sl.logger.Formatter = &logrus.TextFormatter{
|
||||||
|
DisableTimestamp: true, // `INFO[0007] msg` -> `INFO msg`
|
||||||
|
}
|
||||||
|
return sl
|
||||||
|
}
|
||||||
|
|
||||||
|
func exitIfTest() {
|
||||||
|
if isTestRun {
|
||||||
|
os.Exit(exitcodes.WarningInTest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl StderrLog) prefix() string {
|
||||||
|
prefix := ""
|
||||||
|
if sl.name != "" {
|
||||||
|
prefix = fmt.Sprintf("[%s] ", sl.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl StderrLog) Fatalf(format string, args ...interface{}) {
|
||||||
|
sl.logger.Errorf("%s%s", sl.prefix(), fmt.Sprintf(format, args...))
|
||||||
|
os.Exit(exitcodes.Failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl StderrLog) Errorf(format string, args ...interface{}) {
|
||||||
|
if sl.level > LogLevelError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sl.logger.Errorf("%s%s", sl.prefix(), fmt.Sprintf(format, args...))
|
||||||
|
// don't call exitIfTest() because the idea is to
|
||||||
|
// crash on hidden errors (warnings); but Errorf MUST NOT be
|
||||||
|
// called on hidden errors, see log levels comments.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl StderrLog) Warnf(format string, args ...interface{}) {
|
||||||
|
if sl.level > LogLevelWarn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sl.logger.Warnf("%s%s", sl.prefix(), fmt.Sprintf(format, args...))
|
||||||
|
exitIfTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl StderrLog) Infof(format string, args ...interface{}) {
|
||||||
|
if sl.level > LogLevelInfo {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sl.logger.Infof("%s%s", sl.prefix(), fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl StderrLog) Debugf(format string, args ...interface{}) {
|
||||||
|
if sl.level > LogLevelDebug {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sl.logger.Debugf("%s%s", sl.prefix(), fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl StderrLog) Child(name string) Log {
|
||||||
|
prefix := ""
|
||||||
|
if sl.name != "" {
|
||||||
|
prefix = sl.name + ":"
|
||||||
|
}
|
||||||
|
|
||||||
|
child := sl
|
||||||
|
child.name = prefix + name
|
||||||
|
|
||||||
|
return &child
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *StderrLog) SetLevel(level LogLevel) {
|
||||||
|
sl.level = level
|
||||||
|
}
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
|
@ -18,9 +19,12 @@ type Resolver struct {
|
||||||
buildTags []string
|
buildTags []string
|
||||||
|
|
||||||
skippedDirs []string
|
skippedDirs []string
|
||||||
|
log logutils.Log
|
||||||
|
|
||||||
|
wd string // working directory
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver(buildTags, excludeDirs []string) (*Resolver, error) {
|
func NewResolver(buildTags, excludeDirs []string, log logutils.Log) (*Resolver, error) {
|
||||||
excludeDirsMap := map[string]*regexp.Regexp{}
|
excludeDirsMap := map[string]*regexp.Regexp{}
|
||||||
for _, dir := range excludeDirs {
|
for _, dir := range excludeDirs {
|
||||||
re, err := regexp.Compile(dir)
|
re, err := regexp.Compile(dir)
|
||||||
|
@ -31,9 +35,16 @@ func NewResolver(buildTags, excludeDirs []string) (*Resolver, error) {
|
||||||
excludeDirsMap[dir] = re
|
excludeDirsMap[dir] = re
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't get working dir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &Resolver{
|
return &Resolver{
|
||||||
excludeDirs: excludeDirsMap,
|
excludeDirs: excludeDirsMap,
|
||||||
buildTags: buildTags,
|
buildTags: buildTags,
|
||||||
|
log: log,
|
||||||
|
wd: wd,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +91,15 @@ func (r *Resolver) resolveRecursively(root string, prog *Program) error {
|
||||||
|
|
||||||
subdir := filepath.Join(root, fi.Name())
|
subdir := filepath.Join(root, fi.Name())
|
||||||
|
|
||||||
|
// Normalize each subdir because working directory can be one of these subdirs:
|
||||||
|
// working dir = /app/subdir, resolve root is ../, without this normalization
|
||||||
|
// path of subdir will be "../subdir" but it must be ".".
|
||||||
|
// Normalize path before checking is ignored dir.
|
||||||
|
subdir, err := r.normalizePath(subdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if r.isIgnoredDir(subdir) {
|
if r.isIgnoredDir(subdir) {
|
||||||
r.skippedDirs = append(r.skippedDirs, subdir)
|
r.skippedDirs = append(r.skippedDirs, subdir)
|
||||||
continue
|
continue
|
||||||
|
@ -128,7 +148,7 @@ func (r Resolver) addFakePackage(filePath string, prog *Program) {
|
||||||
func (r Resolver) Resolve(paths ...string) (prog *Program, err error) {
|
func (r Resolver) Resolve(paths ...string) (prog *Program, err error) {
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
logrus.Infof("Paths resolving took %s: %s", time.Since(startedAt), prog)
|
r.log.Infof("Paths resolving took %s: %s", time.Since(startedAt), prog)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
|
@ -141,25 +161,24 @@ func (r Resolver) Resolve(paths ...string) (prog *Program, err error) {
|
||||||
bctx: bctx,
|
bctx: bctx,
|
||||||
}
|
}
|
||||||
|
|
||||||
root, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't get working dir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
if err := r.resolvePath(path, prog, root); err != nil {
|
if err := r.resolvePath(path, prog); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.skippedDirs) != 0 {
|
if len(r.skippedDirs) != 0 {
|
||||||
logrus.Infof("Skipped dirs: %s", r.skippedDirs)
|
r.log.Infof("Skipped dirs: %s", r.skippedDirs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return prog, nil
|
return prog, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) resolvePath(path string, prog *Program, root string) error {
|
func (r *Resolver) normalizePath(path string) (string, error) {
|
||||||
|
return fsutils.ShortestRelPath(path, r.wd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) resolvePath(path string, prog *Program) error {
|
||||||
needRecursive := strings.HasSuffix(path, "/...")
|
needRecursive := strings.HasSuffix(path, "/...")
|
||||||
if needRecursive {
|
if needRecursive {
|
||||||
path = filepath.Dir(path)
|
path = filepath.Dir(path)
|
||||||
|
@ -171,14 +190,9 @@ func (r *Resolver) resolvePath(path string, prog *Program, root string) error {
|
||||||
}
|
}
|
||||||
path = evalPath
|
path = evalPath
|
||||||
|
|
||||||
if filepath.IsAbs(path) {
|
path, err = r.normalizePath(path)
|
||||||
var relPath string
|
if err != nil {
|
||||||
relPath, err = filepath.Rel(root, path)
|
return err
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't get relative path for path %s and root %s: %s",
|
|
||||||
path, root, err)
|
|
||||||
}
|
|
||||||
path = relPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if needRecursive {
|
if needRecursive {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/packages"
|
"github.com/golangci/golangci-lint/pkg/packages"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -57,7 +58,7 @@ func prepareFS(t *testing.T, paths ...string) *fsPreparer {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestResolver(t *testing.T, excludeDirs []string) *packages.Resolver {
|
func newTestResolver(t *testing.T, excludeDirs []string) *packages.Resolver {
|
||||||
r, err := packages.NewResolver(nil, excludeDirs)
|
r, err := packages.NewResolver(nil, excludeDirs, logutils.NewStderrLog(""))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
|
@ -7,17 +7,19 @@ import (
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tab struct {
|
type Tab struct {
|
||||||
printLinterName bool
|
printLinterName bool
|
||||||
|
log logutils.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTab(printLinterName bool) *Tab {
|
func NewTab(printLinterName bool, log logutils.Log) *Tab {
|
||||||
return &Tab{
|
return &Tab{
|
||||||
printLinterName: printLinterName,
|
printLinterName: printLinterName,
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,14 +38,14 @@ func (p *Tab) Print(ctx context.Context, issues <-chan result.Issue) (bool, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
if issuesN != 0 {
|
if issuesN != 0 {
|
||||||
logrus.Infof("Found %d issues", issuesN)
|
p.log.Infof("Found %d issues", issuesN)
|
||||||
} else if ctx.Err() == nil { // don't print "congrats" if timeouted
|
} else if ctx.Err() == nil { // don't print "congrats" if timeouted
|
||||||
outStr := p.SprintfColored(color.FgGreen, "Congrats! No issues were found.")
|
outStr := p.SprintfColored(color.FgGreen, "Congrats! No issues were found.")
|
||||||
fmt.Fprintln(StdOut, outStr)
|
fmt.Fprintln(StdOut, outStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := w.Flush(); err != nil {
|
if err := w.Flush(); err != nil {
|
||||||
logrus.Warnf("Can't flush tab writer: %s", err)
|
p.log.Warnf("Can't flush tab writer: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return issuesN != 0, nil
|
return issuesN != 0, nil
|
||||||
|
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type linesCache [][]byte
|
type linesCache [][]byte
|
||||||
|
@ -22,14 +22,16 @@ type Text struct {
|
||||||
printLinterName bool
|
printLinterName bool
|
||||||
|
|
||||||
cache filesCache
|
cache filesCache
|
||||||
|
log logutils.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewText(printIssuedLine, useColors, printLinterName bool) *Text {
|
func NewText(printIssuedLine, useColors, printLinterName bool, log logutils.Log) *Text {
|
||||||
return &Text{
|
return &Text{
|
||||||
printIssuedLine: printIssuedLine,
|
printIssuedLine: printIssuedLine,
|
||||||
useColors: useColors,
|
useColors: useColors,
|
||||||
printLinterName: printLinterName,
|
printLinterName: printLinterName,
|
||||||
cache: filesCache{},
|
cache: filesCache{},
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +64,7 @@ func (p *Text) getFileLinesForIssue(i *result.Issue) (linesCache, error) {
|
||||||
func (p *Text) Print(ctx context.Context, issues <-chan result.Issue) (bool, error) {
|
func (p *Text) Print(ctx context.Context, issues <-chan result.Issue) (bool, error) {
|
||||||
var issuedLineExtractingDuration time.Duration
|
var issuedLineExtractingDuration time.Duration
|
||||||
defer func() {
|
defer func() {
|
||||||
logrus.Infof("Extracting issued lines took %s", issuedLineExtractingDuration)
|
p.log.Infof("Extracting issued lines took %s", issuedLineExtractingDuration)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
issuesN := 0
|
issuesN := 0
|
||||||
|
@ -88,7 +90,7 @@ func (p *Text) Print(ctx context.Context, issues <-chan result.Issue) (bool, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if issuesN != 0 {
|
if issuesN != 0 {
|
||||||
logrus.Infof("Found %d issues", issuesN)
|
p.log.Infof("Found %d issues", issuesN)
|
||||||
} else if ctx.Err() == nil { // don't print "congrats" if timeouted
|
} else if ctx.Err() == nil { // don't print "congrats" if timeouted
|
||||||
outStr := p.SprintfColored(color.FgGreen, "Congrats! No issues were found.")
|
outStr := p.SprintfColored(color.FgGreen, "Congrats! No issues were found.")
|
||||||
fmt.Fprintln(StdOut, outStr)
|
fmt.Fprintln(StdOut, outStr)
|
||||||
|
@ -119,7 +121,7 @@ func (p Text) printIssuedLines(i *result.Issue, lines linesCache) {
|
||||||
|
|
||||||
zeroIndexedLine := line - 1
|
zeroIndexedLine := line - 1
|
||||||
if zeroIndexedLine >= len(lines) {
|
if zeroIndexedLine >= len(lines) {
|
||||||
logrus.Warnf("No line %d in file %s", line, i.FilePath())
|
p.log.Warnf("No line %d in file %s", line, i.FilePath())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
package processors
|
package processors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MaxFromLinter struct {
|
type MaxFromLinter struct {
|
||||||
lc linterToCountMap
|
lc linterToCountMap
|
||||||
limit int
|
limit int
|
||||||
|
log logutils.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Processor = &MaxFromLinter{}
|
var _ Processor = &MaxFromLinter{}
|
||||||
|
|
||||||
func NewMaxFromLinter(limit int) *MaxFromLinter {
|
func NewMaxFromLinter(limit int, log logutils.Log) *MaxFromLinter {
|
||||||
return &MaxFromLinter{
|
return &MaxFromLinter{
|
||||||
lc: linterToCountMap{},
|
lc: linterToCountMap{},
|
||||||
limit: limit,
|
limit: limit,
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +39,7 @@ func (p *MaxFromLinter) Process(issues []result.Issue) ([]result.Issue, error) {
|
||||||
func (p MaxFromLinter) Finish() {
|
func (p MaxFromLinter) Finish() {
|
||||||
walkStringToIntMapSortedByValue(p.lc, func(linter string, count int) {
|
walkStringToIntMapSortedByValue(p.lc, func(linter string, count int) {
|
||||||
if count > p.limit {
|
if count > p.limit {
|
||||||
logrus.Infof("%d/%d issues from linter %s were hidden, use --max-issues-per-linter",
|
p.log.Infof("%d/%d issues from linter %s were hidden, use --max-issues-per-linter",
|
||||||
count-p.limit, count, linter)
|
count-p.limit, count, linter)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,10 +2,12 @@ package processors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMaxFromLinter(t *testing.T) {
|
func TestMaxFromLinter(t *testing.T) {
|
||||||
p := NewMaxFromLinter(1)
|
p := NewMaxFromLinter(1, logutils.NewStderrLog(""))
|
||||||
gosimple := newFromLinterIssue("gosimple")
|
gosimple := newFromLinterIssue("gosimple")
|
||||||
gofmt := newFromLinterIssue("gofmt")
|
gofmt := newFromLinterIssue("gofmt")
|
||||||
processAssertSame(t, p, gosimple) // ok
|
processAssertSame(t, p, gosimple) // ok
|
||||||
|
|
|
@ -3,8 +3,8 @@ package processors
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type textToCountMap map[string]int
|
type textToCountMap map[string]int
|
||||||
|
@ -12,14 +12,16 @@ type textToCountMap map[string]int
|
||||||
type MaxSameIssues struct {
|
type MaxSameIssues struct {
|
||||||
tc textToCountMap
|
tc textToCountMap
|
||||||
limit int
|
limit int
|
||||||
|
log logutils.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Processor = &MaxSameIssues{}
|
var _ Processor = &MaxSameIssues{}
|
||||||
|
|
||||||
func NewMaxSameIssues(limit int) *MaxSameIssues {
|
func NewMaxSameIssues(limit int, log logutils.Log) *MaxSameIssues {
|
||||||
return &MaxSameIssues{
|
return &MaxSameIssues{
|
||||||
tc: textToCountMap{},
|
tc: textToCountMap{},
|
||||||
limit: limit,
|
limit: limit,
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +43,7 @@ func (p *MaxSameIssues) Process(issues []result.Issue) ([]result.Issue, error) {
|
||||||
func (p MaxSameIssues) Finish() {
|
func (p MaxSameIssues) Finish() {
|
||||||
walkStringToIntMapSortedByValue(p.tc, func(text string, count int) {
|
walkStringToIntMapSortedByValue(p.tc, func(text string, count int) {
|
||||||
if count > p.limit {
|
if count > p.limit {
|
||||||
logrus.Infof("%d/%d issues with text %q were hidden, use --max-same-issues",
|
p.log.Infof("%d/%d issues with text %q were hidden, use --max-same-issues",
|
||||||
count-p.limit, count, text)
|
count-p.limit, count, text)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,11 +3,12 @@ package processors
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMaxSameIssues(t *testing.T) {
|
func TestMaxSameIssues(t *testing.T) {
|
||||||
p := NewMaxSameIssues(1)
|
p := NewMaxSameIssues(1, logutils.NewStderrLog(""))
|
||||||
i1 := result.Issue{
|
i1 := result.Issue{
|
||||||
Text: "1",
|
Text: "1",
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -21,7 +22,7 @@ func newNolintFileIssue(line int, fromLinter string) result.Issue {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNolint(t *testing.T) {
|
func TestNolint(t *testing.T) {
|
||||||
p := NewNolint(astcache.NewCache())
|
p := NewNolint(astcache.NewCache(logutils.NewStderrLog("")))
|
||||||
|
|
||||||
// test inline comments
|
// test inline comments
|
||||||
processAssertEmpty(t, p, newNolintFileIssue(3, "gofmt"))
|
processAssertEmpty(t, p, newNolintFileIssue(3, "gofmt"))
|
||||||
|
|
|
@ -7,22 +7,24 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Stopwatch struct {
|
type Stopwatch struct {
|
||||||
name string
|
name string
|
||||||
startedAt time.Time
|
startedAt time.Time
|
||||||
stages map[string]time.Duration
|
stages map[string]time.Duration
|
||||||
|
log logutils.Log
|
||||||
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStopwatch(name string) *Stopwatch {
|
func NewStopwatch(name string, log logutils.Log) *Stopwatch {
|
||||||
return &Stopwatch{
|
return &Stopwatch{
|
||||||
name: name,
|
name: name,
|
||||||
startedAt: time.Now(),
|
startedAt: time.Now(),
|
||||||
stages: map[string]time.Duration{},
|
stages: map[string]time.Duration{},
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,11 +55,11 @@ func (s *Stopwatch) sprintStages() string {
|
||||||
func (s *Stopwatch) Print() {
|
func (s *Stopwatch) Print() {
|
||||||
p := fmt.Sprintf("%s took %s", s.name, time.Since(s.startedAt))
|
p := fmt.Sprintf("%s took %s", s.name, time.Since(s.startedAt))
|
||||||
if len(s.stages) == 0 {
|
if len(s.stages) == 0 {
|
||||||
logrus.Info(p)
|
s.log.Infof("%s", p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Infof("%s with %s", p, s.sprintStages())
|
s.log.Infof("%s with %s", p, s.sprintStages())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stopwatch) PrintStages() {
|
func (s *Stopwatch) PrintStages() {
|
||||||
|
@ -65,7 +67,7 @@ func (s *Stopwatch) PrintStages() {
|
||||||
for _, s := range s.stages {
|
for _, s := range s.stages {
|
||||||
stagesDuration += s
|
stagesDuration += s
|
||||||
}
|
}
|
||||||
logrus.Infof("%s took %s with %s", s.name, stagesDuration, s.sprintStages())
|
s.log.Infof("%s took %s with %s", s.name, stagesDuration, s.sprintStages())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stopwatch) TrackStage(name string, f func()) {
|
func (s *Stopwatch) TrackStage(name string, f func()) {
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Track(from time.Time, format string, args ...interface{}) {
|
func Track(from time.Time, log logutils.Log, format string, args ...interface{}) {
|
||||||
logrus.Infof("%s took %s", fmt.Sprintf(format, args...), time.Since(from))
|
log.Infof("%s took %s", fmt.Sprintf(format, args...), time.Since(from))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -15,7 +16,6 @@ import (
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
gops "github.com/mitchellh/go-ps"
|
gops "github.com/mitchellh/go-ps"
|
||||||
"github.com/shirou/gopsutil/process"
|
"github.com/shirou/gopsutil/process"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func chdir(b *testing.B, dir string) {
|
func chdir(b *testing.B, dir string) {
|
||||||
|
@ -90,7 +90,7 @@ func printCommand(cmd string, args ...string) {
|
||||||
quotedArgs = append(quotedArgs, strconv.Quote(a))
|
quotedArgs = append(quotedArgs, strconv.Quote(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Warnf("%s %s", cmd, strings.Join(quotedArgs, " "))
|
log.Printf("%s %s", cmd, strings.Join(quotedArgs, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGometalinter(b *testing.B) {
|
func runGometalinter(b *testing.B) {
|
||||||
|
@ -215,7 +215,7 @@ func compare(b *testing.B, gometalinterRun, golangciLintRun func(*testing.B), re
|
||||||
if mode != "" {
|
if mode != "" {
|
||||||
mode = " " + mode
|
mode = " " + mode
|
||||||
}
|
}
|
||||||
logrus.Warnf("%s (%d kLoC): golangci-lint%s: time: %s, %.1f times faster; memory: %dMB, %.1f times less",
|
log.Printf("%s (%d kLoC): golangci-lint%s: time: %s, %.1f times faster; memory: %dMB, %.1f times less",
|
||||||
repoName, kLOC, mode,
|
repoName, kLOC, mode,
|
||||||
golangciLintRes.duration, gometalinterRes.duration.Seconds()/golangciLintRes.duration.Seconds(),
|
golangciLintRes.duration, gometalinterRes.duration.Seconds()/golangciLintRes.duration.Seconds(),
|
||||||
golangciLintRes.peakMemMB, float64(gometalinterRes.peakMemMB)/float64(golangciLintRes.peakMemMB),
|
golangciLintRes.peakMemMB, float64(gometalinterRes.peakMemMB)/float64(golangciLintRes.peakMemMB),
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -94,10 +95,10 @@ func TestGolintConsumesXTestFiles(t *testing.T) {
|
||||||
const expIssue = "if block ends with a return statement, so drop this else and outdent its block"
|
const expIssue = "if block ends with a return statement, so drop this else and outdent its block"
|
||||||
|
|
||||||
out, ec := runGolangciLint(t, "--no-config", "--disable-all", "-Egolint", dir)
|
out, ec := runGolangciLint(t, "--no-config", "--disable-all", "-Egolint", dir)
|
||||||
assert.Equal(t, 1, ec)
|
assert.Equal(t, exitcodes.IssuesFound, ec)
|
||||||
assert.Contains(t, out, expIssue)
|
assert.Contains(t, out, expIssue)
|
||||||
|
|
||||||
out, ec = runGolangciLint(t, "--no-config", "--disable-all", "-Egolint", filepath.Join(dir, "p_test.go"))
|
out, ec = runGolangciLint(t, "--no-config", "--disable-all", "-Egolint", filepath.Join(dir, "p_test.go"))
|
||||||
assert.Equal(t, 1, ec)
|
assert.Equal(t, exitcodes.IssuesFound, ec)
|
||||||
assert.Contains(t, out, expIssue)
|
assert.Contains(t, out, expIssue)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -28,7 +29,7 @@ func installBinary(t assert.TestingT) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkNoIssuesRun(t *testing.T, out string, exitCode int) {
|
func checkNoIssuesRun(t *testing.T, out string, exitCode int) {
|
||||||
assert.Equal(t, 0, exitCode)
|
assert.Equal(t, exitcodes.Success, exitCode)
|
||||||
assert.Equal(t, "Congrats! No issues were found.\n", out)
|
assert.Equal(t, "Congrats! No issues were found.\n", out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +48,20 @@ func TestSymlinkLoop(t *testing.T) {
|
||||||
checkNoIssuesRun(t, out, exitCode)
|
checkNoIssuesRun(t, out, exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunOnAbsPath(t *testing.T) {
|
||||||
|
absPath, err := filepath.Abs(filepath.Join(testdataDir, ".."))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
out, exitCode := runGolangciLint(t, "--no-config", "--fast", absPath)
|
||||||
|
checkNoIssuesRun(t, out, exitCode)
|
||||||
|
|
||||||
|
out, exitCode = runGolangciLint(t, "--no-config", absPath)
|
||||||
|
checkNoIssuesRun(t, out, exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeadline(t *testing.T) {
|
func TestDeadline(t *testing.T) {
|
||||||
out, exitCode := runGolangciLint(t, "--deadline=1ms", "../...")
|
out, exitCode := runGolangciLint(t, "--deadline=1ms", filepath.Join("..", "..."))
|
||||||
assert.Equal(t, 4, exitCode)
|
assert.Equal(t, exitcodes.Timeout, exitCode)
|
||||||
assert.Contains(t, out, "deadline exceeded: try increase it by passing --deadline option")
|
assert.Contains(t, out, "deadline exceeded: try increase it by passing --deadline option")
|
||||||
assert.NotContains(t, out, "Congrats! No issues were found.")
|
assert.NotContains(t, out, "Congrats! No issues were found.")
|
||||||
}
|
}
|
||||||
|
@ -79,7 +91,7 @@ func runGolangciLint(t *testing.T, args ...string) (string, int) {
|
||||||
|
|
||||||
func runGolangciLintWithYamlConfig(t *testing.T, cfg string, args ...string) string {
|
func runGolangciLintWithYamlConfig(t *testing.T, cfg string, args ...string) string {
|
||||||
out, ec := runGolangciLintWithYamlConfigWithCode(t, cfg, args...)
|
out, ec := runGolangciLintWithYamlConfigWithCode(t, cfg, args...)
|
||||||
assert.Equal(t, 0, ec)
|
assert.Equal(t, exitcodes.Success, ec)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
@ -107,12 +119,12 @@ func runGolangciLintWithYamlConfigWithCode(t *testing.T, cfg string, args ...str
|
||||||
|
|
||||||
func TestTestsAreLintedByDefault(t *testing.T) {
|
func TestTestsAreLintedByDefault(t *testing.T) {
|
||||||
out, exitCode := runGolangciLint(t, "./testdata/withtests")
|
out, exitCode := runGolangciLint(t, "./testdata/withtests")
|
||||||
assert.Equal(t, 0, exitCode, out)
|
assert.Equal(t, exitcodes.Success, exitCode, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigFileIsDetected(t *testing.T) {
|
func TestConfigFileIsDetected(t *testing.T) {
|
||||||
checkGotConfig := func(out string, exitCode int) {
|
checkGotConfig := func(out string, exitCode int) {
|
||||||
assert.Equal(t, 0, exitCode, out)
|
assert.Equal(t, exitcodes.Success, exitCode, out)
|
||||||
assert.Equal(t, "test\n", out) // test config contains InternalTest: true, it triggers such output
|
assert.Equal(t, "test\n", out) // test config contains InternalTest: true, it triggers such output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +220,7 @@ func TestEnableAllFastAndEnableCanCoexist(t *testing.T) {
|
||||||
checkNoIssuesRun(t, out, exitCode)
|
checkNoIssuesRun(t, out, exitCode)
|
||||||
|
|
||||||
_, exitCode = runGolangciLint(t, "--enable-all", "--enable=typecheck")
|
_, exitCode = runGolangciLint(t, "--enable-all", "--enable=typecheck")
|
||||||
assert.Equal(t, 3, exitCode)
|
assert.Equal(t, exitcodes.Failure, exitCode)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +339,7 @@ func TestEnabledLinters(t *testing.T) {
|
||||||
|
|
||||||
func TestEnabledPresetsAreNotDuplicated(t *testing.T) {
|
func TestEnabledPresetsAreNotDuplicated(t *testing.T) {
|
||||||
out, ec := runGolangciLint(t, "--no-config", "-v", "-p", "style,bugs")
|
out, ec := runGolangciLint(t, "--no-config", "-v", "-p", "style,bugs")
|
||||||
assert.Equal(t, 0, ec)
|
assert.Equal(t, exitcodes.Success, ec)
|
||||||
assert.Contains(t, out, "Active presets: [bugs style]")
|
assert.Contains(t, out, "Active presets: [bugs style]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +383,7 @@ func TestDisallowedOptionsInConfig(t *testing.T) {
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
// Run with disallowed option set only in config
|
// Run with disallowed option set only in config
|
||||||
_, ec := runGolangciLintWithYamlConfigWithCode(t, c.cfg)
|
_, ec := runGolangciLintWithYamlConfigWithCode(t, c.cfg)
|
||||||
assert.Equal(t, 1, ec)
|
assert.Equal(t, exitcodes.Failure, ec)
|
||||||
|
|
||||||
if c.option == "" {
|
if c.option == "" {
|
||||||
continue
|
continue
|
||||||
|
@ -381,10 +393,10 @@ func TestDisallowedOptionsInConfig(t *testing.T) {
|
||||||
|
|
||||||
// Run with disallowed option set only in command-line
|
// Run with disallowed option set only in command-line
|
||||||
_, ec = runGolangciLint(t, args...)
|
_, ec = runGolangciLint(t, args...)
|
||||||
assert.Equal(t, 0, ec)
|
assert.Equal(t, exitcodes.Success, ec)
|
||||||
|
|
||||||
// Run with disallowed option set both in command-line and in config
|
// Run with disallowed option set both in command-line and in config
|
||||||
_, ec = runGolangciLintWithYamlConfigWithCode(t, c.cfg, args...)
|
_, ec = runGolangciLintWithYamlConfigWithCode(t, c.cfg, args...)
|
||||||
assert.Equal(t, 1, ec)
|
assert.Equal(t, exitcodes.Failure, ec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue