589 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			589 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package golinters
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/types"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"runtime"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/go-critic/go-critic/checkers"
 | |
| 	gocriticlinter "github.com/go-critic/go-critic/linter"
 | |
| 	"golang.org/x/exp/maps"
 | |
| 	"golang.org/x/exp/slices"
 | |
| 	"golang.org/x/tools/go/analysis"
 | |
| 
 | |
| 	"github.com/golangci/golangci-lint/pkg/config"
 | |
| 	"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
 | |
| 	"github.com/golangci/golangci-lint/pkg/lint/linter"
 | |
| 	"github.com/golangci/golangci-lint/pkg/logutils"
 | |
| 	"github.com/golangci/golangci-lint/pkg/result"
 | |
| )
 | |
| 
 | |
| const goCriticName = "gocritic"
 | |
| 
 | |
| var (
 | |
| 	goCriticDebugf  = logutils.Debug(logutils.DebugKeyGoCritic)
 | |
| 	isGoCriticDebug = logutils.HaveDebugTag(logutils.DebugKeyGoCritic)
 | |
| )
 | |
| 
 | |
| func NewGoCritic(settings *config.GoCriticSettings, cfg *config.Config) *goanalysis.Linter {
 | |
| 	var mu sync.Mutex
 | |
| 	var resIssues []goanalysis.Issue
 | |
| 
 | |
| 	wrapper := &goCriticWrapper{
 | |
| 		getConfigDir: cfg.GetConfigDir, // Config directory is filled after calling this constructor.
 | |
| 		sizes:        types.SizesFor("gc", runtime.GOARCH),
 | |
| 	}
 | |
| 
 | |
| 	analyzer := &analysis.Analyzer{
 | |
| 		Name: goCriticName,
 | |
| 		Doc:  goanalysis.TheOnlyanalyzerDoc,
 | |
| 		Run: func(pass *analysis.Pass) (any, error) {
 | |
| 			issues, err := wrapper.run(pass)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 
 | |
| 			if len(issues) == 0 {
 | |
| 				return nil, nil
 | |
| 			}
 | |
| 
 | |
| 			mu.Lock()
 | |
| 			resIssues = append(resIssues, issues...)
 | |
| 			mu.Unlock()
 | |
| 
 | |
| 			return nil, nil
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	return goanalysis.NewLinter(
 | |
| 		goCriticName,
 | |
| 		`Provides diagnostics that check for bugs, performance and style issues.
 | |
| Extensible without recompilation through dynamic rules.
 | |
| Dynamic rules are written declaratively with AST patterns, filters, report message and optional suggestion.`,
 | |
| 		[]*analysis.Analyzer{analyzer},
 | |
| 		nil,
 | |
| 	).
 | |
| 		WithContextSetter(func(context *linter.Context) {
 | |
| 			wrapper.init(settings, context.Log)
 | |
| 		}).
 | |
| 		WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
 | |
| 			return resIssues
 | |
| 		}).
 | |
| 		WithLoadMode(goanalysis.LoadModeTypesInfo)
 | |
| }
 | |
| 
 | |
| type goCriticWrapper struct {
 | |
| 	settingsWrapper *goCriticSettingsWrapper
 | |
| 	getConfigDir    func() string
 | |
| 	sizes           types.Sizes
 | |
| 	once            sync.Once
 | |
| }
 | |
| 
 | |
