
* update staticcheck Don't fork staticcheck: use the upstream version. Remove unneeded SSA loading. * Cache go/analysis facts Don't load unneeded packages for go/analysis. Repeated run of go/analysis linters now 10x faster (2s vs 20s on this repo) than before.
301 lines
7.2 KiB
Go
301 lines
7.2 KiB
Go
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
|
|
}
|