refactor lintersdb: split it into abstractions

This commit is contained in:
Denis Isaev 2018-09-01 11:01:17 +03:00 committed by Isaev Denis
parent c37ad6652e
commit a24cc87a06
8 changed files with 291 additions and 252 deletions

View File

@ -2,6 +2,7 @@ package commands
import (
"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/report"
"github.com/spf13/cobra"
@ -10,15 +11,14 @@ import (
type Executor struct {
rootCmd *cobra.Command
cfg *config.Config
exitCode int
version, commit, date string
cfg *config.Config
log logutils.Log
reportData report.Data
DBManager *lintersdb.Manager
EnabledLintersSet *lintersdb.EnabledSet
}
func NewExecutor(version, commit, date string) *Executor {
@ -30,6 +30,9 @@ func NewExecutor(version, commit, date string) *Executor {
}
e.log = report.NewLogWrapper(logutils.NewStderrLog(""), &e.reportData)
e.DBManager = lintersdb.NewManager()
e.EnabledLintersSet = lintersdb.NewEnabledSet(e.DBManager, &lintersdb.Validator{},
e.log.Child("lintersdb"), e.cfg)
e.initRoot()
e.initRun()

View File

@ -7,7 +7,6 @@ import (
"github.com/fatih/color"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/spf13/cobra"
)
@ -41,7 +40,7 @@ func printLinterConfigs(lcs []linter.Config) {
func (e Executor) executeLintersHelp(cmd *cobra.Command, args []string) {
var enabledLCs, disabledLCs []linter.Config
for _, lc := range lintersdb.GetAllSupportedLinterConfigs() {
for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() {
if lc.EnabledByDefault {
enabledLCs = append(enabledLCs, lc)
} else {
@ -55,8 +54,8 @@ func (e Executor) executeLintersHelp(cmd *cobra.Command, args []string) {
printLinterConfigs(disabledLCs)
color.Green("\nLinters presets:")
for _, p := range lintersdb.AllPresets() {
linters := lintersdb.GetAllLinterConfigsForPreset(p)
for _, p := range e.DBManager.AllPresets() {
linters := e.DBManager.GetAllLinterConfigsForPreset(p)
linterNames := []string{}
for _, lc := range linters {
linterNames = append(linterNames, lc.Linter.Name())

View File

@ -6,7 +6,6 @@ import (
"github.com/fatih/color"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
"github.com/spf13/cobra"
)
@ -31,7 +30,7 @@ func IsLinterInConfigsList(name string, linters []linter.Config) bool {
}
func (e Executor) executeLinters(cmd *cobra.Command, args []string) {
enabledLCs, err := lintersdb.GetEnabledLinters(e.cfg, e.log.Child("lintersdb"))
enabledLCs, err := e.EnabledLintersSet.Get()
if err != nil {
log.Fatalf("Can't get enabled linters: %s", err)
}
@ -40,7 +39,7 @@ func (e Executor) executeLinters(cmd *cobra.Command, args []string) {
printLinterConfigs(enabledLCs)
var disabledLCs []linter.Config
for _, lc := range lintersdb.GetAllSupportedLinterConfigs() {
for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() {
if !IsLinterInConfigsList(lc.Linter.Name(), enabledLCs) {
disabledLCs = append(disabledLCs, lc)
}

View File

@ -40,7 +40,7 @@ func wh(text string) string {
return color.GreenString(text)
}
func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager) {
hideFlag := func(name string) {
if err := fs.MarkHidden(name); err != nil {
panic(err)
@ -137,7 +137,7 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters"))
fs.StringSliceVarP(&lc.Presets, "presets", "p", nil,
wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see "+
"them. This option implies option --disable-all", strings.Join(lintersdb.AllPresets(), "|"))))
"them. This option implies option --disable-all", strings.Join(m.AllPresets(), "|"))))
fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set"))
// Issues config
@ -167,7 +167,7 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
func (e *Executor) initRunConfiguration(cmd *cobra.Command) {
fs := cmd.Flags()
fs.SortFlags = false // sort them as they are defined here
initFlagSet(fs, e.cfg)
initFlagSet(fs, e.cfg, e.DBManager)
// 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
@ -178,7 +178,7 @@ func (e *Executor) initRunConfiguration(cmd *cobra.Command) {
// `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)
initFlagSet(fs, cfg, e.DBManager)
// Parse max options, even force version option: don't want
// to get access to Executor here: it's error-prone to use
@ -232,12 +232,12 @@ func fixSlicesFlags(fs *pflag.FlagSet) {
func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) {
e.cfg.Run.Args = args
linters, err := lintersdb.GetEnabledLinters(e.cfg, e.log.Child("lintersdb"))
linters, err := e.EnabledLintersSet.Get()
if err != nil {
return nil, err
}
for _, lc := range lintersdb.GetAllSupportedLinterConfigs() {
for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() {
isEnabled := false
for _, linter := range linters {
if linter.Linter.Name() == lc.Linter.Name() {

View File

@ -0,0 +1,148 @@
package lintersdb
import (
"sort"
"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/golinters"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/logutils"
)
type EnabledSet struct {
m *Manager
v *Validator
log logutils.Log
cfg *config.Config
}
func NewEnabledSet(m *Manager, v *Validator, log logutils.Log, cfg *config.Config) *EnabledSet {
return &EnabledSet{
m: m,
v: v,
log: log,
cfg: cfg,
}
}
// nolint:gocyclo
func (es EnabledSet) build(lcfg *config.Linters, enabledByDefaultLinters []linter.Config) map[string]*linter.Config {
resultLintersSet := map[string]*linter.Config{}
switch {
case len(lcfg.Presets) != 0:
break // imply --disable-all
case lcfg.EnableAll:
resultLintersSet = linterConfigsToMap(es.m.GetAllSupportedLinterConfigs())
case lcfg.DisableAll:
break
default:
resultLintersSet = linterConfigsToMap(enabledByDefaultLinters)
}
// --presets can only add linters to default set
for _, p := range lcfg.Presets {
for _, lc := range es.m.GetAllLinterConfigsForPreset(p) {
lc := lc
resultLintersSet[lc.Linter.Name()] = &lc
}
}
// --fast removes slow linters from current set.
// It should be after --presets to be able to run only fast linters in preset.
// It should be before --enable and --disable to be able to enable or disable specific linter.
if lcfg.Fast {
for name := range resultLintersSet {
if es.m.getLinterConfig(name).DoesFullImport {
delete(resultLintersSet, name)
}
}
}
for _, name := range lcfg.Enable {
resultLintersSet[name] = es.m.getLinterConfig(name)
}
for _, name := range lcfg.Disable {
if name == "megacheck" {
for _, ln := range getAllMegacheckSubLinterNames() {
delete(resultLintersSet, ln)
}
}
delete(resultLintersSet, name)
}
es.optimizeLintersSet(resultLintersSet)
return resultLintersSet
}
func getAllMegacheckSubLinterNames() []string {
unusedName := golinters.Megacheck{UnusedEnabled: true}.Name()
gosimpleName := golinters.Megacheck{GosimpleEnabled: true}.Name()
staticcheckName := golinters.Megacheck{StaticcheckEnabled: true}.Name()
return []string{unusedName, gosimpleName, staticcheckName}
}
func (es EnabledSet) optimizeLintersSet(linters map[string]*linter.Config) {
unusedName := golinters.Megacheck{UnusedEnabled: true}.Name()
gosimpleName := golinters.Megacheck{GosimpleEnabled: true}.Name()
staticcheckName := golinters.Megacheck{StaticcheckEnabled: true}.Name()
fullName := golinters.Megacheck{GosimpleEnabled: true, UnusedEnabled: true, StaticcheckEnabled: true}.Name()
allNames := []string{unusedName, gosimpleName, staticcheckName, fullName}
megacheckCount := 0
for _, n := range allNames {
if linters[n] != nil {
megacheckCount++
}
}
if megacheckCount <= 1 {
return
}
isFullEnabled := linters[fullName] != nil
mega := golinters.Megacheck{
UnusedEnabled: isFullEnabled || linters[unusedName] != nil,
GosimpleEnabled: isFullEnabled || linters[gosimpleName] != nil,
StaticcheckEnabled: isFullEnabled || linters[staticcheckName] != nil,
}
for _, n := range allNames {
delete(linters, n)
}
lc := *es.m.getLinterConfig("megacheck")
lc.Linter = mega
linters[mega.Name()] = &lc
}
func (es EnabledSet) Get() ([]linter.Config, error) {
if err := es.v.validateEnabledDisabledLintersConfig(&es.cfg.Linters); err != nil {
return nil, err
}
resultLintersSet := es.build(&es.cfg.Linters, es.m.GetAllEnabledByDefaultLinters())
var resultLinters []linter.Config
for _, lc := range resultLintersSet {
resultLinters = append(resultLinters, *lc)
}
es.verbosePrintLintersStatus(resultLinters)
return resultLinters, nil
}
func (es EnabledSet) verbosePrintLintersStatus(lcs []linter.Config) {
var linterNames []string
for _, lc := range lcs {
linterNames = append(linterNames, lc.Linter.Name())
}
sort.StringSlice(linterNames).Sort()
es.log.Infof("Active %d linters: %s", len(linterNames), linterNames)
if len(es.cfg.Linters.Presets) != 0 {
sort.StringSlice(es.cfg.Linters.Presets).Sort()
es.log.Infof("Active presets: %s", es.cfg.Linters.Presets)
}
}

View File

@ -44,13 +44,15 @@ func TestGetEnabledLintersSet(t *testing.T) {
},
}
m := NewManager()
es := NewEnabledSet(m, &Validator{}, nil, nil)
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defaultLinters := []linter.Config{}
for _, ln := range c.def {
defaultLinters = append(defaultLinters, *getLinterConfig(ln))
defaultLinters = append(defaultLinters, *m.getLinterConfig(ln))
}
els := getEnabledLintersSet(&c.cfg, defaultLinters)
els := es.build(&c.cfg, defaultLinters)
var enabledLinters []string
for ln, lc := range els {
assert.Equal(t, ln, lc.Linter.Name())

View File

@ -1,43 +1,42 @@
package lintersdb
import (
"fmt"
"os"
"sort"
"strings"
"sync"
"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/golinters"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/logutils"
)
func AllPresets() []string {
type Manager struct {
nameToLC map[string]linter.Config
}
func NewManager() *Manager {
m := &Manager{}
nameToLC := make(map[string]linter.Config)
for _, lc := range m.GetAllSupportedLinterConfigs() {
nameToLC[lc.Linter.Name()] = lc
}
m.nameToLC = nameToLC
return m
}
func (Manager) AllPresets() []string {
return []string{linter.PresetBugs, linter.PresetUnused, linter.PresetFormatting,
linter.PresetStyle, linter.PresetComplexity, linter.PresetPerformance}
}
func allPresetsSet() map[string]bool {
func (m Manager) allPresetsSet() map[string]bool {
ret := map[string]bool{}
for _, p := range AllPresets() {
for _, p := range m.AllPresets() {
ret[p] = true
}
return ret
}
var nameToLC map[string]linter.Config
var nameToLCOnce sync.Once
func getLinterConfig(name string) *linter.Config {
nameToLCOnce.Do(func() {
nameToLC = make(map[string]linter.Config)
for _, lc := range GetAllSupportedLinterConfigs() {
nameToLC[lc.Linter.Name()] = lc
}
})
lc, ok := nameToLC[name]
func (m Manager) getLinterConfig(name string) *linter.Config {
lc, ok := m.nameToLC[name]
if !ok {
return nil
}
@ -55,7 +54,7 @@ func enableLinterConfigs(lcs []linter.Config, isEnabled func(lc *linter.Config)
return ret
}
func GetAllSupportedLinterConfigs() []linter.Config {
func (Manager) GetAllSupportedLinterConfigs() []linter.Config {
lcs := []linter.Config{
linter.NewConfig(golinters.Govet{}).
WithFullImport(). // TODO: depend on it's configuration here
@ -207,9 +206,9 @@ func GetAllSupportedLinterConfigs() []linter.Config {
})
}
func GetAllEnabledByDefaultLinters() []linter.Config {
func (m Manager) GetAllEnabledByDefaultLinters() []linter.Config {
var ret []linter.Config
for _, lc := range GetAllSupportedLinterConfigs() {
for _, lc := range m.GetAllSupportedLinterConfigs() {
if lc.EnabledByDefault {
ret = append(ret, lc)
}
@ -228,89 +227,9 @@ func linterConfigsToMap(lcs []linter.Config) map[string]*linter.Config {
return ret
}
func validateLintersNames(cfg *config.Linters) error {
allNames := append([]string{}, cfg.Enable...)
allNames = append(allNames, cfg.Disable...)
for _, name := range allNames {
if getLinterConfig(name) == nil {
return fmt.Errorf("no such linter %q", name)
}
}
return nil
}
func validatePresets(cfg *config.Linters) 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.EnableAll {
return fmt.Errorf("--presets is incompatible with --enable-all")
}
return nil
}
func validateAllDisableEnableOptions(cfg *config.Linters) error {
if cfg.EnableAll && cfg.DisableAll {
return fmt.Errorf("--enable-all and --disable-all options must not be combined")
}
if cfg.DisableAll {
if len(cfg.Enable) == 0 && len(cfg.Presets) == 0 {
return fmt.Errorf("all linters were disabled, but no one linter was enabled: must enable at least one")
}
if len(cfg.Disable) != 0 {
return fmt.Errorf("can't combine options --disable-all and --disable %s", cfg.Disable[0])
}
}
if cfg.EnableAll && len(cfg.Enable) != 0 && !cfg.Fast {
return fmt.Errorf("can't combine options --enable-all and --enable %s", cfg.Enable[0])
}
return nil
}
func validateDisabledAndEnabledAtOneMoment(cfg *config.Linters) error {
enabledLintersSet := map[string]bool{}
for _, name := range cfg.Enable {
enabledLintersSet[name] = true
}
for _, name := range cfg.Disable {
if enabledLintersSet[name] {
return fmt.Errorf("linter %q can't be disabled and enabled at one moment", name)
}
}
return nil
}
func validateEnabledDisabledLintersConfig(cfg *config.Linters) error {
validators := []func(cfg *config.Linters) error{
validateLintersNames,
validatePresets,
validateAllDisableEnableOptions,
validateDisabledAndEnabledAtOneMoment,
}
for _, v := range validators {
if err := v(cfg); err != nil {
return err
}
}
return nil
}
func GetAllLinterConfigsForPreset(p string) []linter.Config {
func (m Manager) GetAllLinterConfigsForPreset(p string) []linter.Config {
ret := []linter.Config{}
for _, lc := range GetAllSupportedLinterConfigs() {
for _, lc := range m.GetAllSupportedLinterConfigs() {
for _, ip := range lc.InPresets {
if p == ip {
ret = append(ret, lc)
@ -321,127 +240,3 @@ func GetAllLinterConfigsForPreset(p string) []linter.Config {
return ret
}
// nolint:gocyclo
func getEnabledLintersSet(lcfg *config.Linters,
enabledByDefaultLinters []linter.Config) map[string]*linter.Config {
resultLintersSet := map[string]*linter.Config{}
switch {
case len(lcfg.Presets) != 0:
break // imply --disable-all
case lcfg.EnableAll:
resultLintersSet = linterConfigsToMap(GetAllSupportedLinterConfigs())
case lcfg.DisableAll:
break
default:
resultLintersSet = linterConfigsToMap(enabledByDefaultLinters)
}
// --presets can only add linters to default set
for _, p := range lcfg.Presets {
for _, lc := range GetAllLinterConfigsForPreset(p) {
lc := lc
resultLintersSet[lc.Linter.Name()] = &lc
}
}
// --fast removes slow linters from current set.
// It should be after --presets to be able to run only fast linters in preset.
// It should be before --enable and --disable to be able to enable or disable specific linter.
if lcfg.Fast {
for name := range resultLintersSet {
if getLinterConfig(name).DoesFullImport {
delete(resultLintersSet, name)
}
}
}
for _, name := range lcfg.Enable {
resultLintersSet[name] = getLinterConfig(name)
}
for _, name := range lcfg.Disable {
if name == "megacheck" {
for _, ln := range getAllMegacheckSubLinterNames() {
delete(resultLintersSet, ln)
}
}
delete(resultLintersSet, name)
}
optimizeLintersSet(resultLintersSet)
return resultLintersSet
}
func getAllMegacheckSubLinterNames() []string {
unusedName := golinters.Megacheck{UnusedEnabled: true}.Name()
gosimpleName := golinters.Megacheck{GosimpleEnabled: true}.Name()
staticcheckName := golinters.Megacheck{StaticcheckEnabled: true}.Name()
return []string{unusedName, gosimpleName, staticcheckName}
}
func optimizeLintersSet(linters map[string]*linter.Config) {
unusedName := golinters.Megacheck{UnusedEnabled: true}.Name()
gosimpleName := golinters.Megacheck{GosimpleEnabled: true}.Name()
staticcheckName := golinters.Megacheck{StaticcheckEnabled: true}.Name()
fullName := golinters.Megacheck{GosimpleEnabled: true, UnusedEnabled: true, StaticcheckEnabled: true}.Name()
allNames := []string{unusedName, gosimpleName, staticcheckName, fullName}
megacheckCount := 0
for _, n := range allNames {
if linters[n] != nil {
megacheckCount++
}
}
if megacheckCount <= 1 {
return
}
isFullEnabled := linters[fullName] != nil
m := golinters.Megacheck{
UnusedEnabled: isFullEnabled || linters[unusedName] != nil,
GosimpleEnabled: isFullEnabled || linters[gosimpleName] != nil,
StaticcheckEnabled: isFullEnabled || linters[staticcheckName] != nil,
}
for _, n := range allNames {
delete(linters, n)
}
lc := *getLinterConfig("megacheck")
lc.Linter = m
linters[m.Name()] = &lc
}
func GetEnabledLinters(cfg *config.Config, log logutils.Log) ([]linter.Config, error) {
if err := validateEnabledDisabledLintersConfig(&cfg.Linters); err != nil {
return nil, err
}
resultLintersSet := getEnabledLintersSet(&cfg.Linters, GetAllEnabledByDefaultLinters())
var resultLinters []linter.Config
for _, lc := range resultLintersSet {
resultLinters = append(resultLinters, *lc)
}
verbosePrintLintersStatus(cfg, resultLinters, log)
return resultLinters, nil
}
func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config, log logutils.Log) {
var linterNames []string
for _, lc := range lcs {
linterNames = append(linterNames, lc.Linter.Name())
}
sort.StringSlice(linterNames).Sort()
log.Infof("Active %d linters: %s", len(linterNames), linterNames)
if len(cfg.Linters.Presets) != 0 {
sort.StringSlice(cfg.Linters.Presets).Sort()
log.Infof("Active presets: %s", cfg.Linters.Presets)
}
}

View File

@ -0,0 +1,93 @@
package lintersdb
import (
"fmt"
"strings"
"github.com/golangci/golangci-lint/pkg/config"
)
type Validator struct {
m *Manager
}
func (v Validator) validateLintersNames(cfg *config.Linters) error {
allNames := append([]string{}, cfg.Enable...)
allNames = append(allNames, cfg.Disable...)
for _, name := range allNames {
if v.m.getLinterConfig(name) == nil {
return fmt.Errorf("no such linter %q", name)
}
}
return nil
}
func (v Validator) validatePresets(cfg *config.Linters) error {
allPresets := v.m.allPresetsSet()
for _, p := range cfg.Presets {
if !allPresets[p] {
return fmt.Errorf("no such preset %q: only next presets exist: (%s)",
p, strings.Join(v.m.AllPresets(), "|"))
}
}
if len(cfg.Presets) != 0 && cfg.EnableAll {
return fmt.Errorf("--presets is incompatible with --enable-all")
}
return nil
}
func (v Validator) validateAllDisableEnableOptions(cfg *config.Linters) error {
if cfg.EnableAll && cfg.DisableAll {
return fmt.Errorf("--enable-all and --disable-all options must not be combined")
}
if cfg.DisableAll {
if len(cfg.Enable) == 0 && len(cfg.Presets) == 0 {
return fmt.Errorf("all linters were disabled, but no one linter was enabled: must enable at least one")
}
if len(cfg.Disable) != 0 {
return fmt.Errorf("can't combine options --disable-all and --disable %s", cfg.Disable[0])
}
}
if cfg.EnableAll && len(cfg.Enable) != 0 && !cfg.Fast {
return fmt.Errorf("can't combine options --enable-all and --enable %s", cfg.Enable[0])
}
return nil
}
func (v Validator) validateDisabledAndEnabledAtOneMoment(cfg *config.Linters) error {
enabledLintersSet := map[string]bool{}
for _, name := range cfg.Enable {
enabledLintersSet[name] = true
}
for _, name := range cfg.Disable {
if enabledLintersSet[name] {
return fmt.Errorf("linter %q can't be disabled and enabled at one moment", name)
}
}
return nil
}
func (v Validator) validateEnabledDisabledLintersConfig(cfg *config.Linters) error {
validators := []func(cfg *config.Linters) error{
v.validateLintersNames,
v.validatePresets,
v.validateAllDisableEnableOptions,
v.validateDisabledAndEnabledAtOneMoment,
}
for _, v := range validators {
if err := v(cfg); err != nil {
return err
}
}
return nil
}