package golinters import ( "context" "fmt" "github.com/golangci/golangci-lint/pkg/logutils" "honnef.co/go/tools/unused" "honnef.co/go/tools/lint" "golang.org/x/tools/go/analysis" "honnef.co/go/tools/simple" "honnef.co/go/tools/staticcheck" "honnef.co/go/tools/stylecheck" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) const ( MegacheckParentName = "megacheck" MegacheckStaticcheckName = "staticcheck" MegacheckUnusedName = "unused" MegacheckGosimpleName = "gosimple" MegacheckStylecheckName = "stylecheck" ) var debugf = logutils.Debug("megacheck") type Staticcheck struct { megacheck } func NewStaticcheck() *Staticcheck { return &Staticcheck{ megacheck: megacheck{ staticcheckEnabled: true, }, } } func (Staticcheck) Name() string { return MegacheckStaticcheckName } func (Staticcheck) Desc() string { return "Staticcheck is a go vet on steroids, applying a ton of static analysis checks" } type Gosimple struct { megacheck } func NewGosimple() *Gosimple { return &Gosimple{ megacheck: megacheck{ gosimpleEnabled: true, }, } } func (Gosimple) Name() string { return MegacheckGosimpleName } func (Gosimple) Desc() string { return "Linter for Go source code that specializes in simplifying a code" } type Unused struct { megacheck } func NewUnused() *Unused { return &Unused{ megacheck: megacheck{ unusedEnabled: true, }, } } func (Unused) Name() string { return MegacheckUnusedName } func (Unused) Desc() string { return "Checks Go code for unused constants, variables, functions and types" } type Stylecheck struct { megacheck } func NewStylecheck() *Stylecheck { return &Stylecheck{ megacheck: megacheck{ stylecheckEnabled: true, }, } } func (Stylecheck) Name() string { return MegacheckStylecheckName } func (Stylecheck) Desc() string { return "Stylecheck is a replacement for golint" } type megacheck struct { unusedEnabled bool gosimpleEnabled bool staticcheckEnabled bool stylecheckEnabled bool } func (megacheck) Name() string { return MegacheckParentName } func (megacheck) Desc() string { return "" // shouldn't be called } func (m *megacheck) enableChildLinter(name string) error { switch name { case MegacheckStaticcheckName: m.staticcheckEnabled = true case MegacheckGosimpleName: m.gosimpleEnabled = true case MegacheckUnusedName: m.unusedEnabled = true case MegacheckStylecheckName: m.stylecheckEnabled = true default: return fmt.Errorf("invalid child linter name %s for metalinter %s", name, m.Name()) } return nil } type MegacheckMetalinter struct{} func (MegacheckMetalinter) Name() string { return MegacheckParentName } func (MegacheckMetalinter) BuildLinterConfig(enabledChildren []string) (*linter.Config, error) { var m megacheck for _, name := range enabledChildren { if err := m.enableChildLinter(name); err != nil { return nil, err } } // TODO: merge linter.Config and linter.Linter or refactor it in another way lc := &linter.Config{ Linter: m, EnabledByDefault: false, NeedsSSARepr: false, InPresets: []string{linter.PresetStyle, linter.PresetBugs, linter.PresetUnused}, Speed: 1, AlternativeNames: nil, OriginalURL: "", ParentLinterName: "", } if m.unusedEnabled { lc = lc.WithLoadDepsTypeInfo() } else { lc = lc.WithLoadForGoAnalysis() } return lc, nil } func (MegacheckMetalinter) DefaultChildLinterNames() []string { // no stylecheck here for backwards compatibility for users who enabled megacheck: don't enable extra // linter for them return []string{MegacheckStaticcheckName, MegacheckGosimpleName, MegacheckUnusedName} } func (m MegacheckMetalinter) AllChildLinterNames() []string { return append(m.DefaultChildLinterNames(), MegacheckStylecheckName) } func (m megacheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { // Use OriginalPackages not Packages because `unused` doesn't work properly // when we deduplicate normal and test packages. return m.runMegacheck(ctx, lintCtx) } func getAnalyzers(m map[string]*analysis.Analyzer) []*analysis.Analyzer { var ret []*analysis.Analyzer for _, v := range m { ret = append(ret, v) } return ret } func setGoVersion(analyzers []*analysis.Analyzer) { const goVersion = 13 // TODO for _, a := range analyzers { if v := a.Flags.Lookup("go"); v != nil { if err := v.Value.Set(fmt.Sprintf("1.%d", goVersion)); err != nil { debugf("Failed to set go version: %s", err) } } } } func (m megacheck) runMegacheck(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { var linters []linter.Linter if m.gosimpleEnabled { analyzers := getAnalyzers(simple.Analyzers) setGoVersion(analyzers) lnt := goanalysis.NewLinter(MegacheckGosimpleName, "", analyzers, nil) linters = append(linters, lnt) } if m.staticcheckEnabled { analyzers := getAnalyzers(staticcheck.Analyzers) setGoVersion(analyzers) lnt := goanalysis.NewLinter(MegacheckStaticcheckName, "", analyzers, nil) linters = append(linters, lnt) } if m.stylecheckEnabled { analyzers := getAnalyzers(stylecheck.Analyzers) setGoVersion(analyzers) lnt := goanalysis.NewLinter(MegacheckStylecheckName, "", analyzers, nil) linters = append(linters, lnt) } var u lint.CumulativeChecker if m.unusedEnabled { u = unused.NewChecker(lintCtx.Settings().Unused.CheckExported) analyzers := []*analysis.Analyzer{u.Analyzer()} setGoVersion(analyzers) lnt := goanalysis.NewLinter(MegacheckUnusedName, "", analyzers, nil) linters = append(linters, lnt) } if len(linters) == 0 { return nil, nil } var issues []result.Issue for _, lnt := range linters { i, err := lnt.Run(ctx, lintCtx) if err != nil { return nil, err } issues = append(issues, i...) } if u != nil { for _, ur := range u.Result() { p := u.ProblemObject(lintCtx.Packages[0].Fset, ur) issues = append(issues, result.Issue{ FromLinter: MegacheckUnusedName, Text: p.Message, Pos: p.Pos, }) } } return issues, nil } func (m megacheck) Analyzers() []*analysis.Analyzer { if m.unusedEnabled { // Don't treat this linter as go/analysis linter if unused is used // because it has non-standard API. return nil } var allAnalyzers []*analysis.Analyzer if m.gosimpleEnabled { allAnalyzers = append(allAnalyzers, getAnalyzers(simple.Analyzers)...) } if m.staticcheckEnabled { allAnalyzers = append(allAnalyzers, getAnalyzers(staticcheck.Analyzers)...) } if m.stylecheckEnabled { allAnalyzers = append(allAnalyzers, getAnalyzers(stylecheck.Analyzers)...) } setGoVersion(allAnalyzers) return allAnalyzers } func (megacheck) Cfg() map[string]map[string]interface{} { return nil } func (m megacheck) AnalyzerToLinterNameMapping() map[*analysis.Analyzer]string { ret := map[*analysis.Analyzer]string{} if m.gosimpleEnabled { for _, a := range simple.Analyzers { ret[a] = MegacheckGosimpleName } } if m.staticcheckEnabled { for _, a := range staticcheck.Analyzers { ret[a] = MegacheckStaticcheckName } } if m.stylecheckEnabled { for _, a := range stylecheck.Analyzers { ret[a] = MegacheckStylecheckName } } return ret }