diff --git a/pkg/config/config_gocritic.go b/pkg/config/config_gocritic.go index 939942d2..faf2a8bf 100644 --- a/pkg/config/config_gocritic.go +++ b/pkg/config/config_gocritic.go @@ -295,3 +295,11 @@ func (s *GocriticSettings) validateCheckerNames() error { return nil } + +func (s *GocriticSettings) GetLowercasedParams() map[string]GocriticCheckSettings { + ret := map[string]GocriticCheckSettings{} + for checker, params := range s.SettingsPerCheck { + ret[strings.ToLower(checker)] = params + } + return ret +} diff --git a/pkg/golinters/gocritic.go b/pkg/golinters/gocritic.go index 2f245723..669c57b3 100644 --- a/pkg/golinters/gocritic.go +++ b/pkg/golinters/gocritic.go @@ -8,8 +8,12 @@ import ( "path/filepath" "runtime" "runtime/debug" + "sort" + "strings" "sync" + "github.com/golangci/golangci-lint/pkg/config" + "github.com/go-lintpack/lintpack" "golang.org/x/tools/go/loader" @@ -27,11 +31,52 @@ func (Gocritic) Desc() string { return "The most opinionated Go source code linter" } -func (lint Gocritic) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { - sizes := types.SizesFor("gc", runtime.GOARCH) - lintpackCtx := lintpack.NewContext(lintCtx.Program.Fset, sizes) +func (Gocritic) normalizeCheckerInfoParams(info *lintpack.CheckerInfo) lintpack.CheckerParams { + // lowercase info param keys here because golangci-lint's config parser lowercases all strings + ret := lintpack.CheckerParams{} + for k, v := range info.Params { + ret[strings.ToLower(k)] = v + } + return ret +} + +func (lint Gocritic) configureCheckerInfo(info *lintpack.CheckerInfo, allParams map[string]config.GocriticCheckSettings) error { + params := allParams[strings.ToLower(info.Name)] + if params == nil { // no config for this checker + return nil + } + + infoParams := lint.normalizeCheckerInfoParams(info) + for k, p := range params { + v, ok := infoParams[k] + if ok { + v.Value = 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) + } + + var supportedKeys []string + for sk := range info.Params { + supportedKeys = append(supportedKeys, sk) + } + sort.Strings(supportedKeys) + + return fmt.Errorf("checker %s config param %s doesn't exist, all existing: %s", + info.Name, k, supportedKeys) + } + + return nil +} + +func (lint Gocritic) buildEnabledCheckers(lintCtx *linter.Context, lintpackCtx *lintpack.Context) ([]*lintpack.Checker, error) { s := lintCtx.Settings().Gocritic + allParams := s.GetLowercasedParams() var enabledCheckers []*lintpack.Checker for _, info := range lintpack.GetCheckersInfo() { @@ -39,10 +84,26 @@ func (lint Gocritic) Run(ctx context.Context, lintCtx *linter.Context) ([]result continue } + if err := lint.configureCheckerInfo(info, allParams); err != nil { + return nil, err + } + c := lintpack.NewChecker(lintpackCtx, info) enabledCheckers = append(enabledCheckers, c) } + return enabledCheckers, nil +} + +func (lint Gocritic) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { + sizes := types.SizesFor("gc", runtime.GOARCH) + lintpackCtx := lintpack.NewContext(lintCtx.Program.Fset, sizes) + + enabledCheckers, err := lint.buildEnabledCheckers(lintCtx, lintpackCtx) + if err != nil { + return nil, err + } + issuesCh := make(chan result.Issue, 1024) var panicErr error go func() { diff --git a/test/testdata/configs/gocritic.yml b/test/testdata/configs/gocritic.yml new file mode 100644 index 00000000..268f2fb9 --- /dev/null +++ b/test/testdata/configs/gocritic.yml @@ -0,0 +1,8 @@ +linters-settings: + gocritic: + enabled-checks: + - rangeValCopy + - flagDeref + settings: + rangevalcopy: + sizethreshold: 2 diff --git a/test/testdata/gocritic.go b/test/testdata/gocritic.go index 7ad771ae..1e48bd38 100644 --- a/test/testdata/gocritic.go +++ b/test/testdata/gocritic.go @@ -1,6 +1,31 @@ //args: -Egocritic +//config_path: testdata/configs/gocritic.yml package testdata -import "flag" +import ( + "flag" + "log" +) var _ = *flag.Bool("global1", false, "") // ERROR "flagDeref: immediate deref in \*flag.Bool\(.global1., false, ..\) is most likely an error; consider using flag\.BoolVar" + +type size1 struct { + a bool +} + +type size2 struct { + size1 + b bool +} + +func gocriticRangeValCopySize1(ss []size1) { + for _, s := range ss { + log.Print(s) + } +} + +func gocriticRangeValCopySize2(ss []size2) { + for _, s := range ss { // ERROR "rangeValCopy: each iteration copies 2 bytes.*" + log.Print(s) + } +}