github.com/chenfeining/golangci-lint@v1.0.2-0.20230730162517-14c6c67868df/pkg/lint/lintersdb/enabled_set.go (about)

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