github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/lint/lintersdb/enabled_set.go (about)

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