diff --git a/Gopkg.lock b/Gopkg.lock index be4be608..85ad1487 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -101,7 +101,7 @@ "lib/cfg", "lib/whitelist" ] - revision = "31bebf17d90d40b728c41fecdd5acf5fb8654fc7" + revision = "1a9ab8120ec96a014df3afab2d6c47f7e12d8928" [[projects]] branch = "master" @@ -140,7 +140,7 @@ "golangci", "internal/errcheck" ] - revision = "7a3d63cfc6fbd9e46f2e551f9b8d1e9c69bffab1" + revision = "d2c0791055c651c4bb6798ee1aacbd27ad377aa8" source = "github.com/golangci/errcheck" [[projects]] diff --git a/Makefile b/Makefile index 006a8ab8..871ec610 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ test: - go test -v ./... + go test -v -race ./... diff --git a/internal/commands/linters.go b/internal/commands/linters.go index 6d1bc42f..bd25623b 100644 --- a/internal/commands/linters.go +++ b/internal/commands/linters.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "os" + "strings" "github.com/fatih/color" "github.com/golangci/golangci-lint/pkg" @@ -20,7 +21,7 @@ func (e *Executor) initLinters() { func printLinterConfigs(lcs []pkg.LinterConfig) { for _, lc := range lcs { - fmt.Printf("%s: %s\n", color.YellowString(lc.Linter.Name()), lc.Desc) + fmt.Printf("%s: %s\n", color.YellowString(lc.Linter.Name()), lc.Linter.Desc()) } } @@ -39,5 +40,15 @@ func (e Executor) executeLinters(cmd *cobra.Command, args []string) { color.Red("\nDisabled by default linters:\n") printLinterConfigs(disabledLCs) + color.Green("\nLinters presets:") + for _, p := range pkg.AllPresets() { + linters := pkg.GetAllLintersForPreset(p) + linterNames := []string{} + for _, linter := range linters { + linterNames = append(linterNames, linter.Name()) + } + fmt.Printf("%s: %s\n", color.YellowString(p), strings.Join(linterNames, ", ")) + } + os.Exit(0) } diff --git a/internal/commands/run.go b/internal/commands/run.go index da5b6a8f..b1ee9e0e 100644 --- a/internal/commands/run.go +++ b/internal/commands/run.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "go/build" + "go/token" "log" "strings" "time" @@ -38,16 +39,16 @@ func (e *Executor) initRun() { fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))) runCmd.Flags().BoolVar(&rc.PrintIssuedLine, "print-issued-lines", true, "Print lines of code with issue") runCmd.Flags().BoolVar(&rc.PrintLinterName, "print-linter-name", true, "Print linter name in issue line") + runCmd.Flags().BoolVar(&rc.PrintWelcomeMessage, "print-welcome", true, "Print welcome message") runCmd.Flags().IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code", 1, "Exit code when issues were found") runCmd.Flags().StringSliceVar(&rc.BuildTags, "build-tags", []string{}, "Build tags (not all linters support them)") - runCmd.Flags().BoolVar(&rc.Errcheck.CheckClose, "errcheck.check-close", false, "Errcheck: check missed error checks on .Close() calls") runCmd.Flags().BoolVar(&rc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions", false, "Errcheck: check for ignored type assertion results") runCmd.Flags().BoolVar(&rc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false, "Errcheck: check for errors assigned to blank identifier: _ = errFunc()") - runCmd.Flags().BoolVar(&rc.Govet.CheckShadowing, "govet.check-shadowing", true, "Govet: check for shadowed variables") + runCmd.Flags().BoolVar(&rc.Govet.CheckShadowing, "govet.check-shadowing", false, "Govet: check for shadowed variables") runCmd.Flags().Float64Var(&rc.Golint.MinConfidence, "golint.min-confidence", 0.8, "Golint: minimum confidence of a problem to print it") @@ -90,6 +91,9 @@ func (e *Executor) initRun() { runCmd.Flags().StringVar(&rc.DiffFromRevision, "new-from-rev", "", "Show only new issues created after git revision `REV`") runCmd.Flags().StringVar(&rc.DiffPatchFilePath, "new-from-patch", "", "Show only new issues created in git patch with file path `PATH`") runCmd.Flags().BoolVar(&rc.AnalyzeTests, "tests", false, "Analyze tests (*_test.go)") + + runCmd.Flags().StringSliceVarP(&rc.Presets, "presets", "p", []string{}, + fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see them. This option implies option --disable-all", strings.Join(pkg.AllPresets(), "|"))) } func isFullImportNeeded(linters []pkg.Linter) bool { @@ -208,10 +212,15 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (chan result. if len(excludePatterns) != 0 { excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(excludePatterns, "|")) } + fset := token.NewFileSet() + if lintCtx.Program != nil { + fset = lintCtx.Program.Fset + } runner := pkg.SimpleRunner{ Processors: []processors.Processor{ processors.NewExclude(excludeTotalPattern), - processors.NewNolint(lintCtx.Program.Fset), + processors.NewCgo(), + processors.NewNolint(fset), processors.NewUniqByLine(), processors.NewDiff(e.cfg.Run.Diff, e.cfg.Run.DiffFromRevision, e.cfg.Run.DiffPatchFilePath), processors.NewMaxPerFileFromLinter(), @@ -231,6 +240,10 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) { logrus.Infof("Run took %s", time.Since(startedAt)) }(time.Now()) + if e.cfg.Run.PrintWelcomeMessage { + fmt.Println("Run this tool in cloud on every github pull request in https://golangci.com for free (public repos)") + } + f := func() error { issues, err := e.runAnalysis(ctx, args) if err != nil { diff --git a/pkg/config/config.go b/pkg/config/config.go index f4e87e8e..d1b963e3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -15,6 +15,7 @@ const ( var OutFormats = []string{OutFormatColoredLineNumber, OutFormatLineNumber, OutFormatJSON} var DefaultExcludePatterns = []string{ + "Error return value of `(os\\.Std(out|err)\\.Write|.*\\.Close)` is not checked", "should have comment", "comment on exported method", "G104", // disable what errcheck does: it reports on Close etc @@ -33,14 +34,14 @@ type Run struct { // nolint:maligned BuildTags []string - OutFormat string - PrintIssuedLine bool - PrintLinterName bool + OutFormat string + PrintIssuedLine bool + PrintLinterName bool + PrintWelcomeMessage bool ExitCodeIfIssuesFound int Errcheck struct { - CheckClose bool CheckTypeAssertions bool CheckAssignToBlank bool } @@ -83,6 +84,8 @@ type Run struct { // nolint:maligned EnableAllLinters bool DisableAllLinters bool + Presets []string + ExcludePatterns []string UseDefaultExcludes bool diff --git a/pkg/enabled_linters.go b/pkg/enabled_linters.go index e6f3383a..39441ad6 100644 --- a/pkg/enabled_linters.go +++ b/pkg/enabled_linters.go @@ -3,6 +3,7 @@ package pkg import ( "context" "fmt" + "strings" "sync" "github.com/golangci/golangci-lint/pkg/config" @@ -10,12 +11,61 @@ import ( "github.com/sirupsen/logrus" ) +const ( + PresetFormatting = "format" + PresetComplexity = "complexity" + PresetStyle = "style" + PresetBugs = "bugs" + PresetUnused = "unused" + PresetPerformance = "performance" +) + +func AllPresets() []string { + return []string{PresetBugs, PresetUnused, PresetFormatting, PresetStyle, PresetComplexity, PresetPerformance} +} + +func allPresetsSet() map[string]bool { + ret := map[string]bool{} + for _, p := range AllPresets() { + ret[p] = true + } + return ret +} + type LinterConfig struct { - Desc string Linter Linter EnabledByDefault bool DoesFullImport bool NeedsSSARepr bool + InPresets []string +} + +func (lc LinterConfig) WithFullImport() LinterConfig { + lc.DoesFullImport = true + return lc +} + +func (lc LinterConfig) WithSSA() LinterConfig { + lc.DoesFullImport = true + lc.NeedsSSARepr = true + return lc +} + +func (lc LinterConfig) WithPresets(presets ...string) LinterConfig { + lc.InPresets = presets + return lc +} + +func (lc LinterConfig) WithDisabledByDefault() LinterConfig { + lc.EnabledByDefault = false + return lc +} + +func newLinterConfig(linter Linter) LinterConfig { + return LinterConfig{ + Linter: linter, + EnabledByDefault: true, + } } var nameToLC map[string]LinterConfig @@ -37,46 +87,26 @@ func GetLinterConfig(name string) *LinterConfig { return &lc } -func enabledByDefault(linter Linter, desc string, doesFullImport, needsSSARepr bool) LinterConfig { - return LinterConfig{ - EnabledByDefault: true, - Linter: linter, - Desc: desc, - DoesFullImport: doesFullImport, - NeedsSSARepr: needsSSARepr, - } -} - -func disabledByDefault(linter Linter, desc string, doesFullImport, needsSSARepr bool) LinterConfig { - return LinterConfig{ - EnabledByDefault: false, - Linter: linter, - Desc: desc, - DoesFullImport: doesFullImport, - NeedsSSARepr: needsSSARepr, - } -} - func GetAllSupportedLinterConfigs() []LinterConfig { return []LinterConfig{ - enabledByDefault(golinters.Govet{}, "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string", false, false), - enabledByDefault(golinters.Errcheck{}, "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases", true, false), - enabledByDefault(golinters.Golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes", false, false), - enabledByDefault(golinters.Megacheck{}, "Megacheck: 3 sub-linters in one: staticcheck, gosimple and unused", true, true), - enabledByDefault(golinters.Gas{}, "Inspects source code for security problems", true, false), - enabledByDefault(golinters.Structcheck{}, "Finds unused struct fields", true, false), - enabledByDefault(golinters.Varcheck{}, "Finds unused global variables and constants", true, false), - enabledByDefault(golinters.Interfacer{}, "Linter that suggests narrower interface types", true, true), - enabledByDefault(golinters.Unconvert{}, "Remove unnecessary type conversions", true, false), - enabledByDefault(golinters.Ineffassign{}, "Detects when assignments to existing variables are not used", false, false), - enabledByDefault(golinters.Dupl{}, "Tool for code clone detection", false, false), - enabledByDefault(golinters.Goconst{}, "Finds repeated strings that could be replaced by a constant", false, false), - enabledByDefault(golinters.Deadcode{}, "Finds unused code", true, false), - enabledByDefault(golinters.Gocyclo{}, "Computes and checks the cyclomatic complexity of functions", false, false), + newLinterConfig(golinters.Govet{}).WithPresets(PresetBugs), + newLinterConfig(golinters.Errcheck{}).WithFullImport().WithPresets(PresetBugs), + newLinterConfig(golinters.Golint{}).WithDisabledByDefault().WithPresets(PresetStyle), + newLinterConfig(golinters.Megacheck{}).WithSSA().WithPresets(PresetBugs, PresetUnused, PresetStyle), + newLinterConfig(golinters.Gas{}).WithFullImport().WithPresets(PresetBugs), + newLinterConfig(golinters.Structcheck{}).WithFullImport().WithPresets(PresetUnused), + newLinterConfig(golinters.Varcheck{}).WithFullImport().WithPresets(PresetUnused), + newLinterConfig(golinters.Interfacer{}).WithDisabledByDefault().WithSSA().WithPresets(PresetStyle), + newLinterConfig(golinters.Unconvert{}).WithDisabledByDefault().WithFullImport().WithPresets(PresetStyle), + newLinterConfig(golinters.Ineffassign{}).WithPresets(PresetUnused), + newLinterConfig(golinters.Dupl{}).WithDisabledByDefault().WithPresets(PresetStyle), + newLinterConfig(golinters.Goconst{}).WithDisabledByDefault().WithPresets(PresetStyle), + newLinterConfig(golinters.Deadcode{}).WithFullImport().WithPresets(PresetUnused), + newLinterConfig(golinters.Gocyclo{}).WithDisabledByDefault().WithPresets(PresetComplexity), - disabledByDefault(golinters.Gofmt{}, "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification", false, false), - disabledByDefault(golinters.Gofmt{UseGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports", false, false), - disabledByDefault(golinters.Maligned{}, "Tool to detect Go structs that would take less memory if their fields were sorted", true, false), + newLinterConfig(golinters.Gofmt{}).WithDisabledByDefault().WithPresets(PresetFormatting), + newLinterConfig(golinters.Gofmt{UseGoimports: true}).WithDisabledByDefault().WithPresets(PresetFormatting), + newLinterConfig(golinters.Maligned{}).WithFullImport().WithDisabledByDefault().WithPresets(PresetPerformance), } } @@ -132,6 +162,17 @@ func validateEnabledDisabledLintersConfig(cfg *config.Run) error { } } + allPresets := allPresetsSet() + for _, p := range cfg.Presets { + if !allPresets[p] { + return fmt.Errorf("no such preset %q: only next presets exist: (%s)", p, strings.Join(AllPresets(), "|")) + } + } + + if len(cfg.Presets) != 0 && cfg.EnableAllLinters { + return fmt.Errorf("--presets is incompatible with --enable-all") + } + if cfg.EnableAllLinters && cfg.DisableAllLinters { return fmt.Errorf("--enable-all and --disable-all options must not be combined") } @@ -164,12 +205,29 @@ func validateEnabledDisabledLintersConfig(cfg *config.Run) error { return nil } +func GetAllLintersForPreset(p string) []Linter { + ret := []Linter{} + for _, lc := range GetAllSupportedLinterConfigs() { + for _, ip := range lc.InPresets { + if p == ip { + ret = append(ret, lc.Linter) + break + } + } + } + + return ret +} + func GetEnabledLinters(ctx context.Context, cfg *config.Run) ([]Linter, error) { if err := validateEnabledDisabledLintersConfig(cfg); err != nil { return nil, err } + resultLintersSet := map[string]Linter{} switch { + case len(cfg.Presets) != 0: + break // imply --disable-all case cfg.EnableAllLinters: resultLintersSet = lintersToMap(getAllSupportedLinters()) case cfg.DisableAllLinters: @@ -182,6 +240,32 @@ func GetEnabledLinters(ctx context.Context, cfg *config.Run) ([]Linter, error) { resultLintersSet[name] = getLinterByName(name) } + // XXX: hacks because of sub-linters in megacheck + megacheckWasEnabledByUser := resultLintersSet["megacheck"] != nil + if !megacheckWasEnabledByUser { + cfg.Megacheck.EnableGosimple = false + cfg.Megacheck.EnableStaticcheck = false + cfg.Megacheck.EnableUnused = false + } + + for _, p := range cfg.Presets { + for _, linter := range GetAllLintersForPreset(p) { + resultLintersSet[linter.Name()] = linter + } + + if !megacheckWasEnabledByUser { + if p == PresetBugs { + cfg.Megacheck.EnableStaticcheck = true + } + if p == PresetStyle { + cfg.Megacheck.EnableGosimple = true + } + if p == PresetUnused { + cfg.Megacheck.EnableUnused = true + } + } + } + for _, name := range cfg.DisabledLinters { delete(resultLintersSet, name) } @@ -192,7 +276,10 @@ func GetEnabledLinters(ctx context.Context, cfg *config.Run) ([]Linter, error) { resultLinters = append(resultLinters, linter) resultLinterNames = append(resultLinterNames, name) } - logrus.Infof("Enabled linters: %s", resultLinterNames) + logrus.Infof("Active linters: %s", resultLinterNames) + if len(cfg.Presets) != 0 { + logrus.Infof("Active presets: %s", cfg.Presets) + } return resultLinters, nil } diff --git a/pkg/enabled_linters_test.go b/pkg/enabled_linters_test.go index 27edf6d9..fd8c09c3 100644 --- a/pkg/enabled_linters_test.go +++ b/pkg/enabled_linters_test.go @@ -56,6 +56,8 @@ func testOneSource(t *testing.T, sourcePath string) { "--print-issued-lines=false", "--print-linter-name=false", "--out-format=line-number", + "--print-welcome=false", + "--govet.check-shadowing=true", sourcePath) runGoErrchk(cmd, t) } diff --git a/pkg/golinters/deadcode.go b/pkg/golinters/deadcode.go index aa54184d..babb7416 100644 --- a/pkg/golinters/deadcode.go +++ b/pkg/golinters/deadcode.go @@ -14,6 +14,10 @@ func (Deadcode) Name() string { return "deadcode" } +func (Deadcode) Desc() string { + return "Finds unused code" +} + func (d Deadcode) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { issues, err := deadcodeAPI.Run(lintCtx.Program) if err != nil { diff --git a/pkg/golinters/dupl.go b/pkg/golinters/dupl.go index a0e8c7bf..aceae8dc 100644 --- a/pkg/golinters/dupl.go +++ b/pkg/golinters/dupl.go @@ -15,6 +15,10 @@ func (Dupl) Name() string { return "dupl" } +func (Dupl) Desc() string { + return "Tool for code clone detection" +} + func (d Dupl) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { issues, err := duplAPI.Run(lintCtx.Paths.Files, lintCtx.RunCfg().Dupl.Threshold) if err != nil { diff --git a/pkg/golinters/errcheck.go b/pkg/golinters/errcheck.go index 86239789..40e06774 100644 --- a/pkg/golinters/errcheck.go +++ b/pkg/golinters/errcheck.go @@ -3,7 +3,6 @@ package golinters import ( "context" "fmt" - "strings" "github.com/golangci/golangci-lint/pkg/result" errcheckAPI "github.com/kisielk/errcheck/golangci" @@ -15,6 +14,10 @@ func (Errcheck) Name() string { return "errcheck" } +func (Errcheck) Desc() string { + return "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases" +} + func (e Errcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { errCfg := &lintCtx.RunCfg().Errcheck issues, err := errcheckAPI.Run(lintCtx.Program, errCfg.CheckAssignToBlank, errCfg.CheckTypeAssertions) @@ -24,10 +27,6 @@ func (e Errcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, er var res []result.Issue for _, i := range issues { - if !errCfg.CheckClose && strings.HasSuffix(i.FuncName, ".Close") { - continue - } - var text string if i.FuncName != "" { text = fmt.Sprintf("Error return value of %s is not checked", formatCode(i.FuncName, lintCtx.RunCfg())) diff --git a/pkg/golinters/gas.go b/pkg/golinters/gas.go index a932feab..caff9280 100644 --- a/pkg/golinters/gas.go +++ b/pkg/golinters/gas.go @@ -19,6 +19,10 @@ func (Gas) Name() string { return "gas" } +func (Gas) Desc() string { + return "Inspects source code for security problems" +} + func (lint Gas) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { gasConfig := gas.NewConfig() enabledRules := rules.Generate() diff --git a/pkg/golinters/goconst.go b/pkg/golinters/goconst.go index 17ed46b0..756e85df 100644 --- a/pkg/golinters/goconst.go +++ b/pkg/golinters/goconst.go @@ -14,17 +14,27 @@ func (Goconst) Name() string { return "goconst" } +func (Goconst) Desc() string { + return "Finds repeated strings that could be replaced by a constant" +} + func (lint Goconst) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { - issues, err := goconstAPI.Run(lintCtx.Paths.Files, true, - lintCtx.RunCfg().Goconst.MinStringLen, - lintCtx.RunCfg().Goconst.MinOccurrencesCount, - ) - if err != nil { - return nil, err + var goconstIssues []goconstAPI.Issue + // TODO: make it cross-package: pass package names inside goconst + for _, files := range lintCtx.Paths.FilesGrouppedByDirs() { + issues, err := goconstAPI.Run(files, true, + lintCtx.RunCfg().Goconst.MinStringLen, + lintCtx.RunCfg().Goconst.MinOccurrencesCount, + ) + if err != nil { + return nil, err + } + + goconstIssues = append(goconstIssues, issues...) } var res []result.Issue - for _, i := range issues { + for _, i := range goconstIssues { textBegin := fmt.Sprintf("string %s has %d occurrences", formatCode(i.Str, lintCtx.RunCfg()), i.OccurencesCount) var textEnd string if i.MatchingConst == "" { diff --git a/pkg/golinters/gocyclo.go b/pkg/golinters/gocyclo.go index 505dc54d..78d10c5c 100644 --- a/pkg/golinters/gocyclo.go +++ b/pkg/golinters/gocyclo.go @@ -14,6 +14,10 @@ func (Gocyclo) Name() string { return "gocyclo" } +func (Gocyclo) Desc() string { + return "Computes and checks the cyclomatic complexity of functions" +} + func (g Gocyclo) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { stats := gocycloAPI.Run(lintCtx.Paths.MixedPaths()) diff --git a/pkg/golinters/gofmt.go b/pkg/golinters/gofmt.go index a52a4df3..d726ce7e 100644 --- a/pkg/golinters/gofmt.go +++ b/pkg/golinters/gofmt.go @@ -25,6 +25,14 @@ func (g Gofmt) Name() string { return "gofmt" } +func (g Gofmt) Desc() string { + if g.UseGoimports { + return "Goimports does everything that gofmt does. Additionally it checks unused imports" + } + + return "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification" +} + func getFirstDeletedAndAddedLineNumberInHunk(h *diff.Hunk) (int, int, error) { lines := bytes.Split(h.Body, []byte{'\n'}) lineNumber := int(h.OrigStartLine - 1) diff --git a/pkg/golinters/golint.go b/pkg/golinters/golint.go index f79daf25..fad35153 100644 --- a/pkg/golinters/golint.go +++ b/pkg/golinters/golint.go @@ -15,6 +15,10 @@ func (Golint) Name() string { return "golint" } +func (Golint) Desc() string { + return "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes" +} + func (g Golint) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { var issues []result.Issue for _, pkgFiles := range lintCtx.Paths.FilesGrouppedByDirs() { diff --git a/pkg/golinters/govet.go b/pkg/golinters/govet.go index 431a071f..bcefa1e0 100644 --- a/pkg/golinters/govet.go +++ b/pkg/golinters/govet.go @@ -13,14 +13,23 @@ func (Govet) Name() string { return "govet" } +func (Govet) Desc() string { + return "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string" +} + func (g Govet) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { - issues, err := govetAPI.Run(lintCtx.Paths.MixedPaths(), lintCtx.RunCfg().BuildTags, lintCtx.RunCfg().Govet.CheckShadowing) - if err != nil { - return nil, err + // TODO: check .S asm files: govet can do it if pass dirs + var govetIssues []govetAPI.Issue + for _, files := range lintCtx.Paths.FilesGrouppedByDirs() { + issues, err := govetAPI.Run(files, lintCtx.RunCfg().Govet.CheckShadowing) + if err != nil { + return nil, err + } + govetIssues = append(govetIssues, issues...) } var res []result.Issue - for _, i := range issues { + for _, i := range govetIssues { res = append(res, result.Issue{ Pos: i.Pos, Text: i.Message, diff --git a/pkg/golinters/ineffassign.go b/pkg/golinters/ineffassign.go index be5ca72e..e63bd5f2 100644 --- a/pkg/golinters/ineffassign.go +++ b/pkg/golinters/ineffassign.go @@ -14,6 +14,10 @@ func (Ineffassign) Name() string { return "ineffassign" } +func (Ineffassign) Desc() string { + return "Detects when assignments to existing variables are not used" +} + func (lint Ineffassign) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { issues := ineffassignAPI.Run(lintCtx.Paths.Files) diff --git a/pkg/golinters/interfacer.go b/pkg/golinters/interfacer.go index c0512c41..8f9cfdd8 100644 --- a/pkg/golinters/interfacer.go +++ b/pkg/golinters/interfacer.go @@ -14,6 +14,10 @@ func (Interfacer) Name() string { return "interfacer" } +func (Interfacer) Desc() string { + return "Linter that suggests narrower interface types" +} + func (lint Interfacer) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { c := new(check.Checker) c.Program(lintCtx.Program) diff --git a/pkg/golinters/maligned.go b/pkg/golinters/maligned.go index 5a773191..87de7de6 100644 --- a/pkg/golinters/maligned.go +++ b/pkg/golinters/maligned.go @@ -14,6 +14,10 @@ func (Maligned) Name() string { return "maligned" } +func (Maligned) Desc() string { + return "Tool to detect Go structs that would take less memory if their fields were sorted" +} + func (m Maligned) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { issues := malignedAPI.Run(lintCtx.Program) diff --git a/pkg/golinters/megacheck.go b/pkg/golinters/megacheck.go index 279aa0a8..fc5fd021 100644 --- a/pkg/golinters/megacheck.go +++ b/pkg/golinters/megacheck.go @@ -13,6 +13,10 @@ func (Megacheck) Name() string { return "megacheck" } +func (Megacheck) Desc() string { + return "Megacheck: 3 sub-linters in one: staticcheck, gosimple and unused" +} + func (m Megacheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { c := lintCtx.RunCfg().Megacheck issues := megacheckAPI.Run(lintCtx.Program, lintCtx.LoaderConfig, lintCtx.SSAProgram, c.EnableStaticcheck, c.EnableGosimple, c.EnableUnused) diff --git a/pkg/golinters/structcheck.go b/pkg/golinters/structcheck.go index 0966636e..fde7b567 100644 --- a/pkg/golinters/structcheck.go +++ b/pkg/golinters/structcheck.go @@ -14,6 +14,10 @@ func (Structcheck) Name() string { return "structcheck" } +func (Structcheck) Desc() string { + return "Finds unused struct fields" +} + func (s Structcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { issues := structcheckAPI.Run(lintCtx.Program, lintCtx.RunCfg().Structcheck.CheckExportedFields) diff --git a/pkg/golinters/unconvert.go b/pkg/golinters/unconvert.go index 9c2df987..5a40f319 100644 --- a/pkg/golinters/unconvert.go +++ b/pkg/golinters/unconvert.go @@ -13,6 +13,10 @@ func (Unconvert) Name() string { return "unconvert" } +func (Unconvert) Desc() string { + return "Remove unnecessary type conversions" +} + func (lint Unconvert) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { positions := unconvertAPI.Run(lintCtx.Program) var res []result.Issue diff --git a/pkg/golinters/varcheck.go b/pkg/golinters/varcheck.go index 16b43b94..64beda1d 100644 --- a/pkg/golinters/varcheck.go +++ b/pkg/golinters/varcheck.go @@ -14,6 +14,10 @@ func (Varcheck) Name() string { return "varcheck" } +func (Varcheck) Desc() string { + return "Finds unused global variables and constants" +} + func (v Varcheck) Run(ctx context.Context, lintCtx *Context) ([]result.Issue, error) { issues := varcheckAPI.Run(lintCtx.Program, lintCtx.RunCfg().Varcheck.CheckExportedFields) diff --git a/pkg/linter.go b/pkg/linter.go index 8aaeada0..7405ef2f 100644 --- a/pkg/linter.go +++ b/pkg/linter.go @@ -10,4 +10,5 @@ import ( type Linter interface { Run(ctx context.Context, lintCtx *golinters.Context) ([]result.Issue, error) Name() string + Desc() string } diff --git a/pkg/result/processors/cgo.go b/pkg/result/processors/cgo.go new file mode 100644 index 00000000..14189691 --- /dev/null +++ b/pkg/result/processors/cgo.go @@ -0,0 +1,30 @@ +package processors + +import ( + "strings" + + "github.com/golangci/golangci-lint/pkg/result" +) + +type Cgo struct { +} + +var _ Processor = Cgo{} + +func NewCgo() *Cgo { + return &Cgo{} +} + +func (p Cgo) Name() string { + return "cgo" +} + +func (p Cgo) Process(issues []result.Issue) ([]result.Issue, error) { + return filterIssues(issues, func(i *result.Issue) bool { + // some linters (.e.g gas, deadcode) return incorrect filepaths for cgo issues, + // it breaks next processing, so skip them + return !strings.HasSuffix(i.FilePath(), "/C") + }), nil +} + +func (Cgo) Finish() {} diff --git a/pkg/result/processors/nolint.go b/pkg/result/processors/nolint.go index a14b9d12..170bd092 100644 --- a/pkg/result/processors/nolint.go +++ b/pkg/result/processors/nolint.go @@ -1,6 +1,7 @@ package processors import ( + "fmt" "go/ast" "go/parser" "go/token" @@ -43,7 +44,7 @@ func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) { if comments == nil { file, err := parser.ParseFile(p.fset, i.FilePath(), nil, parser.ParseComments) if err != nil { - return true, err + return false, fmt.Errorf("can't parse file %s", i.FilePath()) } comments = extractFileComments(p.fset, file.Comments...) diff --git a/pkg/result/processors/utils.go b/pkg/result/processors/utils.go index 29b442cd..7c636a85 100644 --- a/pkg/result/processors/utils.go +++ b/pkg/result/processors/utils.go @@ -1,6 +1,10 @@ package processors -import "github.com/golangci/golangci-lint/pkg/result" +import ( + "fmt" + + "github.com/golangci/golangci-lint/pkg/result" +) func filterIssues(issues []result.Issue, filter func(i *result.Issue) bool) []result.Issue { retIssues := make([]result.Issue, 0, len(issues)) @@ -18,8 +22,9 @@ func filterIssuesErr(issues []result.Issue, filter func(i *result.Issue) (bool, for _, i := range issues { ok, err := filter(&i) if err != nil { - return nil, err + return nil, fmt.Errorf("can't filter issue %#v: %s", i, err) } + if ok { retIssues = append(retIssues, i) } diff --git a/pkg/runner.go b/pkg/runner.go index 8f05a0e7..89638e37 100644 --- a/pkg/runner.go +++ b/pkg/runner.go @@ -34,11 +34,7 @@ func (r *SimpleRunner) runLinter(ctx context.Context, linter Linter, lintCtx *go startedAt := time.Now() res, err = linter.Run(ctx, lintCtx) - if err == nil && len(res) != 0 { - res = r.processIssues(ctx, res) - } - - logrus.Infof("worker #%d: linter %s took %s and found %d issues", i, linter.Name(), + logrus.Infof("worker #%d: linter %s took %s and found %d issues (before processing them)", i, linter.Name(), time.Since(startedAt), len(res)) return } @@ -120,6 +116,11 @@ func (r SimpleRunner) runGo(ctx context.Context, linters []Linter, lintCtx *goli } finishedN++ + + if len(res.issues) != 0 { + res.issues = r.processIssues(ctx, res.issues) + } + for _, i := range res.issues { retIssues <- i } diff --git a/pkg/testdata/with_issues/errcheck.go b/pkg/testdata/with_issues/errcheck.go index 1beb6ec9..b06ae410 100644 --- a/pkg/testdata/with_issues/errcheck.go +++ b/pkg/testdata/with_issues/errcheck.go @@ -32,3 +32,8 @@ func IgnoreCloseInDeferMissingErrHandling() { panic(resp) } + +func IgnoreStdxWrite() { + os.Stdout.Write([]byte{}) + os.Stderr.Write([]byte{}) +} diff --git a/vendor/github.com/golangci/govet/golangci.go b/vendor/github.com/golangci/govet/golangci.go index 94ef1c1b..c602876b 100644 --- a/vendor/github.com/golangci/govet/golangci.go +++ b/vendor/github.com/golangci/govet/golangci.go @@ -1,9 +1,7 @@ package govet import ( - "fmt" "go/token" - "os" "strings" ) @@ -14,7 +12,7 @@ type Issue struct { var foundIssues []Issue -func Run(paths, buildTags []string, checkShadowing bool) ([]Issue, error) { +func Run(files []string, checkShadowing bool) ([]Issue, error) { foundIssues = nil if checkShadowing { @@ -26,37 +24,16 @@ func Run(paths, buildTags []string, checkShadowing bool) ([]Issue, error) { } } - tagList = buildTags - initPrintFlags() initUnusedFlags() - for _, name := range paths { - // Is it a directory? - fi, err := os.Stat(name) - if err != nil { - warnf("error walking tree: %s", err) - continue - } - if fi.IsDir() { - dirsRun = true - } else { - filesRun = true - if !strings.HasSuffix(name, "_test.go") { - includesNonTest = true - } + filesRun = true + for _, name := range files { + if !strings.HasSuffix(name, "_test.go") { + includesNonTest = true } } - if dirsRun && filesRun { - return nil, fmt.Errorf("can't mix dirs and files") - } - if dirsRun { - for _, name := range paths { - doPackageDir(name) - } - return foundIssues, nil - } - if doPackage(paths, nil) == nil { + if doPackage(files, nil) == nil { return nil, nil } diff --git a/vendor/github.com/kisielk/errcheck/internal/errcheck/errcheck.go b/vendor/github.com/kisielk/errcheck/internal/errcheck/errcheck.go index 36e0ac5b..fd033b1d 100644 --- a/vendor/github.com/kisielk/errcheck/internal/errcheck/errcheck.go +++ b/vendor/github.com/kisielk/errcheck/internal/errcheck/errcheck.go @@ -226,6 +226,18 @@ type visitor struct { errors []UncheckedError } +func getSelName(sel *ast.SelectorExpr) string { + if ident, ok := sel.X.(*ast.Ident); ok { + return fmt.Sprintf("%s.%s", ident.Name, sel.Sel.Name) + } + + if s, ok := sel.X.(*ast.SelectorExpr); ok { + return fmt.Sprintf("%s.%s", getSelName(s), sel.Sel.Name) + } + + return "" +} + func (v *visitor) fullName(call *ast.CallExpr) (string, bool) { if ident, ok := call.Fun.(*ast.Ident); ok { return ident.Name, true @@ -235,6 +247,12 @@ func (v *visitor) fullName(call *ast.CallExpr) (string, bool) { if !ok { return "", false } + + name := getSelName(sel) + if name != "" { + return name, true + } + fn, ok := v.pkg.ObjectOf(sel.Sel).(*types.Func) if !ok { // Shouldn't happen, but be paranoid