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 }