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  }