From 30388862d25566de184411d61acdc23e49c54445 Mon Sep 17 00:00:00 2001 From: golangci Date: Sun, 6 May 2018 13:25:50 +0300 Subject: [PATCH] support enabling and disabling of linters and linters list command --- internal/commands/executor.go | 1 + internal/commands/linters.go | 43 +++++ internal/commands/run.go | 26 ++- pkg/config/config.go | 5 + pkg/golinters/enabled_linters.go | 160 ++++++++++++++++++ ...ported_linters_test.go => linters_test.go} | 0 pkg/golinters/supported_linters.go | 10 -- 7 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 internal/commands/linters.go create mode 100644 pkg/golinters/enabled_linters.go rename pkg/golinters/{supported_linters_test.go => linters_test.go} (100%) delete mode 100644 pkg/golinters/supported_linters.go diff --git a/internal/commands/executor.go b/internal/commands/executor.go index c20694b9..3a65addf 100644 --- a/internal/commands/executor.go +++ b/internal/commands/executor.go @@ -18,6 +18,7 @@ func NewExecutor() *Executor { e.initRoot() e.initRun() + e.initLinters() return e } diff --git a/internal/commands/linters.go b/internal/commands/linters.go new file mode 100644 index 00000000..99974a7a --- /dev/null +++ b/internal/commands/linters.go @@ -0,0 +1,43 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/fatih/color" + "github.com/golangci/golangci-lint/pkg/golinters" + "github.com/spf13/cobra" +) + +func (e *Executor) initLinters() { + var lintersCmd = &cobra.Command{ + Use: "linters", + Short: "List linters", + Run: e.executeLinters, + } + e.rootCmd.AddCommand(lintersCmd) +} + +func printLinterConfigs(lcs []golinters.LinterConfig) { + for _, lc := range lcs { + fmt.Printf("%s: %s\n", color.YellowString(lc.Linter.Name()), lc.Desc) + } +} + +func (e Executor) executeLinters(cmd *cobra.Command, args []string) { + var enabledLCs, disabledLCs []golinters.LinterConfig + for _, lc := range golinters.GetAllSupportedLinterConfigs() { + if lc.EnabledByDefault { + enabledLCs = append(enabledLCs, lc) + } else { + disabledLCs = append(disabledLCs, lc) + } + } + + color.Green("Enabled by default linters:\n") + printLinterConfigs(enabledLCs) + color.Red("\nDisabled by default linters:\n") + printLinterConfigs(disabledLCs) + + os.Exit(0) +} diff --git a/internal/commands/run.go b/internal/commands/run.go index d0e3aec0..11756382 100644 --- a/internal/commands/run.go +++ b/internal/commands/run.go @@ -17,7 +17,9 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters" "github.com/golangci/golangci-lint/pkg/result" "github.com/golangci/golangci-lint/pkg/result/processors" + "github.com/golangci/golangci-shared/pkg/analytics" "github.com/golangci/golangci-shared/pkg/executors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -36,7 +38,7 @@ func (e *Executor) initRun() { runCmd.Flags().IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code", 1, "Exit code when issues were found") - runCmd.Flags().BoolVar(&rc.Errcheck.CheckClose, "errcheck.check-close", false, " Errcheck: check missed error checks on .Close() calls") + 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()") @@ -45,12 +47,21 @@ func (e *Executor) initRun() { runCmd.Flags().Float64Var(&rc.Golint.MinConfidence, "golint.min-confidence", 0.8, "Golint: minimum confidence of a problem to print it") runCmd.Flags().BoolVar(&rc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code") + + runCmd.Flags().StringSliceVarP(&rc.EnabledLinters, "enable", "E", []string{}, "Enable specific linter") + runCmd.Flags().StringSliceVarP(&rc.DisabledLinters, "disable", "D", []string{}, "Disable specific linter") + runCmd.Flags().BoolVar(&rc.EnableAllLinters, "enable-all", false, "Enable all linters") + runCmd.Flags().BoolVar(&rc.DisableAllLinters, "disable-all", false, "Disable all linters") } func (e Executor) executeRun(cmd *cobra.Command, args []string) { f := func() (error, int) { runtime.GOMAXPROCS(e.cfg.Common.Concurrency) + if e.cfg.Common.IsVerbose { + analytics.SetLogLevel(logrus.InfoLevel) + } + if e.cfg.Common.CPUProfilePath != "" { f, err := os.Create(e.cfg.Common.CPUProfilePath) if err != nil { @@ -85,7 +96,12 @@ func (e Executor) executeRun(cmd *cobra.Command, args []string) { }, } - issues, err := runner.Run(ctx, golinters.GetSupportedLinters(), exec, e.cfg) + linters, err := golinters.GetEnabledLinters(ctx, &e.cfg.Run) + if err != nil { + return err, 1 + } + + issues, err := runner.Run(ctx, linters, exec, e.cfg) if err != nil { return err, 1 } @@ -115,7 +131,7 @@ func outputIssues(format string, issues []result.Issue) error { if format == config.OutFormatColoredLineNumber { outStr = color.GreenString(outStr) } - fmt.Fprint(os.Stdout, outStr) + fmt.Println(outStr) } for _, i := range issues { @@ -123,7 +139,7 @@ func outputIssues(format string, issues []result.Issue) error { if format == config.OutFormatColoredLineNumber { text = color.RedString(text) } - fmt.Fprintf(os.Stdout, "%s:%d: %s\n", i.File, i.LineNumber, text) + fmt.Printf("%s:%d: %s\n", i.File, i.LineNumber, text) } return nil } @@ -133,7 +149,7 @@ func outputIssues(format string, issues []result.Issue) error { if err != nil { return err } - fmt.Fprint(os.Stdout, string(outputJSON)) + fmt.Print(string(outputJSON)) return nil } diff --git a/pkg/config/config.go b/pkg/config/config.go index 78e65cc1..911289a4 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -39,6 +39,11 @@ type Run struct { Gofmt struct { Simplify bool } + + EnabledLinters []string + DisabledLinters []string + EnableAllLinters bool + DisableAllLinters bool } type Config struct { diff --git a/pkg/golinters/enabled_linters.go b/pkg/golinters/enabled_linters.go new file mode 100644 index 00000000..20519111 --- /dev/null +++ b/pkg/golinters/enabled_linters.go @@ -0,0 +1,160 @@ +package golinters + +import ( + "context" + "fmt" + "sync" + + "github.com/golangci/golangci-lint/pkg" + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-shared/pkg/analytics" +) + +type LinterConfig struct { + EnabledByDefault bool + Desc string + Linter pkg.Linter +} + +func enabledByDefault(linter pkg.Linter, desc string) LinterConfig { + return LinterConfig{ + EnabledByDefault: true, + Linter: linter, + Desc: desc, + } +} + +func disabledByDefault(linter pkg.Linter, desc string) LinterConfig { + return LinterConfig{ + EnabledByDefault: false, + Linter: linter, + Desc: desc, + } +} + +func GetAllSupportedLinterConfigs() []LinterConfig { + return []LinterConfig{ + enabledByDefault(govet{}, "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string"), + enabledByDefault(errcheck{}, "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases"), + enabledByDefault(golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes."), + disabledByDefault(gofmt{}, "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification."), + disabledByDefault(gofmt{useGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports."), + } +} + +func getAllSupportedLinters() []pkg.Linter { + var ret []pkg.Linter + for _, lc := range GetAllSupportedLinterConfigs() { + ret = append(ret, lc.Linter) + } + + return ret +} + +func getAllEnabledByDefaultLinters() []pkg.Linter { + var ret []pkg.Linter + for _, lc := range GetAllSupportedLinterConfigs() { + if lc.EnabledByDefault { + ret = append(ret, lc.Linter) + } + } + + return ret +} + +var supportedLintersByName map[string]pkg.Linter +var linterByNameMapOnce sync.Once + +func getLinterByName(name string) pkg.Linter { + linterByNameMapOnce.Do(func() { + supportedLintersByName = make(map[string]pkg.Linter) + for _, lc := range GetAllSupportedLinterConfigs() { + supportedLintersByName[lc.Linter.Name()] = lc.Linter + } + }) + + return supportedLintersByName[name] +} + +func lintersToMap(linters []pkg.Linter) map[string]pkg.Linter { + ret := map[string]pkg.Linter{} + for _, linter := range linters { + ret[linter.Name()] = linter + } + + return ret +} + +func validateEnabledDisabledLintersConfig(cfg *config.Run) error { + allNames := append([]string{}, cfg.EnabledLinters...) + allNames = append(allNames, cfg.DisabledLinters...) + for _, name := range allNames { + if getLinterByName(name) == nil { + return fmt.Errorf("no such linter %q", name) + } + } + + if cfg.EnableAllLinters && cfg.DisableAllLinters { + return fmt.Errorf("--enable-all and --disable-all options must not be combined") + } + + if cfg.DisableAllLinters { + if len(cfg.EnabledLinters) == 0 { + return fmt.Errorf("all linters were disabled, but no one linter was enabled: must enable at least one") + } + + if len(cfg.DisabledLinters) != 0 { + return fmt.Errorf("can't combine options --disable-all and --disable %s", cfg.DisabledLinters[0]) + } + } + + if cfg.EnableAllLinters && len(cfg.EnabledLinters) != 0 { + return fmt.Errorf("can't combine options --enable-all and --enable %s", cfg.EnabledLinters[0]) + } + + enabledLintersSet := map[string]bool{} + for _, name := range cfg.EnabledLinters { + enabledLintersSet[name] = true + } + + for _, name := range cfg.DisabledLinters { + if enabledLintersSet[name] { + return fmt.Errorf("linter %q can't be disabled and enabled at one moment", name) + } + } + + return nil +} + +func GetEnabledLinters(ctx context.Context, cfg *config.Run) ([]pkg.Linter, error) { + if err := validateEnabledDisabledLintersConfig(cfg); err != nil { + return nil, err + } + resultLintersSet := map[string]pkg.Linter{} + switch { + case cfg.EnableAllLinters: + resultLintersSet = lintersToMap(getAllSupportedLinters()) + case cfg.DisableAllLinters: + break + default: + resultLintersSet = lintersToMap(getAllEnabledByDefaultLinters()) + } + + for _, name := range cfg.EnabledLinters { + resultLintersSet[name] = getLinterByName(name) + } + + for _, name := range cfg.DisabledLinters { + delete(resultLintersSet, name) + } + + var resultLinters []pkg.Linter + var resultLinterNames []string + for name, linter := range resultLintersSet { + resultLinters = append(resultLinters, linter) + resultLinterNames = append(resultLinterNames, name) + } + analytics.Log(ctx).Infof("Enabled linters: %s", resultLinterNames) + + return resultLinters, nil +} diff --git a/pkg/golinters/supported_linters_test.go b/pkg/golinters/linters_test.go similarity index 100% rename from pkg/golinters/supported_linters_test.go rename to pkg/golinters/linters_test.go diff --git a/pkg/golinters/supported_linters.go b/pkg/golinters/supported_linters.go deleted file mode 100644 index 8f61422b..00000000 --- a/pkg/golinters/supported_linters.go +++ /dev/null @@ -1,10 +0,0 @@ -package golinters - -import "github.com/golangci/golangci-lint/pkg" - -const pathLineColMessage = `^(?P.*?\.go):(?P\d+):(?P\d+):\s*(?P.*)$` -const pathLineMessage = `^(?P.*?\.go):(?P\d+):\s*(?P.*)$` - -func GetSupportedLinters() []pkg.Linter { - return []pkg.Linter{govet{}, errcheck{}, golint{}, gofmt{}, gofmt{useGoimports: true}} -}