
Use deduplicated packages for all linters except megacheck. See https://github.com/golangci/golangci-lint/pull/585 for details.
304 lines
7.2 KiB
Go
304 lines
7.2 KiB
Go
package golinters
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/golangci/go-tools/config"
|
|
"github.com/golangci/go-tools/stylecheck"
|
|
|
|
"github.com/golangci/go-tools/lint"
|
|
"github.com/golangci/go-tools/lint/lintutil"
|
|
"github.com/golangci/go-tools/simple"
|
|
"github.com/golangci/go-tools/staticcheck"
|
|
"github.com/golangci/go-tools/unused"
|
|
"golang.org/x/tools/go/packages"
|
|
|
|
"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"
|
|
)
|
|
|
|
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
|
|
return &linter.Config{
|
|
Linter: m,
|
|
EnabledByDefault: false,
|
|
NeedsTypeInfo: true,
|
|
NeedsSSARepr: true,
|
|
InPresets: []string{linter.PresetStyle, linter.PresetBugs, linter.PresetUnused},
|
|
Speed: 1,
|
|
AlternativeNames: nil,
|
|
OriginalURL: "",
|
|
ParentLinterName: "",
|
|
}, 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 MegacheckMetalinter) isValidChild(name string) bool {
|
|
for _, child := range m.AllChildLinterNames() {
|
|
if child == name {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
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.
|
|
issues, err := m.runMegacheck(lintCtx.OriginalPackages, lintCtx.Settings().Unused.CheckExported)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to run megacheck")
|
|
}
|
|
|
|
if len(issues) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
res := make([]result.Issue, 0, len(issues))
|
|
meta := MegacheckMetalinter{}
|
|
for _, i := range issues {
|
|
if !meta.isValidChild(i.Checker) {
|
|
lintCtx.Log.Warnf("Bad megacheck checker name %q", i.Checker)
|
|
continue
|
|
}
|
|
|
|
res = append(res, result.Issue{
|
|
Pos: i.Position,
|
|
// TODO: use severity
|
|
Text: fmt.Sprintf("%s: %s", i.Check, i.Text),
|
|
FromLinter: i.Checker,
|
|
})
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (m megacheck) runMegacheck(workingPkgs []*packages.Package, checkExportedUnused bool) ([]lint.Problem, error) {
|
|
var checkers []lint.Checker
|
|
|
|
if m.gosimpleEnabled {
|
|
checkers = append(checkers, simple.NewChecker())
|
|
}
|
|
if m.staticcheckEnabled {
|
|
checkers = append(checkers, staticcheck.NewChecker())
|
|
}
|
|
if m.stylecheckEnabled {
|
|
checkers = append(checkers, stylecheck.NewChecker())
|
|
}
|
|
if m.unusedEnabled {
|
|
uc := unused.NewChecker(unused.CheckAll)
|
|
uc.ConsiderReflection = true
|
|
uc.WholeProgram = checkExportedUnused
|
|
checkers = append(checkers, unused.NewLintChecker(uc))
|
|
}
|
|
|
|
if len(checkers) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
cfg := config.Config{}
|
|
opts := &lintutil.Options{
|
|
// TODO: get current go version, but now it doesn't matter,
|
|
// may be needed after next updates of megacheck
|
|
GoVersion: 12,
|
|
|
|
Config: cfg,
|
|
// TODO: support Ignores option
|
|
}
|
|
|
|
return runMegacheckCheckers(checkers, workingPkgs, opts)
|
|
}
|
|
|
|
// parseIgnore is a copy from megacheck honnef.co/go/tools/lint/lintutil.parseIgnore
|
|
// just to not fork megacheck.
|
|
func parseIgnore(s string) ([]lint.Ignore, error) {
|
|
var out []lint.Ignore
|
|
if s == "" {
|
|
return nil, nil
|
|
}
|
|
for _, part := range strings.Fields(s) {
|
|
p := strings.Split(part, ":")
|
|
if len(p) != 2 {
|
|
return nil, errors.New("malformed ignore string")
|
|
}
|
|
path := p[0]
|
|
checks := strings.Split(p[1], ",")
|
|
out = append(out, &lint.GlobIgnore{Pattern: path, Checks: checks})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// runMegacheckCheckers is like megacheck honnef.co/go/tools/lint/lintutil.Lint,
|
|
// but takes a list of already-parsed packages instead of a list of
|
|
// package-paths to parse.
|
|
func runMegacheckCheckers(cs []lint.Checker, workingPkgs []*packages.Package, opt *lintutil.Options) ([]lint.Problem, error) {
|
|
stats := lint.PerfStats{
|
|
CheckerInits: map[string]time.Duration{},
|
|
}
|
|
|
|
if opt == nil {
|
|
opt = &lintutil.Options{}
|
|
}
|
|
ignores, err := parseIgnore(opt.Ignores)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// package-parsing elided here
|
|
stats.PackageLoading = 0
|
|
|
|
var problems []lint.Problem
|
|
// populating 'problems' with parser-problems elided here
|
|
|
|
if len(workingPkgs) == 0 {
|
|
return problems, nil
|
|
}
|
|
|
|
l := &lint.Linter{
|
|
Checkers: cs,
|
|
Ignores: ignores,
|
|
GoVersion: opt.GoVersion,
|
|
ReturnIgnored: opt.ReturnIgnored,
|
|
Config: opt.Config,
|
|
|
|
MaxConcurrentJobs: opt.MaxConcurrentJobs,
|
|
PrintStats: opt.PrintStats,
|
|
}
|
|
problems = append(problems, l.Lint(workingPkgs, &stats)...)
|
|
|
|
return problems, nil
|
|
}
|