github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/config/linters_settings_gocritic.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 _ "github.com/go-critic/go-critic/checkers" // this import register checkers 9 "github.com/go-critic/go-critic/framework/linter" 10 "github.com/pkg/errors" 11 12 "github.com/elek/golangci-lint/pkg/logutils" 13 ) 14 15 const gocriticDebugKey = "gocritic" 16 17 var ( 18 gocriticDebugf = logutils.Debug(gocriticDebugKey) 19 isGocriticDebug = logutils.HaveDebugTag(gocriticDebugKey) 20 allGocriticCheckers = linter.GetCheckersInfo() 21 allGocriticCheckerMap = func() map[string]*linter.CheckerInfo { 22 checkInfoMap := make(map[string]*linter.CheckerInfo) 23 for _, checkInfo := range allGocriticCheckers { 24 checkInfoMap[checkInfo.Name] = checkInfo 25 } 26 return checkInfoMap 27 }() 28 ) 29 30 type GocriticCheckSettings map[string]interface{} 31 32 type GocriticSettings struct { 33 EnabledChecks []string `mapstructure:"enabled-checks"` 34 DisabledChecks []string `mapstructure:"disabled-checks"` 35 EnabledTags []string `mapstructure:"enabled-tags"` 36 DisabledTags []string `mapstructure:"disabled-tags"` 37 SettingsPerCheck map[string]GocriticCheckSettings `mapstructure:"settings"` 38 39 inferredEnabledChecks map[string]bool 40 } 41 42 func debugChecksListf(checks []string, format string, args ...interface{}) { 43 if isGocriticDebug { 44 prefix := fmt.Sprintf(format, args...) 45 gocriticDebugf(prefix+" checks (%d): %s", len(checks), sprintStrings(checks)) 46 } 47 } 48 49 func stringsSliceToSet(ss []string) map[string]bool { 50 ret := map[string]bool{} 51 for _, s := range ss { 52 ret[s] = true 53 } 54 55 return ret 56 } 57 58 func buildGocriticTagToCheckersMap() map[string][]string { 59 tagToCheckers := map[string][]string{} 60 for _, checker := range allGocriticCheckers { 61 for _, tag := range checker.Tags { 62 tagToCheckers[tag] = append(tagToCheckers[tag], checker.Name) 63 } 64 } 65 return tagToCheckers 66 } 67 68 func gocriticCheckerTagsDebugf() { 69 if !isGocriticDebug { 70 return 71 } 72 73 tagToCheckers := buildGocriticTagToCheckersMap() 74 75 var allTags []string 76 for tag := range tagToCheckers { 77 allTags = append(allTags, tag) 78 } 79 sort.Strings(allTags) 80 81 gocriticDebugf("All gocritic existing tags and checks:") 82 for _, tag := range allTags { 83 debugChecksListf(tagToCheckers[tag], " tag %q", tag) 84 } 85 } 86 87 func (s *GocriticSettings) gocriticDisabledCheckersDebugf() { 88 if !isGocriticDebug { 89 return 90 } 91 92 var disabledCheckers []string 93 for _, checker := range allGocriticCheckers { 94 if s.inferredEnabledChecks[strings.ToLower(checker.Name)] { 95 continue 96 } 97 98 disabledCheckers = append(disabledCheckers, checker.Name) 99 } 100 101 if len(disabledCheckers) == 0 { 102 gocriticDebugf("All checks are enabled") 103 } else { 104 debugChecksListf(disabledCheckers, "Final not used") 105 } 106 } 107 108 func (s *GocriticSettings) InferEnabledChecks(log logutils.Log) { 109 gocriticCheckerTagsDebugf() 110 111 enabledByDefaultChecks := getDefaultEnabledGocriticCheckersNames() 112 debugChecksListf(enabledByDefaultChecks, "Enabled by default") 113 114 disabledByDefaultChecks := getDefaultDisabledGocriticCheckersNames() 115 debugChecksListf(disabledByDefaultChecks, "Disabled by default") 116 117 var enabledChecks []string 118 119 // EnabledTags 120 if len(s.EnabledTags) != 0 { 121 tagToCheckers := buildGocriticTagToCheckersMap() 122 for _, tag := range s.EnabledTags { 123 enabledChecks = append(enabledChecks, tagToCheckers[tag]...) 124 } 125 debugChecksListf(enabledChecks, "Enabled by config tags %s", sprintStrings(s.EnabledTags)) 126 } 127 128 if !(len(s.EnabledTags) == 0 && len(s.EnabledChecks) != 0) { 129 // don't use default checks only if we have no enabled tags and enable some checks manually 130 enabledChecks = append(enabledChecks, enabledByDefaultChecks...) 131 } 132 133 // DisabledTags 134 if len(s.DisabledTags) != 0 { 135 enabledChecks = filterByDisableTags(enabledChecks, s.DisabledTags, log) 136 } 137 138 // EnabledChecks 139 if len(s.EnabledChecks) != 0 { 140 debugChecksListf(s.EnabledChecks, "Enabled by config") 141 142 alreadyEnabledChecksSet := stringsSliceToSet(enabledChecks) 143 for _, enabledCheck := range s.EnabledChecks { 144 if alreadyEnabledChecksSet[enabledCheck] { 145 log.Warnf("No need to enable check %q: it's already enabled", enabledCheck) 146 continue 147 } 148 enabledChecks = append(enabledChecks, enabledCheck) 149 } 150 } 151 152 // DisabledChecks 153 if len(s.DisabledChecks) != 0 { 154 debugChecksListf(s.DisabledChecks, "Disabled by config") 155 156 enabledChecksSet := stringsSliceToSet(enabledChecks) 157 for _, disabledCheck := range s.DisabledChecks { 158 if !enabledChecksSet[disabledCheck] { 159 log.Warnf("Gocritic check %q was explicitly disabled via config. However, as this check"+ 160 "is disabled by default, there is no need to explicitly disable it via config.", disabledCheck) 161 continue 162 } 163 delete(enabledChecksSet, disabledCheck) 164 } 165 166 enabledChecks = nil 167 for enabledCheck := range enabledChecksSet { 168 enabledChecks = append(enabledChecks, enabledCheck) 169 } 170 } 171 172 s.inferredEnabledChecks = map[string]bool{} 173 for _, check := range enabledChecks { 174 s.inferredEnabledChecks[strings.ToLower(check)] = true 175 } 176 177 debugChecksListf(enabledChecks, "Final used") 178 s.gocriticDisabledCheckersDebugf() 179 } 180 181 func validateStringsUniq(ss []string) error { 182 set := map[string]bool{} 183 for _, s := range ss { 184 _, ok := set[s] 185 if ok { 186 return fmt.Errorf("%q occurs multiple times in list", s) 187 } 188 set[s] = true 189 } 190 191 return nil 192 } 193 194 func intersectStringSlice(s1, s2 []string) []string { 195 s1Map := make(map[string]struct{}) 196 for _, s := range s1 { 197 s1Map[s] = struct{}{} 198 } 199 200 result := make([]string, 0) 201 for _, s := range s2 { 202 if _, exists := s1Map[s]; exists { 203 result = append(result, s) 204 } 205 } 206 207 return result 208 } 209 210 func (s *GocriticSettings) Validate(log logutils.Log) error { 211 if len(s.EnabledTags) == 0 { 212 if len(s.EnabledChecks) != 0 && len(s.DisabledChecks) != 0 { 213 return errors.New("both enabled and disabled check aren't allowed for gocritic") 214 } 215 } else { 216 if err := validateStringsUniq(s.EnabledTags); err != nil { 217 return errors.Wrap(err, "validate enabled tags") 218 } 219 220 tagToCheckers := buildGocriticTagToCheckersMap() 221 for _, tag := range s.EnabledTags { 222 if _, ok := tagToCheckers[tag]; !ok { 223 return fmt.Errorf("gocritic [enabled]tag %q doesn't exist", tag) 224 } 225 } 226 } 227 228 if len(s.DisabledTags) > 0 { 229 tagToCheckers := buildGocriticTagToCheckersMap() 230 for _, tag := range s.EnabledTags { 231 if _, ok := tagToCheckers[tag]; !ok { 232 return fmt.Errorf("gocritic [disabled]tag %q doesn't exist", tag) 233 } 234 } 235 } 236 237 if err := validateStringsUniq(s.EnabledChecks); err != nil { 238 return errors.Wrap(err, "validate enabled checks") 239 } 240 if err := validateStringsUniq(s.DisabledChecks); err != nil { 241 return errors.Wrap(err, "validate disabled checks") 242 } 243 244 if err := s.validateCheckerNames(log); err != nil { 245 return errors.Wrap(err, "validation failed") 246 } 247 248 return nil 249 } 250 251 func (s *GocriticSettings) IsCheckEnabled(name string) bool { 252 return s.inferredEnabledChecks[strings.ToLower(name)] 253 } 254 255 func sprintAllowedCheckerNames(allowedNames map[string]bool) string { 256 var namesSlice []string 257 for name := range allowedNames { 258 namesSlice = append(namesSlice, name) 259 } 260 return sprintStrings(namesSlice) 261 } 262 263 func sprintStrings(ss []string) string { 264 sort.Strings(ss) 265 return fmt.Sprint(ss) 266 } 267 268 // getAllCheckerNames returns a map containing all checker names supported by gocritic. 269 func getAllCheckerNames() map[string]bool { 270 allCheckerNames := map[string]bool{} 271 for _, checker := range allGocriticCheckers { 272 allCheckerNames[strings.ToLower(checker.Name)] = true 273 } 274 275 return allCheckerNames 276 } 277 278 func isEnabledByDefaultGocriticCheck(info *linter.CheckerInfo) bool { 279 return !info.HasTag("experimental") && 280 !info.HasTag("opinionated") && 281 !info.HasTag("performance") 282 } 283 284 func getDefaultEnabledGocriticCheckersNames() []string { 285 var enabled []string 286 for _, info := range allGocriticCheckers { 287 enable := isEnabledByDefaultGocriticCheck(info) 288 if enable { 289 enabled = append(enabled, info.Name) 290 } 291 } 292 293 return enabled 294 } 295 296 func getDefaultDisabledGocriticCheckersNames() []string { 297 var disabled []string 298 for _, info := range allGocriticCheckers { 299 enable := isEnabledByDefaultGocriticCheck(info) 300 if !enable { 301 disabled = append(disabled, info.Name) 302 } 303 } 304 305 return disabled 306 } 307 308 func (s *GocriticSettings) validateCheckerNames(log logutils.Log) error { 309 allowedNames := getAllCheckerNames() 310 311 for _, name := range s.EnabledChecks { 312 if !allowedNames[strings.ToLower(name)] { 313 return fmt.Errorf("enabled checker %s doesn't exist, all existing checkers: %s", 314 name, sprintAllowedCheckerNames(allowedNames)) 315 } 316 } 317 318 for _, name := range s.DisabledChecks { 319 if !allowedNames[strings.ToLower(name)] { 320 return fmt.Errorf("disabled checker %s doesn't exist, all existing checkers: %s", 321 name, sprintAllowedCheckerNames(allowedNames)) 322 } 323 } 324 325 for checkName := range s.SettingsPerCheck { 326 if _, ok := allowedNames[checkName]; !ok { 327 return fmt.Errorf("invalid setting, checker %s doesn't exist, all existing checkers: %s", 328 checkName, sprintAllowedCheckerNames(allowedNames)) 329 } 330 if !s.IsCheckEnabled(checkName) { 331 log.Warnf("Gocritic settings were provided for not enabled check %q", checkName) 332 } 333 } 334 335 return nil 336 } 337 338 func (s *GocriticSettings) GetLowercasedParams() map[string]GocriticCheckSettings { 339 ret := map[string]GocriticCheckSettings{} 340 for checker, params := range s.SettingsPerCheck { 341 ret[strings.ToLower(checker)] = params 342 } 343 return ret 344 } 345 346 func filterByDisableTags(enabledChecks, disableTags []string, log logutils.Log) []string { 347 enabledChecksSet := stringsSliceToSet(enabledChecks) 348 for _, enabledCheck := range enabledChecks { 349 checkInfo, checkInfoExists := allGocriticCheckerMap[enabledCheck] 350 if !checkInfoExists { 351 log.Warnf("Gocritic check %q was not exists via filtering disabled tags", enabledCheck) 352 continue 353 } 354 hitTags := intersectStringSlice(checkInfo.Tags, disableTags) 355 if len(hitTags) != 0 { 356 delete(enabledChecksSet, enabledCheck) 357 } 358 debugChecksListf(enabledChecks, "Disabled by config tags %s", sprintStrings(disableTags)) 359 } 360 enabledChecks = nil 361 for enabledCheck := range enabledChecksSet { 362 enabledChecks = append(enabledChecks, enabledCheck) 363 } 364 return enabledChecks 365 }