| func (w *goCriticWrapper) init(settings *config.GoCriticSettings, logger logutils.Log) {
 | |
| 	if settings == nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	w.once.Do(func() {
 | |
| 		err := checkers.InitEmbeddedRules()
 | |
| 		if err != nil {
 | |
| 			logger.Fatalf("%s: %v: setting an explicit GOROOT can fix this problem", goCriticName, err)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	settingsWrapper := newGoCriticSettingsWrapper(settings, logger)
 | |
| 	settingsWrapper.InferEnabledChecks()
 | |
| 	// Validate must be after InferEnabledChecks, not before.
 | |
| 	// Because it uses gathered information about tags set and finally enabled checks.
 | |
| 	if err := settingsWrapper.Validate(); err != nil {
 | |
| 		logger.Fatalf("%s: invalid settings: %s", goCriticName, err)
 | |
| 	}
 | |
| 
 | |
| 	w.settingsWrapper = settingsWrapper
 | |
| }
 | |
| 
 | |
| func (w *goCriticWrapper) run(pass *analysis.Pass) ([]goanalysis.Issue, error) {
 | |
| 	if w.settingsWrapper == nil {
 | |
| 		return nil, errors.New("the settings wrapper is nil")
 | |
| 	}
 | |
| 
 | |
| 	linterCtx := gocriticlinter.NewContext(pass.Fset, w.sizes)
 | |
| 
 | |
| 	linterCtx.SetGoVersion(w.settingsWrapper.Go)
 | |
| 
 | |
| 	enabledCheckers, err := w.buildEnabledCheckers(linterCtx)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	linterCtx.SetPackageInfo(pass.TypesInfo, pass.Pkg)
 | |
| 
 | |
| 	pkgIssues := runGoCriticOnPackage(linterCtx, enabledCheckers, pass.Files)
 | |
| 
 | |
| 	issues := make([]goanalysis.Issue, 0, len(pkgIssues))
 | |
| 	for i := range pkgIssues {
 | |
| 		issues = append(issues, goanalysis.NewIssue(&pkgIssues[i], pass))
 | |
| 	}
 | |
| 
 | |
| 	return issues, nil
 | |
| }
 | |
| 
 | |
| func (w *goCriticWrapper) buildEnabledCheckers(linterCtx *gocriticlinter.Context) ([]*gocriticlinter.Checker, error) {
 | |
| 	allLowerCasedParams := w.settingsWrapper.GetLowerCasedParams()
 | |
| 
 | |
| 	var enabledCheckers []*gocriticlinter.Checker
 | |
| 	for _, info := range gocriticlinter.GetCheckersInfo() {
 | |
| 		if !w.settingsWrapper.IsCheckEnabled(info.Name) {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if err := w.configureCheckerInfo(info, allLowerCasedParams); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		c, err := gocriticlinter.NewChecker(linterCtx, info)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		enabledCheckers = append(enabledCheckers, c)
 | |
| 	}
 | |
| 
 | |
| 	return enabledCheckers, nil
 | |
| }
 | |
| 
 | |
| func (w *goCriticWrapper) configureCheckerInfo(
 | |
| 	info *gocriticlinter.CheckerInfo,
 | |
| 	allLowerCasedParams map[string]config.GoCriticCheckSettings,
 | |
| ) error {
 | |
| 	params := allLowerCasedParams[strings.ToLower(info.Name)]
 | |
| 	if params == nil { // no config for this checker
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// To lowercase info param keys here because golangci-lint's config parser lowercases all strings.
 | |
| 	infoParams := normalizeMap(info.Params)
 | |
| 	for k, p := range params {
 | |
| 		v, ok := infoParams[k]
 | |
| 		if ok {
 | |
| 			v.Value = w.normalizeCheckerParamsValue(p)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// param `k` isn't supported
 | |
| 		if len(info.Params) == 0 {
 | |
| 			return fmt.Errorf("checker %s config param %s doesn't exist: checker doesn't have params",
 | |
| 				info.Name, k)
 | |
| 		}
 | |
| 
 | |
| 		supportedKeys := maps.Keys(info.Params)
 | |
| 		sort.Strings(supportedKeys)
 | |
| 
 | |
| 		return fmt.Errorf("checker %s config param %s doesn't exist, all existing: %s",
 | |
| 			info.Name, k, supportedKeys)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // normalizeCheckerParamsValue normalizes value types.
 | |
| // go-critic asserts that CheckerParam.Value has some specific types,
 | |
| // but the file parsers (TOML, YAML, JSON) don't create the same representation for raw type.
 | |
| // then we have to convert value types into the expected value types.
 | |
| // Maybe in the future, this kind of conversion will be done in go-critic itself.
 | |
| func (w *goCriticWrapper) normalizeCheckerParamsValue(p any) any {
 | |
| 	rv := reflect.ValueOf(p)
 | |
| 	switch rv.Type().Kind() {
 | |
| 	case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
 | |
| 		return int(rv.Int())
 | |
| 	case reflect.Bool:
 | |
| 		return rv.Bool()
 | |
| 	case reflect.String:
 | |
| 		// Perform variable substitution.
 | |
| 		return strings.ReplaceAll(rv.String(), "${configDir}", w.getConfigDir())
 | |
| 	default:
 | |
| 		return p
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func runGoCriticOnPackage(linterCtx *gocriticlinter.Context, checks []*gocriticlinter.Checker, files []*ast.File) []result.Issue {
 | |
| 	var res []result.Issue
 | |
| 	for _, f := range files {
 | |
| 		filename := filepath.Base(linterCtx.FileSet.Position(f.Pos()).Filename)
 | |
| 		linterCtx.SetFileInfo(filename, f)
 | |
| 
 | |
| 		issues := runGoCriticOnFile(linterCtx, f, checks)
 | |
| 		res = append(res, issues...)
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func runGoCriticOnFile(linterCtx *gocriticlinter.Context, f *ast.File, checks []*gocriticlinter.Checker) []result.Issue {
 | |
| 	var res []result.Issue
 | |
| 
 | |
| 	for _, c := range checks {
 | |
| 		// All checkers are expected to use *lint.Context
 | |
| 		// as read-only structure, so no copying is required.
 | |
| 		for _, warn := range c.Check(f) {
 | |
| 			pos := linterCtx.FileSet.Position(warn.Pos)
 | |
| 			issue := result.Issue{
 | |
| 				Pos:        pos,
 | |
| 				Text:       fmt.Sprintf("%s: %s", c.Info.Name, warn.Text),
 | |
| 				FromLinter: goCriticName,
 | |
| 			}
 | |
| 
 | |
| 			if warn.HasQuickFix() {
 | |
| 				issue.Replacement = &result.Replacement{
 | |
| 					Inline: &result.InlineFix{
 | |
| 						StartCol:  pos.Column - 1,
 | |
| 						Length:    int(warn.Suggestion.To - warn.Suggestion.From),
 | |
| 						NewString: string(warn.Suggestion.Replacement),
 | |
| 					},
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			res = append(res, issue)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| type goCriticChecks[T any] map[string]T
 | |
| 
 | |
| func (m goCriticChecks[T]) has(name string) bool {
 | |
| 	_, ok := m[name]
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| type goCriticSettingsWrapper struct {
 | |
| 	*config.GoCriticSettings
 | |
| 
 | |
| 	logger logutils.Log
 | |
| 
 | |
| 	allCheckers []*gocriticlinter.CheckerInfo
 | |
| 
 | |
| 	allChecks             goCriticChecks[struct{}]
 | |
| 	allChecksByTag        goCriticChecks[[]string]
 | |
| 	allTagsSorted         []string
 | |
| 	inferredEnabledChecks goCriticChecks[struct{}]
 | |
| 
 | |
| 	// *LowerCased fields are used for GoCriticSettings.SettingsPerCheck validation only.
 | |
| 
 | |
| 	allChecksLowerCased             goCriticChecks[struct{}]
 | |
| 	inferredEnabledChecksLowerCased goCriticChecks[struct{}]
 | |
| }
 | |
| 
 | |
| func newGoCriticSettingsWrapper(settings *config.GoCriticSettings, logger logutils.Log) *goCriticSettingsWrapper {
 | |
| 	allCheckers := gocriticlinter.GetCheckersInfo()
 | |
| 
 | |
| 	allChecks := make(goCriticChecks[struct{}], len(allCheckers))
 | |
| 	allChecksLowerCased := make(goCriticChecks[struct{}], len(allCheckers))
 | |
| 	allChecksByTag := make(goCriticChecks[[]string])
 | |
| 	for _, checker := range allCheckers {
 | |
| 		allChecks[checker.Name] = struct{}{}
 | |
| 		allChecksLowerCased[strings.ToLower(checker.Name)] = struct{}{}
 | |
| 
 | |
| 		for _, tag := range checker.Tags {
 | |
| 			allChecksByTag[tag] = append(allChecksByTag[tag], checker.Name)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	allTagsSorted := maps.Keys(allChecksByTag)
 | |
| 	sort.Strings(allTagsSorted)
 | |
| 
 | |
| 	return &goCriticSettingsWrapper{
 | |
| 		GoCriticSettings:                settings,
 | |
| 		logger:                          logger,
 | |
| 		allCheckers:                     allCheckers,
 | |
| 		allChecks:                       allChecks,
 | |
| 		allChecksLowerCased:             allChecksLowerCased,
 | |
| 		allChecksByTag:                  allChecksByTag,
 | |
| 		allTagsSorted:                   allTagsSorted,
 | |
| 		inferredEnabledChecks:           make(goCriticChecks[struct{}]),
 | |
| 		inferredEnabledChecksLowerCased: make(goCriticChecks[struct{}]),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) IsCheckEnabled(name string) bool {
 | |
| 	return s.inferredEnabledChecks.has(name)
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) GetLowerCasedParams() map[string]config.GoCriticCheckSettings {
 | |
| 	return normalizeMap(s.SettingsPerCheck)
 | |
| }
 | |
| 
 | |
| // InferEnabledChecks tries to be consistent with (lintersdb.EnabledSet).build.
 | |
| func (s *goCriticSettingsWrapper) InferEnabledChecks() {
 | |
| 	s.debugChecksInitialState()
 | |
| 
 | |
| 	enabledByDefaultChecks, disabledByDefaultChecks := s.buildEnabledAndDisabledByDefaultChecks()
 | |
| 	debugChecksListf(enabledByDefaultChecks, "Enabled by default")
 | |
| 	debugChecksListf(disabledByDefaultChecks, "Disabled by default")
 | |
| 
 | |
| 	enabledChecks := make(goCriticChecks[struct{}])
 | |
| 
 | |
| 	if s.EnableAll {
 | |
| 		enabledChecks = make(goCriticChecks[struct{}], len(s.allCheckers))
 | |
| 		for _, info := range s.allCheckers {
 | |
| 			enabledChecks[info.Name] = struct{}{}
 | |
| 		}
 | |
| 	} else if !s.DisableAll {
 | |
| 		// enable-all/disable-all revokes the default settings.
 | |
| 		enabledChecks = make(goCriticChecks[struct{}], len(enabledByDefaultChecks))
 | |
| 		for _, check := range enabledByDefaultChecks {
 | |
| 			enabledChecks[check] = struct{}{}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(s.EnabledTags) != 0 {
 | |
| 		enabledFromTags := s.expandTagsToChecks(s.EnabledTags)
 | |
| 		debugChecksListf(enabledFromTags, "Enabled by config tags %s", sprintSortedStrings(s.EnabledTags))
 | |
| 
 | |
| 		for _, check := range enabledFromTags {
 | |
| 			enabledChecks[check] = struct{}{}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(s.EnabledChecks) != 0 {
 | |
| 		debugChecksListf(s.EnabledChecks, "Enabled by config")
 | |
| 
 | |
| 		for _, check := range s.EnabledChecks {
 | |
| 			if enabledChecks.has(check) {
 | |
| 				s.logger.Warnf("%s: no need to enable check %q: it's already enabled", goCriticName, check)
 | |
| 				continue
 | |
| 			}
 | |
| 			enabledChecks[check] = struct{}{}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(s.DisabledTags) != 0 {
 | |
| 		disabledFromTags := s.expandTagsToChecks(s.DisabledTags)
 | |
| 		debugChecksListf(disabledFromTags, "Disabled by config tags %s", sprintSortedStrings(s.DisabledTags))
 | |
| 
 | |
| 		for _, check := range disabledFromTags {
 | |
| 			delete(enabledChecks, check)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(s.DisabledChecks) != 0 {
 | |
| 		debugChecksListf(s.DisabledChecks, "Disabled by config")
 | |
| 
 | |
| 		for _, check := range s.DisabledChecks {
 | |
| 			if !enabledChecks.has(check) {
 | |
| 				s.logger.Warnf("%s: no need to disable check %q: it's already disabled", goCriticName, check)
 | |
| 				continue
 | |
| 			}
 | |
| 			delete(enabledChecks, check)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	s.inferredEnabledChecks = enabledChecks
 | |
| 	s.inferredEnabledChecksLowerCased = normalizeMap(s.inferredEnabledChecks)
 | |
| 	s.debugChecksFinalState()
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) buildEnabledAndDisabledByDefaultChecks() (enabled, disabled []string) {
 | |
| 	for _, info := range s.allCheckers {
 | |
| 		if enabledByDef := isEnabledByDefaultGoCriticChecker(info); enabledByDef {
 | |
| 			enabled = append(enabled, info.Name)
 | |
| 		} else {
 | |
| 			disabled = append(disabled, info.Name)
 | |
| 		}
 | |
| 	}
 | |
| 	return enabled, disabled
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) expandTagsToChecks(tags []string) []string {
 | |
| 	var checks []string
 | |
| 	for _, tag := range tags {
 | |
| 		checks = append(checks, s.allChecksByTag[tag]...)
 | |
| 	}
 | |
| 	return checks
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) debugChecksInitialState() {
 | |
| 	if !isGoCriticDebug {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	goCriticDebugf("All gocritic existing tags and checks:")
 | |
| 	for _, tag := range s.allTagsSorted {
 | |
| 		debugChecksListf(s.allChecksByTag[tag], "  tag %q", tag)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) debugChecksFinalState() {
 | |
| 	if !isGoCriticDebug {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var enabledChecks []string
 | |
| 	var disabledChecks []string
 | |
| 
 | |
| 	for _, checker := range s.allCheckers {
 | |
| 		name := checker.Name
 | |
| 		if s.inferredEnabledChecks.has(name) {
 | |
| 			enabledChecks = append(enabledChecks, name)
 | |
| 		} else {
 | |
| 			disabledChecks = append(disabledChecks, name)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	debugChecksListf(enabledChecks, "Final used")
 | |
| 
 | |
| 	if len(disabledChecks) == 0 {
 | |
| 		goCriticDebugf("All checks are enabled")
 | |
| 	} else {
 | |
| 		debugChecksListf(disabledChecks, "Final not used")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Validate tries to be consistent with (lintersdb.Validator).validateEnabledDisabledLintersConfig.
 | |
| func (s *goCriticSettingsWrapper) Validate() error {
 | |
| 	for _, v := range []func() error{
 | |
| 		s.validateOptionsCombinations,
 | |
| 		s.validateCheckerTags,
 | |
| 		s.validateCheckerNames,
 | |
| 		s.validateDisabledAndEnabledAtOneMoment,
 | |
| 		s.validateAtLeastOneCheckerEnabled,
 | |
| 	} {
 | |
| 		if err := v(); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) validateOptionsCombinations() error {
 | |
| 	if s.EnableAll {
 | |
| 		if s.DisableAll {
 | |
| 			return errors.New("enable-all and disable-all options must not be combined")
 | |
| 		}
 | |
| 
 | |
| 		if len(s.EnabledTags) != 0 {
 | |
| 			return errors.New("enable-all and enabled-tags options must not be combined")
 | |
| 		}
 | |
| 
 | |
| 		if len(s.EnabledChecks) != 0 {
 | |
| 			return errors.New("enable-all and enabled-checks options must not be combined")
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if s.DisableAll {
 | |
| 		if len(s.DisabledTags) != 0 {
 | |
| 			return errors.New("disable-all and disabled-tags options must not be combined")
 | |
| 		}
 | |
| 
 | |
| 		if len(s.DisabledChecks) != 0 {
 | |
| 			return errors.New("disable-all and disabled-checks options must not be combined")
 | |
| 		}
 | |
| 
 | |
| 		if len(s.EnabledTags) == 0 && len(s.EnabledChecks) == 0 {
 | |
| 			return errors.New("all checks were disabled, but no one check was enabled: at least one must be enabled")
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) validateCheckerTags() error {
 | |
| 	for _, tag := range s.EnabledTags {
 | |
| 		if !s.allChecksByTag.has(tag) {
 | |
| 			return fmt.Errorf("enabled tag %q doesn't exist, see %s's documentation", tag, goCriticName)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, tag := range s.DisabledTags {
 | |
| 		if !s.allChecksByTag.has(tag) {
 | |
| 			return fmt.Errorf("disabled tag %q doesn't exist, see %s's documentation", tag, goCriticName)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) validateCheckerNames() error {
 | |
| 	for _, name := range s.EnabledChecks {
 | |
| 		if !s.allChecks.has(name) {
 | |
| 			return fmt.Errorf("enabled check %q doesn't exist, see %s's documentation", name, goCriticName)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, name := range s.DisabledChecks {
 | |
| 		if !s.allChecks.has(name) {
 | |
| 			return fmt.Errorf("disabled check %q doesn't exist, see %s documentation", name, goCriticName)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for name := range s.SettingsPerCheck {
 | |
| 		lcName := strings.ToLower(name)
 | |
| 		if !s.allChecksLowerCased.has(lcName) {
 | |
| 			return fmt.Errorf("invalid check settings: check %q doesn't exist, see %s documentation", name, goCriticName)
 | |
| 		}
 | |
| 		if !s.inferredEnabledChecksLowerCased.has(lcName) {
 | |
| 			s.logger.Warnf("%s: settings were provided for disabled check %q", goCriticName, name)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) validateDisabledAndEnabledAtOneMoment() error {
 | |
| 	for _, tag := range s.DisabledTags {
 | |
| 		if slices.Contains(s.EnabledTags, tag) {
 | |
| 			return fmt.Errorf("tag %q disabled and enabled at one moment", tag)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, check := range s.DisabledChecks {
 | |
| 		if slices.Contains(s.EnabledChecks, check) {
 | |
| 			return fmt.Errorf("check %q disabled and enabled at one moment", check)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *goCriticSettingsWrapper) validateAtLeastOneCheckerEnabled() error {
 | |
| 	if len(s.inferredEnabledChecks) == 0 {
 | |
| 		return errors.New("eventually all checks were disabled: at least one must be enabled")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func normalizeMap[ValueT any](in map[string]ValueT) map[string]ValueT {
 | |
| 	ret := make(map[string]ValueT, len(in))
 | |
| 	for k, v := range in {
 | |
| 		ret[strings.ToLower(k)] = v
 | |
| 	}
 | |
| 	return ret
 | |
| }
 | |
| 
 | |
| func isEnabledByDefaultGoCriticChecker(info *gocriticlinter.CheckerInfo) bool {
 | |
| 	// https://github.com/go-critic/go-critic/blob/5b67cfd487ae9fe058b4b19321901b3131810f65/cmd/gocritic/check.go#L342-L345
 | |
| 	return !info.HasTag(gocriticlinter.ExperimentalTag) &&
 | |
| 		!info.HasTag(gocriticlinter.OpinionatedTag) &&
 | |
| 		!info.HasTag(gocriticlinter.PerformanceTag) &&
 | |
| 		!info.HasTag(gocriticlinter.SecurityTag)
 | |
| }
 | |
| 
 | |
| func debugChecksListf(checks []string, format string, args ...any) {
 | |
| 	if !isGoCriticDebug {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	goCriticDebugf("%s checks (%d): %s", fmt.Sprintf(format, args...), len(checks), sprintSortedStrings(checks))
 | |
| }
 | |
| 
 | |
| func sprintSortedStrings(v []string) string {
 | |
| 	sort.Strings(slices.Clone(v))
 | |
| 	return fmt.Sprint(v)
 | |
| }
 | 
