diff --git a/Gopkg.lock b/Gopkg.lock index ac541ddc..93fe1c20 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -58,6 +58,12 @@ revision = "c34cdb4725f4c3844d095133c6e40e448b86589b" version = "v1.1.1" +[[projects]] + branch = "master" + name = "github.com/golangci/go-misc" + packages = ["deadcode"] + revision = "a82b63c685e730fbc1efbade9ce6316ac85cceb7" + [[projects]] branch = "master" name = "github.com/golangci/gofmt" @@ -231,6 +237,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "c4187e819ccca1b06537adb888a052a895c6236f90accd827314c12b2fd1ce3e" + inputs-digest = "fd69e23afecb188e33d824ca597d116e47e913418eb1f0842ccb4f9bb44c7c7c" solver-name = "gps-cdcl" solver-version = 1 diff --git a/pkg/golinters/deadcode.go b/pkg/golinters/deadcode.go new file mode 100644 index 00000000..08f9c6e5 --- /dev/null +++ b/pkg/golinters/deadcode.go @@ -0,0 +1,35 @@ +package golinters + +import ( + "context" + "fmt" + + deadcodeAPI "github.com/golangci/go-misc/deadcode" + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/result" + "github.com/golangci/golangci-shared/pkg/executors" +) + +type deadcode struct{} + +func (deadcode) Name() string { + return "deadcode" +} + +func (d deadcode) Run(ctx context.Context, exec executors.Executor, cfg *config.Run) (*result.Result, error) { + issues, err := deadcodeAPI.Run(cfg.Paths.MixedPaths(), true) // TODO: configure need of tests + if err != nil { + return nil, err + } + + res := &result.Result{} + for _, i := range issues { + res.Issues = append(res.Issues, result.Issue{ + File: i.Pos.Filename, + LineNumber: i.Pos.Line, + Text: fmt.Sprintf("%s is unused", formatCode(i.UnusedIdentName, cfg)), + FromLinter: d.Name(), + }) + } + return res, nil +} diff --git a/pkg/golinters/enabled_linters.go b/pkg/golinters/enabled_linters.go index 20519111..72f131fe 100644 --- a/pkg/golinters/enabled_linters.go +++ b/pkg/golinters/enabled_linters.go @@ -36,9 +36,11 @@ func GetAllSupportedLinterConfigs() []LinterConfig { return []LinterConfig{ enabledByDefault(govet{}, "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string"), enabledByDefault(errcheck{}, "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases"), - enabledByDefault(golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes."), - disabledByDefault(gofmt{}, "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification."), - disabledByDefault(gofmt{useGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports."), + enabledByDefault(golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes"), + enabledByDefault(deadcode{}, "Finds unused code"), + + disabledByDefault(gofmt{}, "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification"), + disabledByDefault(gofmt{useGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports"), } } diff --git a/pkg/golinters/testdata/deadcode.go b/pkg/golinters/testdata/deadcode.go new file mode 100644 index 00000000..a239496f --- /dev/null +++ b/pkg/golinters/testdata/deadcode.go @@ -0,0 +1,20 @@ +package testdata + +var y int + +var unused int // ERROR "`unused` is unused" + +func f(x int) { +} + +func g(x int) { // ERROR "`g` is unused" +} + +func H(x int) { +} + +func init() { + f(y) +} + +var _ int diff --git a/pkg/golinters/testdata/errcheck.go b/pkg/golinters/testdata/errcheck.go index ef277a10..1beb6ec9 100644 --- a/pkg/golinters/testdata/errcheck.go +++ b/pkg/golinters/testdata/errcheck.go @@ -5,15 +5,15 @@ import ( "os" ) -func retErr() error { +func RetErr() error { return nil } -func missedErrorCheck() { - retErr() // ERROR "Error return value of `retErr` is not checked" +func MissedErrorCheck() { + RetErr() // ERROR "Error return value of `RetErr` is not checked" } -func ignoreCloseMissingErrHandling() error { +func IgnoreCloseMissingErrHandling() error { f, err := os.Open("t.go") if err != nil { return err @@ -23,7 +23,7 @@ func ignoreCloseMissingErrHandling() error { return nil } -func ignoreCloseInDeferMissingErrHandling() { +func IgnoreCloseInDeferMissingErrHandling() { resp, err := http.Get("http://example.com/") if err != nil { panic(err) diff --git a/pkg/golinters/testdata/gofmt.go b/pkg/golinters/testdata/gofmt.go index 759c561f..fea1ab83 100644 --- a/pkg/golinters/testdata/gofmt.go +++ b/pkg/golinters/testdata/gofmt.go @@ -2,7 +2,7 @@ package testdata import "fmt" -func gofmtNotSimplified() { +func GofmtNotSimplified() { var x []string fmt.Print(x[1:len(x)]) // ERROR "File is not gofmt-ed with -s" } diff --git a/pkg/golinters/testdata/goimports.go b/pkg/golinters/testdata/goimports.go index 67d72c10..c530933f 100644 --- a/pkg/golinters/testdata/goimports.go +++ b/pkg/golinters/testdata/goimports.go @@ -5,7 +5,7 @@ import ( "github.com/golangci/golangci-lint/pkg/config" ) -func bar() { +func Bar() { fmt.Print("x") _ = config.Config{} } diff --git a/pkg/golinters/testdata/golint.go b/pkg/golinters/testdata/golint.go index e1a7024d..7ab20e53 100644 --- a/pkg/golinters/testdata/golint.go +++ b/pkg/golinters/testdata/golint.go @@ -1,6 +1,6 @@ package testdata -var go_lint string // ERROR "don't use underscores in Go names; var go_lint should be goLint" +var Go_lint string // ERROR "don't use underscores in Go names; var Go_lint should be GoLint" func ExportedFuncWithNoComment() { } diff --git a/pkg/golinters/testdata/govet.go b/pkg/golinters/testdata/govet.go index 8db2154c..74124715 100644 --- a/pkg/golinters/testdata/govet.go +++ b/pkg/golinters/testdata/govet.go @@ -2,11 +2,11 @@ package testdata import "os" -func govet() error { +func Govet() error { return &os.PathError{"first", "path", os.ErrNotExist} // ERROR "os.PathError composite literal uses unkeyed fields" } -func govetShadow(f *os.File, buf []byte) (err error) { +func GovetShadow(f *os.File, buf []byte) (err error) { if f != nil { _, err := f.Read(buf) // ERROR "declaration of .err. shadows declaration at testdata/govet.go:9" if err != nil { diff --git a/pkg/runner.go b/pkg/runner.go index c14cc4c3..fa595acb 100644 --- a/pkg/runner.go +++ b/pkg/runner.go @@ -27,6 +27,16 @@ type lintRes struct { res *result.Result } +func runLinter(ctx context.Context, linter Linter, exec executors.Executor, cfg *config.Run) (res *result.Result, err error) { + defer func() { + if panicData := recover(); panicData != nil { + err = fmt.Errorf("panic occured: %s", panicData) + } + }() + res, err = linter.Run(ctx, exec, cfg) + return +} + func runLinters(ctx context.Context, wg *sync.WaitGroup, tasksCh chan Linter, lintResultsCh chan lintRes, exec executors.Executor, cfg *config.Config) { for i := 0; i < cfg.Common.Concurrency; i++ { go func() { @@ -47,7 +57,7 @@ func runLinters(ctx context.Context, wg *sync.WaitGroup, tasksCh chan Linter, li if !ok { return } - res, lerr := linter.Run(ctx, exec, &cfg.Run) + res, lerr := runLinter(ctx, linter, exec, &cfg.Run) lintResultsCh <- lintRes{ linter: linter, err: lerr, diff --git a/vendor/github.com/golangci/go-misc/LICENSE b/vendor/github.com/golangci/go-misc/LICENSE new file mode 100644 index 00000000..cc42dd45 --- /dev/null +++ b/vendor/github.com/golangci/go-misc/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 Rémy Oudompheng. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * The name of Rémy Oudompheng may not be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/golangci/go-misc/deadcode/README.md b/vendor/github.com/golangci/go-misc/deadcode/README.md new file mode 100644 index 00000000..55042312 --- /dev/null +++ b/vendor/github.com/golangci/go-misc/deadcode/README.md @@ -0,0 +1,18 @@ +# deadcode + +`deadcode` is a very simple utility which detects unused declarations in a Go package. + +## Usage +``` +deadcode [-test] [packages] + + -test Include test files + packages A list of packages using the same conventions as the go tool +``` + +## Limitations + +* Self-referential unused code is not currently reported +* A single package can be tested at a time +* Unused methods are not reported + diff --git a/vendor/github.com/golangci/go-misc/deadcode/deadcode.go b/vendor/github.com/golangci/go-misc/deadcode/deadcode.go new file mode 100644 index 00000000..2343f660 --- /dev/null +++ b/vendor/github.com/golangci/go-misc/deadcode/deadcode.go @@ -0,0 +1,125 @@ +package deadcode + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "os" + "path/filepath" + "sort" + + "golang.org/x/tools/go/loader" +) + +var exitCode int + +var ( + withTestFiles bool +) + +type Issue struct { + Pos token.Position + UnusedIdentName string +} + +func Run(paths []string, processTestFiles bool) ([]Issue, error) { + ctx := &Context{ + withTests: processTestFiles, + } + ctx.Load(paths...) + report := ctx.Process() + var issues []Issue + for _, obj := range report { + issues = append(issues, Issue{ + Pos: ctx.Config.Fset.Position(obj.Pos()), + UnusedIdentName: obj.Name(), + }) + } + + return issues, nil +} + +func fatalf(format string, args ...interface{}) { + panic(fmt.Errorf(format, args...)) +} + +type Context struct { + cwd string + withTests bool + + loader.Config +} + +func (ctx *Context) Load(args ...string) { + ctx.Config.FromArgs(args, ctx.withTests) +} + +// error formats the error to standard error, adding program +// identification and a newline +func (ctx *Context) errorf(pos token.Pos, format string, args ...interface{}) { + if ctx.cwd == "" { + ctx.cwd, _ = os.Getwd() + } + p := ctx.Config.Fset.Position(pos) + f, err := filepath.Rel(ctx.cwd, p.Filename) + if err == nil { + p.Filename = f + } + fmt.Fprintf(os.Stderr, p.String()+": "+format+"\n", args...) + exitCode = 2 +} + +func (ctx *Context) Process() []types.Object { + prog, err := ctx.Config.Load() + if err != nil { + fatalf("cannot load packages: %s", err) + } + var allUnused []types.Object + for _, pkg := range prog.Imported { + unused := doPackage(prog, pkg) + allUnused = append(allUnused, unused...) + } + for _, pkg := range prog.Created { + unused := doPackage(prog, pkg) + allUnused = append(allUnused, unused...) + } + sort.Sort(objects(allUnused)) + return allUnused +} + +func doPackage(prog *loader.Program, pkg *loader.PackageInfo) []types.Object { + used := make(map[types.Object]bool) + for _, file := range pkg.Files { + ast.Inspect(file, func(n ast.Node) bool { + id, ok := n.(*ast.Ident) + if !ok { + return true + } + obj := pkg.Info.Uses[id] + if obj != nil { + used[obj] = true + } + return false + }) + } + + global := pkg.Pkg.Scope() + var unused []types.Object + for _, name := range global.Names() { + if pkg.Pkg.Name() == "main" && name == "main" { + continue + } + obj := global.Lookup(name) + if !used[obj] && (pkg.Pkg.Name() == "main" || !ast.IsExported(name)) { + unused = append(unused, obj) + } + } + return unused +} + +type objects []types.Object + +func (s objects) Len() int { return len(s) } +func (s objects) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s objects) Less(i, j int) bool { return s[i].Pos() < s[j].Pos() } diff --git a/vendor/github.com/golangci/go-misc/webtoys/static/vdeck.js b/vendor/github.com/golangci/go-misc/webtoys/static/vdeck.js new file mode 120000 index 00000000..303d7400 --- /dev/null +++ b/vendor/github.com/golangci/go-misc/webtoys/static/vdeck.js @@ -0,0 +1 @@ +../vdeck/vdeck.js \ No newline at end of file