package pkg import ( "context" "fmt" "sync" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters" "github.com/golangci/golangci-shared/pkg/analytics" ) type LinterConfig struct { Desc string Linter Linter EnabledByDefault bool DoesFullImport bool NeedsSSARepr bool } var nameToLC map[string]LinterConfig var nameToLCOnce sync.Once func GetLinterConfig(name string) *LinterConfig { nameToLCOnce.Do(func() { nameToLC = make(map[string]LinterConfig) for _, lc := range GetAllSupportedLinterConfigs() { nameToLC[lc.Linter.Name()] = lc } }) lc, ok := nameToLC[name] if !ok { return nil } 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.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), 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), } } func getAllSupportedLinters() []Linter { var ret []Linter for _, lc := range GetAllSupportedLinterConfigs() { ret = append(ret, lc.Linter) } return ret } func getAllEnabledByDefaultLinters() []Linter { var ret []Linter for _, lc := range GetAllSupportedLinterConfigs() { if lc.EnabledByDefault { ret = append(ret, lc.Linter) } } return ret } var supportedLintersByName map[string]Linter var linterByNameMapOnce sync.Once func getLinterByName(name string) Linter { linterByNameMapOnce.Do(func() { supportedLintersByName = make(map[string]Linter) for _, lc := range GetAllSupportedLinterConfigs() { supportedLintersByName[lc.Linter.Name()] = lc.Linter } }) return supportedLintersByName[name] } func lintersToMap(linters []Linter) map[string]Linter { ret := map[string]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) ([]Linter, error) { if err := validateEnabledDisabledLintersConfig(cfg); err != nil { return nil, err } resultLintersSet := map[string]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 []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 }