diff --git a/pkg/commands/run.go b/pkg/commands/run.go index 92d417e6..2559c94d 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -14,6 +14,7 @@ import ( "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/lint" "github.com/golangci/golangci-lint/pkg/lint/lintersdb" + "github.com/golangci/golangci-lint/pkg/logutils" "github.com/golangci/golangci-lint/pkg/printers" "github.com/golangci/golangci-lint/pkg/result" "github.com/golangci/golangci-lint/pkg/result/processors" @@ -231,12 +232,14 @@ func setOutputToDevNull() (savedStdout, savedStderr *os.File) { } func (e *Executor) runAndPrint(ctx context.Context, args []string) error { - // Don't allow linters and loader to print anything - log.SetOutput(ioutil.Discard) - savedStdout, savedStderr := setOutputToDevNull() - defer func() { - os.Stdout, os.Stderr = savedStdout, savedStderr - }() + if !logutils.HaveDebugTag("linters_output") { + // Don't allow linters and loader to print anything + log.SetOutput(ioutil.Discard) + savedStdout, savedStderr := setOutputToDevNull() + defer func() { + os.Stdout, os.Stderr = savedStdout, savedStderr + }() + } issues, err := e.runAnalysis(ctx, args) if err != nil { diff --git a/pkg/golinters/gofmt.go b/pkg/golinters/gofmt.go index 4c9f6039..76822d3e 100644 --- a/pkg/golinters/gofmt.go +++ b/pkg/golinters/gofmt.go @@ -75,7 +75,6 @@ func (g Gofmt) extractIssuesFromPatch(patch string) ([]result.Issue, error) { for _, hunk := range d.Hunks { deletedLine, addedLine, err := getFirstDeletedAndAddedLineNumberInHunk(hunk) if err != nil { - logrus.Infof("Can't get first deleted line number for hunk: %s", err) if addedLine > 1 { deletedLine = addedLine - 1 // use previous line, TODO: use both prev and next lines } else { diff --git a/pkg/lint/load.go b/pkg/lint/load.go index a7d54fec..f97f2a7b 100644 --- a/pkg/lint/load.go +++ b/pkg/lint/load.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/golangci/golangci-lint/pkg/logutils" + "github.com/golangci/go-tools/ssa" "github.com/golangci/go-tools/ssa/ssautil" "github.com/golangci/golangci-lint/pkg/config" @@ -21,6 +23,8 @@ import ( "golang.org/x/tools/go/loader" ) +var loadDebugf = logutils.Debug("load") + func isFullImportNeeded(linters []linter.Config) bool { for _, linter := range linters { if linter.NeedsProgramLoading() { @@ -64,6 +68,89 @@ func normalizePaths(paths []string) ([]string, error) { return ret, nil } +func getCurrentProjectImportPath() (string, error) { + gopath := os.Getenv("GOPATH") + if gopath == "" { + return "", fmt.Errorf("no GOPATH env variable") + } + + wd, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("can't get workind directory: %s", err) + } + + if !strings.HasPrefix(wd, gopath) { + return "", fmt.Errorf("currently no in gopath: %q isn't a prefix of %q", gopath, wd) + } + + path := strings.TrimPrefix(wd, gopath) + path = strings.TrimPrefix(path, string(os.PathSeparator)) // if GOPATH contains separator at the end + src := "src" + string(os.PathSeparator) + if !strings.HasPrefix(path, src) { + return "", fmt.Errorf("currently no in gopath/src: %q isn't a prefix of %q", src, path) + } + + path = strings.TrimPrefix(path, src) + path = strings.Replace(path, string(os.PathSeparator), "/", -1) + return path, nil +} + +func isLocalProjectAnalysis(args []string) bool { + for _, arg := range args { + if strings.HasPrefix(arg, "..") || filepath.IsAbs(arg) { + return false + } + } + + return true +} + +func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *packages.Program) func(string) bool { + if !isLocalProjectAnalysis(cfg.Args) { + loadDebugf("analysis in nonlocal, don't optimize loading by not typechecking func bodies") + return nil + } + + if isSSAReprNeeded(linters) { + loadDebugf("ssa repr is needed, don't optimize loading by not typechecking func bodies") + return nil + } + + if len(pkgProg.Dirs()) == 0 { + // files run, in this mode packages are fake: can't check their path properly + return nil + } + + projPath, err := getCurrentProjectImportPath() + if err != nil { + logrus.Infof("can't get cur project path: %s", err) + return nil + } + + return func(path string) bool { + if strings.HasPrefix(path, ".") { + loadDebugf("%s: dot import: typecheck func bodies", path) + return true + } + + isLocalPath := strings.HasPrefix(path, projPath) + if isLocalPath { + localPath := strings.TrimPrefix(path, projPath) + localPath = strings.TrimPrefix(localPath, "/") + if strings.HasPrefix(localPath, "vendor/") { + loadDebugf("%s: local vendor import: DO NOT typecheck func bodies", path) + return false + } + + loadDebugf("%s: local import: typecheck func bodies", path) + return true + } + + loadDebugf("%s: not local import: DO NOT typecheck func bodies", path) + return false + } +} + func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Run, pkgProg *packages.Program) (*loader.Program, *loader.Config, error) { if !isFullImportNeeded(linters) { return nil, nil, nil @@ -76,9 +163,10 @@ func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *con bctx := pkgProg.BuildContext() loadcfg := &loader.Config{ - Build: bctx, - AllowErrors: true, // Try to analyze partially - ParserMode: parser.ParseComments, // AST will be reused by linters + Build: bctx, + AllowErrors: true, // Try to analyze partially + ParserMode: parser.ParseComments, // AST will be reused by linters + TypeCheckFuncBodies: getTypeCheckFuncBodies(cfg, linters, pkgProg), } var loaderArgs []string diff --git a/pkg/logutils/logutils.go b/pkg/logutils/logutils.go index affd07d5..94b2351e 100644 --- a/pkg/logutils/logutils.go +++ b/pkg/logutils/logutils.go @@ -51,3 +51,7 @@ func Debug(tag string) DebugFunc { func IsDebugEnabled() bool { return len(enabledDebugs) != 0 } + +func HaveDebugTag(tag string) bool { + return enabledDebugs[tag] +}