197 lines
4.8 KiB
Go
197 lines
4.8 KiB
Go
package lint
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/build"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golangci/go-tools/ssa"
|
|
"github.com/golangci/go-tools/ssa/ssautil"
|
|
"github.com/golangci/golangci-lint/pkg/config"
|
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
|
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/tools/go/loader"
|
|
)
|
|
|
|
type Context struct {
|
|
Paths *fsutils.ProjectPaths
|
|
Cfg *config.Config
|
|
Program *loader.Program
|
|
SSAProgram *ssa.Program
|
|
LoaderConfig *loader.Config
|
|
ASTCache *astcache.Cache
|
|
NotCompilingPackages []*loader.PackageInfo
|
|
}
|
|
|
|
func (c *Context) Settings() *config.LintersSettings {
|
|
return &c.Cfg.LintersSettings
|
|
}
|
|
|
|
func isFullImportNeeded(linters []linter.Config) bool {
|
|
for _, linter := range linters {
|
|
if linter.NeedsProgramLoading() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func isSSAReprNeeded(linters []linter.Config) bool {
|
|
for _, linter := range linters {
|
|
if linter.NeedsSSARepresentation() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Run, paths *fsutils.ProjectPaths) (*loader.Program, *loader.Config, error) {
|
|
if !isFullImportNeeded(linters) {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
startedAt := time.Now()
|
|
defer func() {
|
|
logrus.Infof("Program loading took %s", time.Since(startedAt))
|
|
}()
|
|
|
|
bctx := build.Default
|
|
bctx.BuildTags = append(bctx.BuildTags, cfg.BuildTags...)
|
|
loadcfg := &loader.Config{
|
|
Build: &bctx,
|
|
AllowErrors: true, // Try to analyze event partially
|
|
}
|
|
rest, err := loadcfg.FromArgs(paths.MixedPaths(), cfg.AnalyzeTests)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("can't parepare load config with paths: %s", err)
|
|
}
|
|
if len(rest) > 0 {
|
|
return nil, nil, fmt.Errorf("unhandled loading paths: %v", rest)
|
|
}
|
|
|
|
prog, err := loadcfg.Load()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("can't load program from paths %v: %s", paths.MixedPaths(), err)
|
|
}
|
|
|
|
return prog, loadcfg, nil
|
|
}
|
|
|
|
func buildSSAProgram(ctx context.Context, lprog *loader.Program) *ssa.Program {
|
|
startedAt := time.Now()
|
|
defer func() {
|
|
logrus.Infof("SSA repr building took %s", time.Since(startedAt))
|
|
}()
|
|
|
|
ssaProg := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
|
ssaProg.Build()
|
|
return ssaProg
|
|
}
|
|
|
|
func discoverGoRoot() (string, error) {
|
|
goroot := os.Getenv("GOROOT")
|
|
if goroot != "" {
|
|
return goroot, nil
|
|
}
|
|
|
|
output, err := exec.Command("go", "env", "GOROOT").Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("can't execute go env GOROOT: %s", err)
|
|
}
|
|
|
|
return strings.TrimSpace(string(output)), nil
|
|
}
|
|
|
|
// separateNotCompilingPackages moves not compiling packages into separate slices:
|
|
// a lot of linters crash on such packages. Leave them only for those linters
|
|
// which can work with them.
|
|
func separateNotCompilingPackages(lintCtx *linter.Context) {
|
|
prog := lintCtx.Program
|
|
|
|
if prog.Created != nil {
|
|
compilingCreated := make([]*loader.PackageInfo, 0, len(prog.Created))
|
|
for _, info := range prog.Created {
|
|
if len(info.Errors) != 0 {
|
|
lintCtx.NotCompilingPackages = append(lintCtx.NotCompilingPackages, info)
|
|
} else {
|
|
compilingCreated = append(compilingCreated, info)
|
|
}
|
|
}
|
|
prog.Created = compilingCreated
|
|
}
|
|
|
|
if prog.Imported != nil {
|
|
for k, info := range prog.Imported {
|
|
if len(info.Errors) != 0 {
|
|
lintCtx.NotCompilingPackages = append(lintCtx.NotCompilingPackages, info)
|
|
delete(prog.Imported, k)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Config) (*linter.Context, error) {
|
|
// Set GOROOT to have working cross-compilation: cross-compiled binaries
|
|
// have invalid GOROOT. XXX: can't use runtime.GOROOT().
|
|
goroot, err := discoverGoRoot()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't discover GOROOT: %s", err)
|
|
}
|
|
os.Setenv("GOROOT", goroot)
|
|
build.Default.GOROOT = goroot
|
|
logrus.Infof("set GOROOT=%q", goroot)
|
|
|
|
args := cfg.Run.Args
|
|
if len(args) == 0 {
|
|
args = []string{"./..."}
|
|
}
|
|
|
|
paths, err := fsutils.GetPathsForAnalysis(ctx, args, cfg.Run.AnalyzeTests, cfg.Run.SkipDirs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prog, loaderConfig, err := loadWholeAppIfNeeded(ctx, linters, &cfg.Run, paths)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ssaProg *ssa.Program
|
|
if prog != nil && isSSAReprNeeded(linters) {
|
|
ssaProg = buildSSAProgram(ctx, prog)
|
|
}
|
|
|
|
var astCache *astcache.Cache
|
|
if prog != nil {
|
|
astCache, err = astcache.LoadFromProgram(prog)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
astCache = astcache.LoadFromFiles(paths.Files)
|
|
}
|
|
|
|
ret := &linter.Context{
|
|
Paths: paths,
|
|
Cfg: cfg,
|
|
Program: prog,
|
|
SSAProgram: ssaProg,
|
|
LoaderConfig: loaderConfig,
|
|
ASTCache: astCache,
|
|
}
|
|
|
|
if prog != nil {
|
|
separateNotCompilingPackages(ret)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|