package golinters import ( "context" "errors" "fmt" "strings" "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" "github.com/golangci/tools/go/ssa" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/packages" "github.com/golangci/golangci-lint/pkg/fsutils" "github.com/golangci/golangci-lint/pkg/lint/linter" libpackages "github.com/golangci/golangci-lint/pkg/packages" "github.com/golangci/golangci-lint/pkg/result" ) const megacheckName = "megacheck" type Megacheck struct { UnusedEnabled bool GosimpleEnabled bool StaticcheckEnabled bool } func (m Megacheck) Name() string { names := []string{} if m.UnusedEnabled { names = append(names, "unused") } if m.GosimpleEnabled { names = append(names, "gosimple") } if m.StaticcheckEnabled { names = append(names, "staticcheck") } if len(names) == 1 { return names[0] // only one sublinter is enabled } if len(names) == 3 { return megacheckName // all enabled } return fmt.Sprintf("megacheck.{%s}", strings.Join(names, ",")) } func (m Megacheck) Desc() string { descs := map[string]string{ "unused": "Checks Go code for unused constants, variables, functions and types", "gosimple": "Linter for Go source code that specializes in simplifying a code", "staticcheck": "Staticcheck is a go vet on steroids, applying a ton of static analysis checks", "megacheck": "3 sub-linters in one: unused, gosimple and staticcheck", } return descs[m.Name()] } func prettifyCompilationError(err packages.Error) error { i, _ := TypeCheck{}.parseError(err) if i == nil { return err } shortFilename, pathErr := fsutils.ShortestRelPath(i.Pos.Filename, "") if pathErr != nil { return err } errText := shortFilename if i.Line() != 0 { errText += fmt.Sprintf(":%d", i.Line()) } errText += fmt.Sprintf(": %s", i.Text) return errors.New(errText) } func (m Megacheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { if len(lintCtx.NotCompilingPackages) != 0 { var errPkgs []string var errors []packages.Error for _, p := range lintCtx.NotCompilingPackages { errPkgs = append(errPkgs, p.String()) errors = append(errors, libpackages.ExtractErrors(p)...) } warnText := fmt.Sprintf("Can't run megacheck because of compilation errors in packages %s", errPkgs) if len(errors) != 0 { warnText += fmt.Sprintf(": %s", prettifyCompilationError(errors[0])) if len(errors) > 1 { const runCmd = "golangci-lint run --no-config --disable-all -E typecheck" warnText += fmt.Sprintf(" and %d more errors: run `%s` to see all errors", len(errors)-1, runCmd) } } lintCtx.Log.Warnf("%s", warnText) // megacheck crashes if there are not compiling packages return nil, nil } issues := runMegacheck(lintCtx.Program, lintCtx.MegacheckSSAProgram, lintCtx.LoaderConfig, m.StaticcheckEnabled, m.GosimpleEnabled, m.UnusedEnabled, lintCtx.Settings().Unused.CheckExported) if len(issues) == 0 { return nil, nil } res := make([]result.Issue, 0, len(issues)) for _, i := range issues { res = append(res, result.Issue{ Pos: i.Position, Text: markIdentifiers(i.Text), FromLinter: m.Name(), }) } return res, nil } func runMegacheck(program *loader.Program, ssaProg *ssa.Program, conf *loader.Config, enableStaticcheck, enableGosimple, enableUnused, checkExportedUnused bool) []lint.Problem { var checkers []lintutil.CheckerConfig if enableStaticcheck { sac := staticcheck.NewChecker() checkers = append(checkers, lintutil.CheckerConfig{ Checker: sac, }) } if enableGosimple { sc := simple.NewChecker() checkers = append(checkers, lintutil.CheckerConfig{ Checker: sc, }) } if enableUnused { uc := unused.NewChecker(unused.CheckAll) uc.WholeProgram = checkExportedUnused uc.ConsiderReflection = true checkers = append(checkers, lintutil.CheckerConfig{ Checker: unused.NewLintChecker(uc), }) } fs := lintutil.FlagSet(megacheckName) return lintutil.ProcessFlagSet(checkers, fs, program, ssaProg, conf) }