1. Allow govet to work in 2 modes: fast and slow. Default is slow. In fast mode golangci-lint runs `go install -i` and `go test -i` for analyzed packages. But it's fast only when: - go >= 1.10 - it's repeated run or $GOPATH/pkg or `go env GOCACHE` is cached between CI builds In slow mode we load program from source code like for another linters and do it only once for all linters. 3. Patch govet code to warn about any troubles with the type information. Default behaviour of govet was to hide such warnings. Fail analysis if there are any troubles with type loading: it will prevent false-positives and false-negatives from govet. 4. Describe almost all options in .golangci.example.yml and include it into README. Describe when to use slow or fast mode of govet. 5. Speed up govet: reuse AST parsing: it's already parsed once by golangci-lint. For "slow" runs (when we run at least one slow linter) speedup by not loading type information second time. 6. Improve logging, debug logging 7. Fix crash in logging of AST cache warnings (#118)
This commit is contained in:
parent
f239b80ce1
commit
5514c4393e
@ -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
|
2
Gopkg.lock
generated
2
Gopkg.lock
generated
@ -122,7 +122,7 @@
|
||||
"lib/cfg",
|
||||
"lib/whitelist"
|
||||
]
|
||||
revision = "1a9ab8120ec96a014df3afab2d6c47f7e12d8928"
|
||||
revision = "18c83969a303d67eaa9d67747a930f007e3e9c89"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
1
Makefile
1
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
|
||||
|
165
README.md
165
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]
|
||||
@ -311,10 +311,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:
|
||||
@ -410,11 +561,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**
|
||||
|
||||
|
@ -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**
|
||||
|
||||
|
@ -121,6 +121,7 @@ type LintersSettings struct {
|
||||
}
|
||||
Govet struct {
|
||||
CheckShadowing bool `mapstructure:"check-shadowing"`
|
||||
UseInstalledPackages bool `mapstructure:"use-installed-packages"`
|
||||
}
|
||||
Golint struct {
|
||||
MinConfidence float64 `mapstructure:"min-confidence"`
|
||||
|
@ -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)
|
||||
}
|
||||
govetIssues = append(govetIssues, issues...)
|
||||
} else {
|
||||
govetIssues, err = g.runOnSourcePackages(ctx, lintCtx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't run govet on source packages: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
50
pkg/goutils/goutils.go
Normal file
50
pkg/goutils/goutils.go
Normal file
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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/"),
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
cfg := &config.Config{
|
||||
Run: config.Run{
|
||||
AnalyzeTests: true,
|
||||
}, pkgProg, logutils.NewStderrLog(""))
|
||||
},
|
||||
}
|
||||
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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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,7 +33,7 @@ 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 TestCongratsMessageGoneIfSilent(t *testing.T) {
|
||||
@ -40,7 +43,7 @@ func TestCongratsMessageGoneIfSilent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCongratsMessageIfNoIssues(t *testing.T) {
|
||||
out, exitCode := runGolangciLint(t, "../...")
|
||||
out, exitCode := runGolangciLint(t, root)
|
||||
checkNoIssuesRun(t, out, exitCode)
|
||||
}
|
||||
|
||||
@ -66,7 +69,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.")
|
||||
@ -343,6 +346,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)
|
||||
|
16
vendor/github.com/golangci/govet/golangci.go
generated
vendored
16
vendor/github.com/golangci/govet/golangci.go
generated
vendored
@ -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
|
||||
}
|
||||
|
||||
|
94
vendor/github.com/golangci/govet/main.go
generated
vendored
94
vendor/github.com/golangci/govet/main.go
generated
vendored
@ -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)
|
||||
|
||||
data, err := ioutil.ReadFile(shortName)
|
||||
if err != nil {
|
||||
warnf("%s: %s", name, err)
|
||||
return nil
|
||||
}
|
||||
astFiles = append(astFiles, parsedFile)
|
||||
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 {
|
||||
errors := []string{}
|
||||
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 = 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.
|
||||
|
59
vendor/github.com/golangci/govet/types.go
generated
vendored
59
vendor/github.com/golangci/govet/types.go
generated
vendored
@ -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 {
|
||||
typ, err := importType("fmt", "Stringer")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stringerType = typ.Underlying().(*types.Interface)
|
||||
|
||||
typ, err = importType("fmt", "Formatter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if typ := importType("fmt", "Formatter"); typ != nil {
|
||||
formatterType = typ.Underlying().(*types.Interface)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isNamedType reports whether t is the named type path.name.
|
||||
@ -48,36 +60,50 @@ 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)}
|
||||
}
|
||||
}
|
||||
|
||||
var allErrors []error
|
||||
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.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.
|
||||
@ -98,9 +124,12 @@ func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) []error {
|
||||
}
|
||||
typesPkg, err := config.Check(pkg.path, fs, astFiles, info)
|
||||
if len(allErrors) == 0 && err != nil {
|
||||
allErrors = append(allErrors, err)
|
||||
allErrors = append(allErrors, fmt.Errorf("type checker failed: %s", err))
|
||||
}
|
||||
|
||||
pkg.typesPkg = typesPkg
|
||||
}
|
||||
|
||||
// update spans
|
||||
for id, obj := range pkg.defs {
|
||||
pkg.growSpan(id, obj)
|
||||
|
Loading…
x
Reference in New Issue
Block a user