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 }