package golinters import ( "context" "fmt" "go/ast" "go/types" "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" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) type Gocritic struct{} func (Gocritic) Name() string { return "gocritic" } func (Gocritic) Desc() string { return "The most opinionated Go source code linter" } 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() { if !s.IsCheckEnabled(info.Name) { 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() { defer func() { if err := recover(); err != nil { panicErr = fmt.Errorf("panic occurred: %s", err) lintCtx.Log.Warnf("Panic: %s", debug.Stack()) } }() for _, pkgInfo := range lintCtx.Program.InitialPackages() { lintpackCtx.SetPackageInfo(&pkgInfo.Info, pkgInfo.Pkg) lint.runOnPackage(lintpackCtx, enabledCheckers, pkgInfo, issuesCh) } close(issuesCh) }() var res []result.Issue for i := range issuesCh { res = append(res, i) } if panicErr != nil { return nil, panicErr } return res, nil } func (lint Gocritic) runOnPackage(lintpackCtx *lintpack.Context, checkers []*lintpack.Checker, pkgInfo *loader.PackageInfo, ret chan<- result.Issue) { for _, f := range pkgInfo.Files { filename := filepath.Base(lintpackCtx.FileSet.Position(f.Pos()).Filename) lintpackCtx.SetFileInfo(filename, f) lint.runOnFile(lintpackCtx, f, checkers, ret) } } func (lint Gocritic) runOnFile(ctx *lintpack.Context, f *ast.File, checkers []*lintpack.Checker, ret chan<- result.Issue) { var wg sync.WaitGroup wg.Add(len(checkers)) for _, c := range checkers { // All checkers are expected to use *lint.Context // as read-only structure, so no copying is required. go func(c *lintpack.Checker) { defer wg.Done() for _, warn := range c.Check(f) { pos := ctx.FileSet.Position(warn.Node.Pos()) ret <- result.Issue{ Pos: pos, Text: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text), FromLinter: lint.Name(), } } }(c) } wg.Wait() }