github.com/vanstinator/golangci-lint@v0.0.0-20240223191551-cc572f00d9d1/pkg/lint/lintersdb/enabled_set.go (about)

     1  package lintersdb
     2  
     3  import (
     4  	"os"
     5  	"sort"
     6  
     7  	"golang.org/x/exp/maps"
     8  
     9  	"github.com/vanstinator/golangci-lint/pkg/config"
    10  	"github.com/vanstinator/golangci-lint/pkg/golinters/goanalysis"
    11  	"github.com/vanstinator/golangci-lint/pkg/lint/linter"
    12  	"github.com/vanstinator/golangci-lint/pkg/logutils"
    13  )
    14  
    15  // EnvTestRun value: "1"
    16  const EnvTestRun = "GL_TEST_RUN"
    17  
    18  type EnabledSet struct {
    19  	m      *Manager
    20  	v      *Validator
    21  	log    logutils.Log
    22  	cfg    *config.Config
    23  	debugf logutils.DebugFunc
    24  }
    25  
    26  func NewEnabledSet(m *Manager, v *Validator, log logutils.Log, cfg *config.Config) *EnabledSet {
    27  	return &EnabledSet{
    28  		m:      m,
    29  		v:      v,
    30  		log:    log,
    31  		cfg:    cfg,
    32  		debugf: logutils.Debug(logutils.DebugKeyEnabledLinters),
    33  	}
    34  }
    35  
    36  //nolint:gocyclo // the complexity cannot be reduced.
    37  func (es EnabledSet) build(lcfg *config.Linters, enabledByDefaultLinters []*linter.Config) map[string]*linter.Config {
    38  	es.debugf("Linters config: %#v", lcfg)
    39  
    40  	resultLintersSet := map[string]*linter.Config{}
    41  	switch {
    42  	case len(lcfg.Presets) != 0:
    43  		break // imply --disable-all
    44  	case lcfg.EnableAll:
    45  		resultLintersSet = linterConfigsToMap(es.m.GetAllSupportedLinterConfigs())
    46  	case lcfg.DisableAll:
    47  		break
    48  	default:
    49  		resultLintersSet = linterConfigsToMap(enabledByDefaultLinters)
    50  	}
    51  
    52  	// --presets can only add linters to default set
    53  	for _, p := range lcfg.Presets {
    54  		for _, lc := range es.m.GetAllLinterConfigsForPreset(p) {
    55  			lc := lc
    56  			resultLintersSet[lc.Name()] = lc
    57  		}
    58  	}
    59  
    60  	// --fast removes slow linters from current set.
    61  	// It should be after --presets to be able to run only fast linters in preset.
    62  	// It should be before --enable and --disable to be able to enable or disable specific linter.
    63  	if lcfg.Fast {
    64  		for name, lc := range resultLintersSet {
    65  			if lc.IsSlowLinter() {
    66  				delete(resultLintersSet, name)
    67  			}
    68  		}
    69  	}
    70  
    71  	for _, name := range lcfg.Enable {
    72  		for _, lc := range es.m.GetLinterConfigs(name) {
    73  			// it's important to use lc.Name() nor name because name can be alias
    74  			resultLintersSet[lc.Name()] = lc
    75  		}
    76  	}
    77  
    78  	for _, name := range lcfg.Disable {
    79  		for _, lc := range es.m.GetLinterConfigs(name) {
    80  			// it's important to use lc.Name() nor name because name can be alias
    81  			delete(resultLintersSet, lc.Name())
    82  		}
    83  	}
    84  
    85  	// typecheck is not a real linter and cannot be disabled.
    86  	if _, ok := resultLintersSet["typecheck"]; !ok && (es.cfg == nil || !es.cfg.InternalCmdTest) {
    87  		for _, lc := range es.m.GetLinterConfigs("typecheck") {
    88  			// it's important to use lc.Name() nor name because name can be alias
    89  			resultLintersSet[lc.Name()] = lc
    90  		}
    91  	}
    92  
    93  	return resultLintersSet
    94  }
    95  
    96  func (es EnabledSet) GetEnabledLintersMap() (map[string]*linter.Config, error) {
    97  	if err := es.v.validateEnabledDisabledLintersConfig(&es.cfg.Linters); err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	enabledLinters := es.build(&es.cfg.Linters, es.m.GetAllEnabledByDefaultLinters())
   102  	if os.Getenv(EnvTestRun) == "1" {
   103  		es.verbosePrintLintersStatus(enabledLinters)
   104  	}
   105  	return enabledLinters, nil
   106  }
   107  
   108  // GetOptimizedLinters returns enabled linters after optimization (merging) of multiple linters
   109  // into a fewer number of linters. E.g. some go/analysis linters can be optimized into
   110  // one metalinter for data reuse and speed up.
   111  func (es EnabledSet) GetOptimizedLinters() ([]*linter.Config, error) {
   112  	if err := es.v.validateEnabledDisabledLintersConfig(&es.cfg.Linters); err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	resultLintersSet := es.build(&es.cfg.Linters, es.m.GetAllEnabledByDefaultLinters())
   117  	es.verbosePrintLintersStatus(resultLintersSet)
   118  	es.combineGoAnalysisLinters(resultLintersSet)
   119  
   120  	resultLinters := maps.Values(resultLintersSet)
   121  
   122  	// Make order of execution of linters (go/analysis metalinter and unused) stable.
   123  	sort.Slice(resultLinters, func(i, j int) bool {
   124  		a, b := resultLinters[i], resultLinters[j]
   125  
   126  		if b.Name() == linter.LastLinter {
   127  			return true
   128  		}
   129  
   130  		if a.Name() == linter.LastLinter {
   131  			return false
   132  		}
   133  
   134  		if a.DoesChangeTypes != b.DoesChangeTypes {
   135  			return b.DoesChangeTypes // move type-changing linters to the end to optimize speed
   136  		}
   137  		return a.Name() < b.Name()
   138  	})
   139  
   140  	return resultLinters, nil
   141  }
   142  
   143  func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config) {
   144  	var goanalysisLinters []*goanalysis.Linter
   145  	goanalysisPresets := map[string]bool{}
   146  	for _, lc := range linters {
   147  		lnt, ok := lc.Linter.(*goanalysis.Linter)
   148  		if !ok {
   149  			continue
   150  		}
   151  		if lnt.LoadMode() == goanalysis.LoadModeWholeProgram {
   152  			// It's ineffective by CPU and memory to run whole-program and incremental analyzers at once.
   153  			continue
   154  		}
   155  		goanalysisLinters = append(goanalysisLinters, lnt)
   156  		for _, p := range lc.InPresets {
   157  			goanalysisPresets[p] = true
   158  		}
   159  	}
   160  
   161  	if len(goanalysisLinters) <= 1 {
   162  		es.debugf("Didn't combine go/analysis linters: got only %d linters", len(goanalysisLinters))
   163  		return
   164  	}
   165  
   166  	for _, lnt := range goanalysisLinters {
   167  		delete(linters, lnt.Name())
   168  	}
   169  
   170  	// Make order of execution of go/analysis analyzers stable.
   171  	sort.Slice(goanalysisLinters, func(i, j int) bool {
   172  		a, b := goanalysisLinters[i], goanalysisLinters[j]
   173  
   174  		if b.Name() == linter.LastLinter {
   175  			return true
   176  		}
   177  
   178  		if a.Name() == linter.LastLinter {
   179  			return false
   180  		}
   181  
   182  		return a.Name() <= b.Name()
   183  	})
   184  
   185  	ml := goanalysis.NewMetaLinter(goanalysisLinters)
   186  
   187  	presets := maps.Keys(goanalysisPresets)
   188  
   189  	mlConfig := &linter.Config{
   190  		Linter:           ml,
   191  		EnabledByDefault: false,
   192  		InPresets:        presets,
   193  		AlternativeNames: nil,
   194  		OriginalURL:      "",
   195  	}
   196  
   197  	mlConfig = mlConfig.WithLoadForGoAnalysis()
   198  
   199  	linters[ml.Name()] = mlConfig
   200  	es.debugf("Combined %d go/analysis linters into one metalinter", len(goanalysisLinters))
   201  }
   202  
   203  func (es EnabledSet) verbosePrintLintersStatus(lcs map[string]*linter.Config) {
   204  	var linterNames []string
   205  	for _, lc := range lcs {
   206  		if lc.Internal {
   207  			continue
   208  		}
   209  
   210  		linterNames = append(linterNames, lc.Name())
   211  	}
   212  	sort.StringSlice(linterNames).Sort()
   213  	es.log.Infof("Active %d linters: %s", len(linterNames), linterNames)
   214  
   215  	if len(es.cfg.Linters.Presets) != 0 {
   216  		sort.StringSlice(es.cfg.Linters.Presets).Sort()
   217  		es.log.Infof("Active presets: %s", es.cfg.Linters.Presets)
   218  	}
   219  }