diff --git a/.golangci.example.yml b/.golangci.example.yml index c0dafcb4b9ed..cbcd534eda1c 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -1,40 +1,98 @@ +# This file contains all available configuration options +# with their default values. + +# options for analysis running run: + # default concurrency is a available CPU number concurrency: 4 + + # timeout for analysis, e.g. 30s, 5m, default is 1m deadline: 1m + + # exit code when at least one issue was found, default is 1 issues-exit-code: 1 + + # include test files or not, default is true tests: true + + # list of build tags, all linters use it. Default is empty list. build-tags: - mytag + + # which dirs to skip: they won't be analyzed; + # can use regexp here: generated.*, regexp is applied on full path; + # default value is empty list, but next dirs are always skipped independently + # from this option's value: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ skip-dirs: - src/external_libs - autogenerated_by_my_lib + + # which files to skip: they will be analyzed, but issues from them + # won't be reported. Default value is empty list, but there is + # no need to include all autogenerated files, we confidently recognize + # autogenerated files. If it's not please let us know. skip-files: - - ".*\\.pb\\.go$" + - ".*\\.my\\.go$" - lib/bad.go +# output configuration options output: + # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" format: colored-line-number + + # print lines of code with issue, default is true print-issued-lines: true + + # print linter name in the end of issue text, default is true print-linter-name: true +# all available settings of specific linters linters-settings: errcheck: + # report about not checking of errors in type assetions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. check-type-assertions: false + + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. check-blank: false govet: + # report about shadowed variables check-shadowing: true + + # Obtain type information from installed (to $GOPATH/pkg) package files: + # golangci-lint will execute `go install -i` and `go test -i` for analyzed packages + # before analyzing them. + # By default this option is disabled and govet gets type information by loader from source code. + # Loading from source code is slow, but it's done only once for all linters. + # Go-installing of packages first time is much slower than loading them from source code, + # therefore this option is disabled by default. + # But repeated installation is fast in go >= 1.10 because of build caching. + # Enable this option only if all conditions are met: + # 1. you use only "fast" linters (--fast e.g.): no program loading occurs + # 2. you use go >= 1.10 + # 3. you do repeated runs (false for CI) or cache $GOPATH/pkg or `go env GOCACHE` dir in CI. + use-installed-packages: false golint: + # minimal confidence for issues, default is 0.8 min-confidence: 0.8 gofmt: + # simplify code: gofmt with `-s` option, true by default simplify: true gocyclo: + # minimal code complexity to report, 30 by default (but we recommend 10-20) min-complexity: 10 maligned: + # print struct with more effective memory layout or not, false by default suggest-new: true dupl: - threshold: 50 + # tokens count to trigger issue, 150 by default + threshold: 100 goconst: + # minimal length of string constant, 3 by default min-len: 3 + # minimal occurrences count to trigger, 3 by default min-occurrences: 3 depguard: list-type: blacklist @@ -45,8 +103,8 @@ linters-settings: linters: enable: - megacheck - - vet - enable-all: true + - govet + enable-all: false disable: maligned disable-all: false @@ -56,11 +114,35 @@ linters: fast: false issues: + # List of regexps of issue texts to exclude, empty list by default. + # But independently from this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. To list all + # excluded by default patterns execute `golangci-lint run --help` exclude: - abcdef + + # Independently from option `exclude` we use default exclude patterns, + # it can be disabled by this option. To list all + # excluded by default patterns execute `golangci-lint run --help`. + # Default value for this option is false. exclude-use-default: true + + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. max-per-linter: 0 + + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. max-same: 0 + + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing + # large codebase. It's not practical to fix all existing issues at the moment + # of integration: much better don't allow issues in new code. + # Default is false. new: false - new-from-rev: "" - new-from-patch: "" + + # Show only new issues created after git revision `REV` + new-from-rev: REV + + # Show only new issues created in git patch with set file path. + new-from-patch: path/to/patch/file \ No newline at end of file diff --git a/Gopkg.lock b/Gopkg.lock index c04e4ce70bff..21944a8d0535 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -122,7 +122,7 @@ "lib/cfg", "lib/whitelist" ] - revision = "1a9ab8120ec96a014df3afab2d6c47f7e12d8928" + revision = "18c83969a303d67eaa9d67747a930f007e3e9c89" [[projects]] branch = "master" diff --git a/Makefile b/Makefile index 687d414ab01f..ac1c08835484 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ test: - go install ./cmd/... # needed for govet and golint if go < 1.10 GL_TEST_RUN=1 golangci-lint run -v GL_TEST_RUN=1 golangci-lint run --fast --no-config -v GL_TEST_RUN=1 golangci-lint run --no-config -v diff --git a/README.md b/README.md index 5bb01b12d61d..ae244fe50574 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ GolangCI-Lint can be used with zero configuration. By default the following lint ``` $ golangci-lint linters Enabled by default linters: -govet: Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true] +govet: Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false] errcheck: Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false] staticcheck: Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false] unused: Checks Go code for unused constants, variables, functions and types [fast: false] @@ -310,10 +310,161 @@ To see which config file is being used and where it was sourced from run golangc Config options inside the file are identical to command-line options. You can configure specific linters' options only within the config file (not the command-line). -There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) example config file with all supported options. +There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) example +config file with all supported options, their description and default value: +```yaml +# This file contains all available configuration options +# with their default values. + +# options for analysis running +run: + # default concurrency is a available CPU number + concurrency: 4 + + # timeout for analysis, e.g. 30s, 5m, default is 1m + deadline: 1m + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + + # include test files or not, default is true + tests: true + + # list of build tags, all linters use it. Default is empty list. + build-tags: + - mytag + + # which dirs to skip: they won't be analyzed; + # can use regexp here: generated.*, regexp is applied on full path; + # default value is empty list, but next dirs are always skipped independently + # from this option's value: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + skip-dirs: + - src/external_libs + - autogenerated_by_my_lib + + # which files to skip: they will be analyzed, but issues from them + # won't be reported. Default value is empty list, but there is + # no need to include all autogenerated files, we confidently recognize + # autogenerated files. If it's not please let us know. + skip-files: + - ".*\\.my\\.go$" + - lib/bad.go + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" + format: colored-line-number + + # print lines of code with issue, default is true + print-issued-lines: true + + # print linter name in the end of issue text, default is true + print-linter-name: true + +# all available settings of specific linters +linters-settings: + errcheck: + # report about not checking of errors in type assetions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: false + + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: false + govet: + # report about shadowed variables + check-shadowing: true + + # Obtain type information from installed (to $GOPATH/pkg) package files: + # golangci-lint will execute `go install -i` and `go test -i` for analyzed packages + # before analyzing them. + # By default this option is disabled and govet gets type information by loader from source code. + # Loading from source code is slow, but it's done only once for all linters. + # Go-installing of packages first time is much slower than loading them from source code, + # therefore this option is disabled by default. + # But repeated installation is fast in go >= 1.10 because of build caching. + # Enable this option only if all conditions are met: + # 1. you use only "fast" linters (--fast e.g.): no program loading occurs + # 2. you use go >= 1.10 + # 3. you do repeated runs (false for CI) or cache $GOPATH/pkg or `go env GOCACHE` dir in CI. + use-installed-packages: false + golint: + # minimal confidence for issues, default is 0.8 + min-confidence: 0.8 + gofmt: + # simplify code: gofmt with `-s` option, true by default + simplify: true + gocyclo: + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 10 + maligned: + # print struct with more effective memory layout or not, false by default + suggest-new: true + dupl: + # tokens count to trigger issue, 150 by default + threshold: 100 + goconst: + # minimal length of string constant, 3 by default + min-len: 3 + # minimal occurrences count to trigger, 3 by default + min-occurrences: 3 + depguard: + list-type: blacklist + include-go-root: false + packages: + - github.com/davecgh/go-spew/spew + +linters: + enable: + - megacheck + - govet + enable-all: false + disable: + maligned + disable-all: false + presets: + - bugs + - unused + fast: false + +issues: + # List of regexps of issue texts to exclude, empty list by default. + # But independently from this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. To list all + # excluded by default patterns execute `golangci-lint run --help` + exclude: + - abcdef + + # Independently from option `exclude` we use default exclude patterns, + # it can be disabled by this option. To list all + # excluded by default patterns execute `golangci-lint run --help`. + # Default value for this option is false. + exclude-use-default: true + + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. + max-per-linter: 0 + + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-same: 0 + + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing + # large codebase. It's not practical to fix all existing issues at the moment + # of integration: much better don't allow issues in new code. + # Default is false. + new: false + + # Show only new issues created after git revision `REV` + new-from-rev: REV + + # Show only new issues created in git patch with set file path. + new-from-patch: path/to/patch/file +``` It's a [.golangci.yml](https://github.com/golangci/golangci-lint/blob/master/.golangci.yml) config file of this repo: we enable more linters -than the default and more strict settings: +than the default and have more strict settings: ```yaml linters-settings: govet: @@ -409,11 +560,11 @@ go install ./vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/ ``` Vendoring `golangci-lint` saves a network request, potentially making your CI system a little more reliable. -**`govet` or `golint` reports false-positives or false-negatives** +**Does I need to run `go install`?** -These linters obtain type information from installed package files. -The same issue you will encounter if will run `govet` or `golint` without `golangci-lint`. -Try `go install ./...` (or `go install ./cmd/...`: depends on the project) first. +No, you don't need to do it anymore. We will run `go install -i` and `go test -i` +for analyzed packages ourselves. We will run them only +if option `govet.use-installed-packages` is `true`. **`golangci-lint` doesn't work** diff --git a/README.md.tmpl b/README.md.tmpl index 620c3afa9dfa..345045d83878 100644 --- a/README.md.tmpl +++ b/README.md.tmpl @@ -200,10 +200,14 @@ To see which config file is being used and where it was sourced from run golangc Config options inside the file are identical to command-line options. You can configure specific linters' options only within the config file (not the command-line). -There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) example config file with all supported options. +There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) example +config file with all supported options, their description and default value: +```yaml +{{.GolangciYamlExample}} +``` It's a [.golangci.yml](https://github.com/golangci/golangci-lint/blob/master/.golangci.yml) config file of this repo: we enable more linters -than the default and more strict settings: +than the default and have more strict settings: ```yaml {{.GolangciYaml}} ``` @@ -275,11 +279,11 @@ go install ./vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/ ``` Vendoring `golangci-lint` saves a network request, potentially making your CI system a little more reliable. -**`govet` or `golint` reports false-positives or false-negatives** +**Does I need to run `go install`?** -These linters obtain type information from installed package files. -The same issue you will encounter if will run `govet` or `golint` without `golangci-lint`. -Try `go install ./...` (or `go install ./cmd/...`: depends on the project) first. +No, you don't need to do it anymore. We will run `go install -i` and `go test -i` +for analyzed packages ourselves. We will run them only +if option `govet.use-installed-packages` is `true`. **`golangci-lint` doesn't work** diff --git a/pkg/config/config.go b/pkg/config/config.go index 5423cd1ea4da..f0c7c3d8b4ce 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -119,7 +119,8 @@ type LintersSettings struct { CheckAssignToBlank bool `mapstructure:"check-blank"` } Govet struct { - CheckShadowing bool `mapstructure:"check-shadowing"` + CheckShadowing bool `mapstructure:"check-shadowing"` + UseInstalledPackages bool `mapstructure:"use-installed-packages"` } Golint struct { MinConfidence float64 `mapstructure:"min-confidence"` diff --git a/pkg/golinters/govet.go b/pkg/golinters/govet.go index 768138d4753e..f5dbe460876a 100644 --- a/pkg/golinters/govet.go +++ b/pkg/golinters/govet.go @@ -2,9 +2,19 @@ package golinters import ( "context" + "fmt" + "go/ast" + "go/token" + "os" + "os/exec" + "strings" + "time" + "github.com/golangci/golangci-lint/pkg/goutils" "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/logutils" "github.com/golangci/golangci-lint/pkg/result" + "github.com/golangci/golangci-lint/pkg/timeutils" govetAPI "github.com/golangci/govet" ) @@ -19,15 +29,20 @@ func (Govet) Desc() string { } func (g Govet) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { - // TODO: check .S asm files: govet can do it if pass dirs var govetIssues []govetAPI.Issue - for _, pkg := range lintCtx.PkgProgram.Packages() { - issues, err := govetAPI.Run(pkg.Files(lintCtx.Cfg.Run.AnalyzeTests), lintCtx.Settings().Govet.CheckShadowing) + var err error + if lintCtx.Settings().Govet.UseInstalledPackages { + govetIssues, err = g.runOnInstalledPackages(ctx, lintCtx) if err != nil { - return nil, err + return nil, fmt.Errorf("can't run govet on installed packages: %s", err) + } + } else { + govetIssues, err = g.runOnSourcePackages(ctx, lintCtx) + if err != nil { + return nil, fmt.Errorf("can't run govet on source packages: %s", err) } - govetIssues = append(govetIssues, issues...) } + if len(govetIssues) == 0 { return nil, nil } @@ -42,3 +57,172 @@ func (g Govet) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue } return res, nil } + +func (g Govet) runOnInstalledPackages(ctx context.Context, lintCtx *linter.Context) ([]govetAPI.Issue, error) { + if err := g.installPackages(ctx, lintCtx); err != nil { + return nil, fmt.Errorf("can't install packages (it's required for govet): %s", err) + } + + // TODO: check .S asm files: govet can do it if pass dirs + var govetIssues []govetAPI.Issue + for _, pkg := range lintCtx.PkgProgram.Packages() { + var astFiles []*ast.File + var fset *token.FileSet + for _, fname := range pkg.Files(lintCtx.Cfg.Run.AnalyzeTests) { + af := lintCtx.ASTCache.Get(fname) + if af == nil || af.Err != nil { + return nil, fmt.Errorf("can't get parsed file %q from ast cache: %#v", fname, af) + } + astFiles = append(astFiles, af.F) + fset = af.Fset + } + issues, err := govetAPI.Analyze(astFiles, fset, nil, + lintCtx.Settings().Govet.CheckShadowing) + if err != nil { + return nil, err + } + govetIssues = append(govetIssues, issues...) + } + + return govetIssues, nil +} + +func (g Govet) installPackages(ctx context.Context, lintCtx *linter.Context) error { + inGoRoot, err := goutils.InGoRoot() + if err != nil { + return fmt.Errorf("can't check whether we are in $GOROOT: %s", err) + } + + if inGoRoot { + // Go source packages already should be installed into $GOROOT/pkg with go distribution + lintCtx.Log.Infof("In $GOROOT, don't install packages") + return nil + } + + if err := g.installNonTestPackages(ctx, lintCtx); err != nil { + return err + } + + if err := g.installTestDependencies(ctx, lintCtx); err != nil { + return err + } + + return nil +} + +func (g Govet) installTestDependencies(ctx context.Context, lintCtx *linter.Context) error { + log := lintCtx.Log + packages := lintCtx.PkgProgram.Packages() + var testDirs []string + for _, pkg := range packages { + dir := pkg.Dir() + if dir == "" { + log.Warnf("Package %#v has empty dir", pkg) + continue + } + + if !strings.HasPrefix(dir, ".") { + // go install can't work without that + dir = "./" + dir + } + + if len(pkg.TestFiles()) != 0 { + testDirs = append(testDirs, dir) + } + } + + if len(testDirs) == 0 { + log.Infof("No test files in packages %#v", packages) + return nil + } + + args := append([]string{"test", "-i"}, testDirs...) + return runGoCommand(ctx, log, args...) +} + +func (g Govet) installNonTestPackages(ctx context.Context, lintCtx *linter.Context) error { + log := lintCtx.Log + packages := lintCtx.PkgProgram.Packages() + var importPaths []string + for _, pkg := range packages { + if pkg.IsTestOnly() { + // test-only package will be processed by installTestDependencies + continue + } + + dir := pkg.Dir() + if dir == "" { + log.Warnf("Package %#v has empty dir", pkg) + continue + } + + if !strings.HasPrefix(dir, ".") { + // go install can't work without that + dir = "./" + dir + } + + importPaths = append(importPaths, dir) + } + + if len(importPaths) == 0 { + log.Infof("No packages to install, all packages: %#v", packages) + return nil + } + + // we need type information of dependencies of analyzed packages + // so we pass -i option to install it + if err := runGoInstall(ctx, log, importPaths, true); err != nil { + // try without -i option: go < 1.10 doesn't support this option + // and install dependencies by default. + return runGoInstall(ctx, log, importPaths, false) + } + + return nil +} + +func runGoInstall(ctx context.Context, log logutils.Log, importPaths []string, withIOption bool) error { + args := []string{"install"} + if withIOption { + args = append(args, "-i") + } + args = append(args, importPaths...) + + return runGoCommand(ctx, log, args...) +} + +func runGoCommand(ctx context.Context, log logutils.Log, args ...string) error { + argsStr := strings.Join(args, " ") + defer timeutils.Track(time.Now(), log, "go %s", argsStr) + + cmd := exec.CommandContext(ctx, "go", args...) + cmd.Env = append([]string{}, os.Environ()...) + cmd.Env = append(cmd.Env, "GOMAXPROCS=1") // don't consume more than 1 cpu + + // use .Output but not .Run to capture StdErr in err + _, err := cmd.Output() + if err != nil { + var stderr string + if ee, ok := err.(*exec.ExitError); ok && ee.Stderr != nil { + stderr = ": " + string(ee.Stderr) + } + + return fmt.Errorf("can't run [go %s]: %s%s", argsStr, err, stderr) + } + + return nil +} + +func (g Govet) runOnSourcePackages(ctx context.Context, lintCtx *linter.Context) ([]govetAPI.Issue, error) { + // TODO: check .S asm files: govet can do it if pass dirs + var govetIssues []govetAPI.Issue + for _, pkg := range lintCtx.Program.InitialPackages() { + issues, err := govetAPI.Analyze(pkg.Files, lintCtx.Program.Fset, pkg, + lintCtx.Settings().Govet.CheckShadowing) + if err != nil { + return nil, err + } + govetIssues = append(govetIssues, issues...) + } + + return govetIssues, nil +} diff --git a/pkg/goutils/goutils.go b/pkg/goutils/goutils.go new file mode 100644 index 000000000000..7bd5bb3a9766 --- /dev/null +++ b/pkg/goutils/goutils.go @@ -0,0 +1,50 @@ +package goutils + +import ( + "fmt" + "os" + "os/exec" + "strings" + "sync" +) + +var discoverGoRootOnce sync.Once +var discoveredGoRoot string +var discoveredGoRootError error + +func DiscoverGoRoot() (string, error) { + discoverGoRootOnce.Do(func() { + discoveredGoRoot, discoveredGoRootError = discoverGoRootImpl() + }) + + return discoveredGoRoot, discoveredGoRootError +} + +func discoverGoRootImpl() (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 +} + +func InGoRoot() (bool, error) { + goroot, err := DiscoverGoRoot() + if err != nil { + return false, err + } + + wd, err := os.Getwd() + if err != nil { + return false, err + } + + // TODO: strip, then add slashes + return strings.HasPrefix(wd, goroot), nil +} diff --git a/pkg/lint/astcache/astcache.go b/pkg/lint/astcache/astcache.go index 15b9d1526fe6..209c73c74e0d 100644 --- a/pkg/lint/astcache/astcache.go +++ b/pkg/lint/astcache/astcache.go @@ -62,10 +62,8 @@ func (c *Cache) prepareValidFiles() { c.s = files } -func LoadFromProgram(prog *loader.Program) (*Cache, error) { - c := &Cache{ - m: map[string]*File{}, - } +func LoadFromProgram(prog *loader.Program, log logutils.Log) (*Cache, error) { + c := NewCache(log) root, err := os.Getwd() if err != nil { @@ -115,10 +113,8 @@ func (c *Cache) parseFile(filePath string, fset *token.FileSet) { } } -func LoadFromFiles(files []string) (*Cache, error) { - c := &Cache{ - m: map[string]*File{}, - } +func LoadFromFiles(files []string, log logutils.Log) (*Cache, error) { + c := NewCache(log) fset := token.NewFileSet() for _, filePath := range files { diff --git a/pkg/lint/lintersdb/lintersdb.go b/pkg/lint/lintersdb/lintersdb.go index 4f35fd66e3e8..dc978936d408 100644 --- a/pkg/lint/lintersdb/lintersdb.go +++ b/pkg/lint/lintersdb/lintersdb.go @@ -57,6 +57,7 @@ func enableLinterConfigs(lcs []linter.Config, isEnabled func(lc *linter.Config) func GetAllSupportedLinterConfigs() []linter.Config { lcs := []linter.Config{ linter.NewConfig(golinters.Govet{}). + WithFullImport(). // TODO: depend on it's configuration here WithPresets(linter.PresetBugs). WithSpeed(4). WithURL("https://golang.org/cmd/vet/"), diff --git a/pkg/lint/load.go b/pkg/lint/load.go index 9f089f5e0bf7..bc899ea783eb 100644 --- a/pkg/lint/load.go +++ b/pkg/lint/load.go @@ -6,11 +6,11 @@ import ( "go/build" "go/parser" "os" - "os/exec" "path/filepath" "strings" "time" + "github.com/golangci/golangci-lint/pkg/goutils" "github.com/golangci/golangci-lint/pkg/logutils" "github.com/golangci/go-tools/ssa" @@ -24,9 +24,14 @@ import ( var loadDebugf = logutils.Debug("load") -func isFullImportNeeded(linters []linter.Config) bool { +func isFullImportNeeded(linters []linter.Config, cfg *config.Config) bool { for _, linter := range linters { if linter.NeedsProgramLoading() { + if linter.Linter.Name() == "govet" && cfg.LintersSettings.Govet.UseInstalledPackages { + // TODO: remove this hack + continue + } + return true } } @@ -150,8 +155,8 @@ func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *p } } -func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Run, pkgProg *packages.Program, log logutils.Log) (*loader.Program, *loader.Config, error) { - if !isFullImportNeeded(linters) { +func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Config, pkgProg *packages.Program, log logutils.Log) (*loader.Program, *loader.Config, error) { + if !isFullImportNeeded(linters, cfg) { return nil, nil, nil } @@ -165,7 +170,7 @@ func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *con Build: bctx, AllowErrors: true, // Try to analyze partially ParserMode: parser.ParseComments, // AST will be reused by linters - TypeCheckFuncBodies: getTypeCheckFuncBodies(cfg, linters, pkgProg, log), + TypeCheckFuncBodies: getTypeCheckFuncBodies(&cfg.Run, linters, pkgProg, log), } var loaderArgs []string @@ -173,7 +178,7 @@ func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *con if len(dirs) != 0 { loaderArgs = dirs // dirs run } else { - loaderArgs = pkgProg.Files(cfg.AnalyzeTests) // files run + loaderArgs = pkgProg.Files(cfg.Run.AnalyzeTests) // files run } nLoaderArgs, err := normalizePaths(loaderArgs) @@ -181,7 +186,7 @@ func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *con return nil, nil, err } - rest, err := loadcfg.FromArgs(nLoaderArgs, cfg.AnalyzeTests) + rest, err := loadcfg.FromArgs(nLoaderArgs, cfg.Run.AnalyzeTests) if err != nil { return nil, nil, fmt.Errorf("can't parepare load config with paths: %s", err) } @@ -217,20 +222,6 @@ func buildSSAProgram(ctx context.Context, lprog *loader.Program, log logutils.Lo 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. @@ -267,7 +258,7 @@ func separateNotCompilingPackages(lintCtx *linter.Context) { func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Config, log logutils.Log) (*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() + goroot, err := goutils.DiscoverGoRoot() if err != nil { return nil, fmt.Errorf("can't discover GOROOT: %s", err) } @@ -291,7 +282,7 @@ func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Confi return nil, err } - prog, loaderConfig, err := loadWholeAppIfNeeded(ctx, linters, &cfg.Run, pkgProg, log) + prog, loaderConfig, err := loadWholeAppIfNeeded(ctx, linters, cfg, pkgProg, log) if err != nil { return nil, err } @@ -301,11 +292,12 @@ func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Confi ssaProg = buildSSAProgram(ctx, prog, log) } + astLog := log.Child("astcache") var astCache *astcache.Cache if prog != nil { - astCache, err = astcache.LoadFromProgram(prog) + astCache, err = astcache.LoadFromProgram(prog, astLog) } else { - astCache, err = astcache.LoadFromFiles(pkgProg.Files(cfg.Run.AnalyzeTests)) + astCache, err = astcache.LoadFromFiles(pkgProg.Files(cfg.Run.AnalyzeTests), astLog) } if err != nil { return nil, err diff --git a/pkg/lint/load_test.go b/pkg/lint/load_test.go index eaaeb700a802..b0f5d87eff53 100644 --- a/pkg/lint/load_test.go +++ b/pkg/lint/load_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/golangci/golangci-lint/pkg/golinters" "github.com/golangci/golangci-lint/pkg/logutils" "github.com/golangci/golangci-lint/pkg/config" @@ -16,12 +17,13 @@ import ( func TestASTCacheLoading(t *testing.T) { ctx := context.Background() linters := []linter.Config{ - linter.NewConfig(nil).WithFullImport(), + linter.NewConfig(golinters.Errcheck{}).WithFullImport(), } inputPaths := []string{"./...", "./", "./load.go", "load.go"} + log := logutils.NewStderrLog("") for _, inputPath := range inputPaths { - r, err := packages.NewResolver(nil, nil, logutils.NewStderrLog("")) + r, err := packages.NewResolver(nil, nil, log) assert.NoError(t, err) pkgProg, err := r.Resolve(inputPath) @@ -30,15 +32,18 @@ func TestASTCacheLoading(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, pkgProg.Files(true)) - prog, _, err := loadWholeAppIfNeeded(ctx, linters, &config.Run{ - AnalyzeTests: true, - }, pkgProg, logutils.NewStderrLog("")) + cfg := &config.Config{ + Run: config.Run{ + AnalyzeTests: true, + }, + } + prog, _, err := loadWholeAppIfNeeded(ctx, linters, cfg, pkgProg, logutils.NewStderrLog("")) assert.NoError(t, err) - astCacheFromProg, err := astcache.LoadFromProgram(prog) + astCacheFromProg, err := astcache.LoadFromProgram(prog, log) assert.NoError(t, err) - astCacheFromFiles, err := astcache.LoadFromFiles(pkgProg.Files(true)) + astCacheFromFiles, err := astcache.LoadFromFiles(pkgProg.Files(true), log) assert.NoError(t, err) filesFromProg := astCacheFromProg.GetAllValidFiles() diff --git a/pkg/lint/runner.go b/pkg/lint/runner.go index 1f7a90f4c20b..ea313fc66c96 100644 --- a/pkg/lint/runner.go +++ b/pkg/lint/runner.go @@ -75,7 +75,7 @@ func (r Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context, lc l }() specificLintCtx := *lintCtx - specificLintCtx.Log = lintCtx.Log.Child(lc.Linter.Name()) + specificLintCtx.Log = r.Log.Child(lc.Linter.Name()) issues, err := lc.Linter.Run(ctx, &specificLintCtx) if err != nil { return nil, err diff --git a/pkg/packages/package.go b/pkg/packages/package.go index 8dfae0ff4a6c..fa938bd55bed 100644 --- a/pkg/packages/package.go +++ b/pkg/packages/package.go @@ -9,6 +9,7 @@ type Package struct { bp *build.Package isFake bool + dir string // dir != bp.dir only if isFake == true } func (pkg *Package) Files(includeTest bool) []string { @@ -16,8 +17,7 @@ func (pkg *Package) Files(includeTest bool) []string { // TODO: add cgo files if includeTest { - pkgFiles = append(pkgFiles, pkg.bp.TestGoFiles...) - pkgFiles = append(pkgFiles, pkg.bp.XTestGoFiles...) + pkgFiles = append(pkgFiles, pkg.TestFiles()...) } for i, f := range pkgFiles { @@ -26,3 +26,22 @@ func (pkg *Package) Files(includeTest bool) []string { return pkgFiles } + +func (pkg *Package) Dir() string { + if pkg.dir != "" { // for fake packages + return pkg.dir + } + + return pkg.bp.Dir +} + +func (pkg *Package) IsTestOnly() bool { + return len(pkg.bp.GoFiles) == 0 +} + +func (pkg *Package) TestFiles() []string { + var pkgFiles []string + pkgFiles = append(pkgFiles, pkg.bp.TestGoFiles...) + pkgFiles = append(pkgFiles, pkg.bp.XTestGoFiles...) + return pkgFiles +} diff --git a/pkg/packages/program.go b/pkg/packages/program.go index d268994446d4..638a36e1f797 100644 --- a/pkg/packages/program.go +++ b/pkg/packages/program.go @@ -63,7 +63,7 @@ func (p *Program) Dirs() []string { var ret []string for _, pkg := range p.packages { if !pkg.isFake { - ret = append(ret, pkg.bp.Dir) + ret = append(ret, pkg.Dir()) } } diff --git a/pkg/packages/resolver.go b/pkg/packages/resolver.go index d224e665cee7..f281c59ed285 100644 --- a/pkg/packages/resolver.go +++ b/pkg/packages/resolver.go @@ -138,9 +138,11 @@ func (r Resolver) addFakePackage(filePath string, prog *Program) { // do it. p := Package{ bp: &build.Package{ + // TODO: detect is it test file or not: without that we can't analyze only one test file GoFiles: []string{filePath}, }, isFake: true, + dir: filepath.Dir(filePath), } prog.addPackage(&p) } diff --git a/pkg/result/processors/max_same_issues.go b/pkg/result/processors/max_same_issues.go index b59b7a3227da..fe3c2b22a097 100644 --- a/pkg/result/processors/max_same_issues.go +++ b/pkg/result/processors/max_same_issues.go @@ -57,7 +57,10 @@ type kv struct { func walkStringToIntMapSortedByValue(m map[string]int, walk func(k string, v int)) { var ss []kv for k, v := range m { - ss = append(ss, kv{k, v}) + ss = append(ss, kv{ + Key: k, + Value: v, + }) } sort.Slice(ss, func(i, j int) bool { diff --git a/scripts/gen_readme/main.go b/scripts/gen_readme/main.go index eb207ca5f19d..51a3eae9302d 100644 --- a/scripts/gen_readme/main.go +++ b/scripts/gen_readme/main.go @@ -48,6 +48,11 @@ func buildTemplateContext() (map[string]interface{}, error) { return nil, fmt.Errorf("can't read .golangci.yml: %s", err) } + golangciYamlExample, err := ioutil.ReadFile(".golangci.example.yml") + if err != nil { + return nil, fmt.Errorf("can't read .golangci.example.yml: %s", err) + } + if err = exec.Command("go", "install", "./cmd/...").Run(); err != nil { return nil, fmt.Errorf("can't run go install: %s", err) } @@ -72,6 +77,7 @@ func buildTemplateContext() (map[string]interface{}, error) { return map[string]interface{}{ "GolangciYaml": string(golangciYaml), + "GolangciYamlExample": string(golangciYamlExample), "LintersCommandOutputEnabledOnly": string(lintersOutParts[0]), "LintersCommandOutputDisabledOnly": string(lintersOutParts[1]), "EnabledByDefaultLinters": getLintersListMarkdown(true), diff --git a/test/run_test.go b/test/run_test.go index fa9647f2091e..83f4a7d4d476 100644 --- a/test/run_test.go +++ b/test/run_test.go @@ -19,8 +19,11 @@ import ( "github.com/stretchr/testify/assert" ) +var root = filepath.Join("..", "...") var installOnce sync.Once +const noIssuesOut = "Congrats! No issues were found.\n" + func installBinary(t assert.TestingT) { installOnce.Do(func() { cmd := exec.Command("go", "install", filepath.Join("..", "cmd", binName)) @@ -30,11 +33,11 @@ func installBinary(t assert.TestingT) { func checkNoIssuesRun(t *testing.T, out string, exitCode int) { assert.Equal(t, exitcodes.Success, exitCode) - assert.Equal(t, "Congrats! No issues were found.\n", out) + assert.Equal(t, noIssuesOut, out) } func TestCongratsMessageIfNoIssues(t *testing.T) { - out, exitCode := runGolangciLint(t, "../...") + out, exitCode := runGolangciLint(t, root) checkNoIssuesRun(t, out, exitCode) } @@ -60,7 +63,7 @@ func TestRunOnAbsPath(t *testing.T) { } func TestDeadline(t *testing.T) { - out, exitCode := runGolangciLint(t, "--deadline=1ms", filepath.Join("..", "...")) + out, exitCode := runGolangciLint(t, "--deadline=1ms", root) assert.Equal(t, exitcodes.Timeout, exitCode) assert.Contains(t, out, "deadline exceeded: try increase it by passing --deadline option") assert.NotContains(t, out, "Congrats! No issues were found.") @@ -337,6 +340,15 @@ func TestEnabledLinters(t *testing.T) { } } +func TestGovetInFastMode(t *testing.T) { + cfg := ` + linters-settings: + use-installed-packages: true + ` + out := runGolangciLintWithYamlConfig(t, cfg, "--fast", "-Egovet", root) + assert.Equal(t, noIssuesOut, out) +} + func TestEnabledPresetsAreNotDuplicated(t *testing.T) { out, ec := runGolangciLint(t, "--no-config", "-v", "-p", "style,bugs") assert.Equal(t, exitcodes.Success, ec) diff --git a/vendor/github.com/golangci/govet/golangci.go b/vendor/github.com/golangci/govet/golangci.go index c602876b7bea..a8b4ea457f04 100644 --- a/vendor/github.com/golangci/govet/golangci.go +++ b/vendor/github.com/golangci/govet/golangci.go @@ -1,8 +1,11 @@ package govet import ( + "go/ast" "go/token" "strings" + + "golang.org/x/tools/go/loader" ) type Issue struct { @@ -12,8 +15,9 @@ type Issue struct { var foundIssues []Issue -func Run(files []string, checkShadowing bool) ([]Issue, error) { +func Analyze(files []*ast.File, fset *token.FileSet, pkgInfo *loader.PackageInfo, checkShadowing bool) ([]Issue, error) { foundIssues = nil + *source = false // import type data for "fmt" from installed packages if checkShadowing { experimental["shadow"] = false @@ -28,12 +32,18 @@ func Run(files []string, checkShadowing bool) ([]Issue, error) { initUnusedFlags() filesRun = true - for _, name := range files { + for _, f := range files { + name := fset.Position(f.Pos()).Filename if !strings.HasSuffix(name, "_test.go") { includesNonTest = true } } - if doPackage(files, nil) == nil { + pkg, err := doPackage(nil, pkgInfo, fset, files) + if err != nil { + return nil, err + } + + if pkg == nil { return nil, nil } diff --git a/vendor/github.com/golangci/govet/main.go b/vendor/github.com/golangci/govet/main.go index c7e2afc1a970..dea3ed719df3 100644 --- a/vendor/github.com/golangci/govet/main.go +++ b/vendor/github.com/golangci/govet/main.go @@ -14,7 +14,6 @@ import ( "go/ast" "go/build" "go/importer" - "go/parser" "go/printer" "go/token" "go/types" @@ -24,12 +23,14 @@ import ( "path/filepath" "strconv" "strings" + + "golang.org/x/tools/go/loader" ) // Important! If you add flags here, make sure to update cmd/go/internal/vet/vetflag.go. var ( - verbose = flag.Bool("v", false, "verbose") + verbose = flag.Bool("v", true, "verbose") source = flag.Bool("source", false, "import from source instead of compiled object files") tags = flag.String("tags", "", "space-separated list of build tags to apply when parsing") tagList = []string{} // exploded version of tags flag; set in main @@ -271,7 +272,7 @@ func main() { } os.Exit(exitCode) } - if doPackage(flag.Args(), nil) == nil { + if pkg, _ := doPackage(nil, nil, nil, nil); pkg == nil { warnf("no files checked") } os.Exit(exitCode) @@ -344,7 +345,7 @@ func doPackageCfg(cfgFile string) { stdImporter = &vcfg inittypes() mustTypecheck = true - doPackage(vcfg.GoFiles, nil) + doPackage(nil, nil, nil, nil) } // doPackageDir analyzes the single package found in the directory, if there is one, @@ -372,12 +373,12 @@ func doPackageDir(directory string) { names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. names = append(names, pkg.SFiles...) prefixDirectory(directory, names) - basePkg := doPackage(names, nil) + basePkg, _ := doPackage(nil, nil, nil, nil) // Is there also a "foo_test" package? If so, do that one as well. if len(pkg.XTestGoFiles) > 0 { names = pkg.XTestGoFiles prefixDirectory(directory, names) - doPackage(names, basePkg) + doPackage(basePkg, nil, nil, nil) } } @@ -392,58 +393,73 @@ type Package struct { typesPkg *types.Package } +func shortestRelPath(path string, wd string) (string, error) { + if wd == "" { // get it if user don't have cached working dir + var err error + wd, err = os.Getwd() + if err != nil { + return "", fmt.Errorf("can't get working directory: %s", err) + } + } + + // make path absolute and then relative to be able to fix this case: + // we'are in /test dir, we want to normalize ../test, and have file file.go in this dir; + // it must have normalized path file.go, not ../test/file.go, + var absPath string + if filepath.IsAbs(path) { + absPath = path + } else { + absPath = filepath.Join(wd, path) + } + + relPath, err := filepath.Rel(wd, absPath) + if err != nil { + return "", fmt.Errorf("can't get relative path for path %s and root %s: %s", + absPath, wd, err) + } + + return relPath, nil +} + // doPackage analyzes the single package constructed from the named files. // It returns the parsed Package or nil if none of the files have been checked. -func doPackage(names []string, basePkg *Package) *Package { +func doPackage(basePkg *Package, pkgInfo *loader.PackageInfo, fs *token.FileSet, astFiles []*ast.File) (*Package, error) { var files []*File - var astFiles []*ast.File - fs := token.NewFileSet() - for _, name := range names { - data, err := ioutil.ReadFile(name) + for _, parsedFile := range astFiles { + name := fs.Position(parsedFile.Pos()).Filename + shortName, err := shortestRelPath(name, "") if err != nil { - // Warn but continue to next package. - warnf("%s: %s", name, err) - return nil + return nil, err } - checkBuildTag(name, data) - var parsedFile *ast.File - if strings.HasSuffix(name, ".go") { - parsedFile, err = parser.ParseFile(fs, name, data, 0) - if err != nil { - warnf("%s: %s", name, err) - return nil - } - astFiles = append(astFiles, parsedFile) + + data, err := ioutil.ReadFile(shortName) + if err != nil { + return nil, fmt.Errorf("can't read %q: %s", shortName, err) } + + checkBuildTag(shortName, data) + files = append(files, &File{ fset: fs, content: data, - name: name, + name: shortName, file: parsedFile, dead: make(map[ast.Node]bool), }) } - if len(astFiles) == 0 { - return nil - } + pkg := new(Package) pkg.path = astFiles[0].Name.Name pkg.files = files // Type check the package. - errs := pkg.check(fs, astFiles) + errs := pkg.check(fs, astFiles, pkgInfo) if errs != nil { - if *verbose || mustTypecheck { - for _, err := range errs { - fmt.Fprintf(os.Stderr, "%v\n", err) - } - if mustTypecheck { - // This message could be silenced, and we could just exit, - // but it might be helpful at least at first to make clear that the - // above errors are coming from vet and not the compiler - // (they often look like compiler errors, such as "declared but not used"). - errorf("typecheck failures") - } + errors := []string{} + for _, err := range errs { + errors = append(errors, err.Error()) } + + return nil, fmt.Errorf("can't typecheck package: %s", strings.Join(errors, "|")) } // Check. @@ -464,7 +480,7 @@ func doPackage(names []string, basePkg *Package) *Package { } } asmCheck(pkg) - return pkg + return pkg, nil } func visit(path string, f os.FileInfo, err error) error { @@ -552,7 +568,7 @@ func (f *File) loc(pos token.Pos) string { // expression instead of the inner part with the actual error, the // precision can mislead. posn := f.fset.Position(pos) - return fmt.Sprintf("%s:%d", posn.Filename, posn.Line) + return fmt.Sprintf("%s:%d", f.name, posn.Line) } // locPrefix returns a formatted representation of the position for use as a line prefix. diff --git a/vendor/github.com/golangci/govet/types.go b/vendor/github.com/golangci/govet/types.go index 4bed51f7967e..14548c8e4e78 100644 --- a/vendor/github.com/golangci/govet/types.go +++ b/vendor/github.com/golangci/govet/types.go @@ -7,11 +7,16 @@ package govet import ( + "fmt" "go/ast" "go/build" "go/importer" "go/token" "go/types" + "os" + "time" + + "golang.org/x/tools/go/loader" ) // stdImporter is the importer we use to import packages. @@ -24,15 +29,22 @@ var ( formatterType *types.Interface // possibly nil ) -func inittypes() { +func inittypes() error { errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) - if typ := importType("fmt", "Stringer"); typ != nil { - stringerType = typ.Underlying().(*types.Interface) + typ, err := importType("fmt", "Stringer") + if err != nil { + return err } - if typ := importType("fmt", "Formatter"); typ != nil { - formatterType = typ.Underlying().(*types.Interface) + stringerType = typ.Underlying().(*types.Interface) + + typ, err = importType("fmt", "Formatter") + if err != nil { + return err } + formatterType = typ.Underlying().(*types.Interface) + + return nil } // isNamedType reports whether t is the named type path.name. @@ -48,59 +60,76 @@ func isNamedType(t types.Type, path, name string) bool { // importType returns the type denoted by the qualified identifier // path.name, and adds the respective package to the imports map // as a side effect. In case of an error, importType returns nil. -func importType(path, name string) types.Type { +func importType(path, name string) (types.Type, error) { + startedAt := time.Now() + defer func() { + fmt.Fprintf(os.Stderr, "vet: import of type %s.%s took %s\n", path, name, time.Since(startedAt)) + }() + pkg, err := stdImporter.Import(path) if err != nil { // This can happen if the package at path hasn't been compiled yet. - warnf("import failed: %v", err) - return nil + return nil, fmt.Errorf("import of type %s.%s failed: %v", path, name, err) } if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok { - return obj.Type() + return obj.Type(), nil } - warnf("invalid type name %q", name) - return nil + + return nil, fmt.Errorf("can't import type %s.%s: invalid type name %q", path, name, name) } -func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) []error { +func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File, pkgInfo *loader.PackageInfo) []error { if stdImporter == nil { if *source { stdImporter = importer.For("source", nil) } else { stdImporter = importer.Default() } - inittypes() + if err := inittypes(); err != nil { + return []error{fmt.Errorf("can't init std types: %s", err)} + } } - pkg.defs = make(map[*ast.Ident]types.Object) - pkg.uses = make(map[*ast.Ident]types.Object) - pkg.selectors = make(map[*ast.SelectorExpr]*types.Selection) - pkg.spans = make(map[types.Object]Span) - pkg.types = make(map[ast.Expr]types.TypeAndValue) var allErrors []error - config := types.Config{ - // We use the same importer for all imports to ensure that - // everybody sees identical packages for the given paths. - Importer: stdImporter, - // By providing a Config with our own error function, it will continue - // past the first error. We collect them all for printing later. - Error: func(e error) { - allErrors = append(allErrors, e) - }, - - Sizes: archSizes, - } - info := &types.Info{ - Selections: pkg.selectors, - Types: pkg.types, - Defs: pkg.defs, - Uses: pkg.uses, - } - typesPkg, err := config.Check(pkg.path, fs, astFiles, info) - if len(allErrors) == 0 && err != nil { - allErrors = append(allErrors, err) + pkg.spans = make(map[types.Object]Span) + if pkgInfo != nil { + pkg.defs = pkgInfo.Defs + pkg.uses = pkgInfo.Uses + pkg.selectors = pkgInfo.Selections + pkg.types = pkgInfo.Types + pkg.typesPkg = pkgInfo.Pkg + } else { + pkg.defs = make(map[*ast.Ident]types.Object) + pkg.uses = make(map[*ast.Ident]types.Object) + pkg.selectors = make(map[*ast.SelectorExpr]*types.Selection) + pkg.types = make(map[ast.Expr]types.TypeAndValue) + + config := types.Config{ + // We use the same importer for all imports to ensure that + // everybody sees identical packages for the given paths. + Importer: stdImporter, + // By providing a Config with our own error function, it will continue + // past the first error. We collect them all for printing later. + Error: func(e error) { + allErrors = append(allErrors, e) + }, + + Sizes: archSizes, + } + info := &types.Info{ + Selections: pkg.selectors, + Types: pkg.types, + Defs: pkg.defs, + Uses: pkg.uses, + } + typesPkg, err := config.Check(pkg.path, fs, astFiles, info) + if len(allErrors) == 0 && err != nil { + allErrors = append(allErrors, fmt.Errorf("type checker failed: %s", err)) + } + + pkg.typesPkg = typesPkg } - pkg.typesPkg = typesPkg + // update spans for id, obj := range pkg.defs { pkg.growSpan(id, obj)