Skip to content

Commit 355fb1c

Browse files
authored
Merge pull request #148 from suzmue/typecheck-with-gopackages
Support go modules
2 parents e96dacd + 2f843a1 commit 355fb1c

File tree

8 files changed

+204
-98
lines changed

8 files changed

+204
-98
lines changed

.travis.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ sudo: false
33

44
matrix:
55
include:
6-
- go: "1.6"
7-
- go: "1.7"
8-
- go: "1.8"
96
- go: "1.9"
107
- go: "1.10"
118
- go: "tip"

README.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ errcheck is a program for checking for unchecked errors in go programs.
88

99
go get -u github.com/kisielk/errcheck
1010

11-
errcheck requires Go 1.6 or newer and depends on the package go/loader from the golang.org/x/tools repository.
11+
errcheck requires Go 1.9 or newer and depends on the package go/packages from the golang.org/x/tools repository.
1212

1313
## Use
1414

@@ -94,12 +94,9 @@ no arguments.
9494

9595
## Cgo
9696

97-
Currently errcheck is unable to check packages that `import "C"` due to limitations
98-
in the importer.
97+
Currently errcheck is unable to check packages that import "C" due to limitations in the importer when used with versions earlier than Go 1.11.
9998

100-
However, you can use errcheck on packages that depend on those which use cgo. In
101-
order for this to work you need to `go install` the cgo dependencies before running
102-
errcheck on the dependent packages.
99+
However, you can use errcheck on packages that depend on those which use cgo. In order for this to work you need to go install the cgo dependencies before running errcheck on the dependent packages.
103100

104101
See https://github.com/kisielk/errcheck/issues/16 for more details.
105102

go.mod

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
module "github.com/kisielk/errcheck"
1+
module github.com/kisielk/errcheck
22

3-
require (
4-
"github.com/kisielk/gotool" v1.0.0
5-
"golang.org/x/tools" v0.0.0-20180221164845-07fd8470d635
6-
)
3+
require golang.org/x/tools v0.0.0-20180803180156-3c07937fe18c

internal/errcheck/errcheck.go

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"errors"
99
"fmt"
1010
"go/ast"
11-
"go/build"
1211
"go/token"
1312
"go/types"
1413
"os"
@@ -17,8 +16,7 @@ import (
1716
"strings"
1817
"sync"
1918

20-
"go/parser"
21-
"golang.org/x/tools/go/loader"
19+
"golang.org/x/tools/go/packages"
2220
)
2321

2422
var errorType *types.Interface
@@ -165,28 +163,19 @@ func (c *Checker) logf(msg string, args ...interface{}) {
165163
}
166164
}
167165

168-
func (c *Checker) load(paths ...string) (*loader.Program, error) {
169-
ctx := build.Default
170-
for _, tag := range c.Tags {
171-
ctx.BuildTags = append(ctx.BuildTags, tag)
172-
}
173-
loadcfg := loader.Config{
174-
Build: &ctx,
175-
}
176-
177-
if c.WithoutGeneratedCode {
178-
loadcfg.ParserMode = parser.ParseComments
179-
}
166+
// loadPackages is used for testing.
167+
var loadPackages = func(cfg *packages.Config, paths ...string) ([]*packages.Package, error) {
168+
return packages.Load(cfg, paths...)
169+
}
180170

181-
rest, err := loadcfg.FromArgs(paths, !c.WithoutTests)
182-
if err != nil {
183-
return nil, fmt.Errorf("could not parse arguments: %s", err)
184-
}
185-
if len(rest) > 0 {
186-
return nil, fmt.Errorf("unhandled extra arguments: %v", rest)
171+
func (c *Checker) load(paths ...string) ([]*packages.Package, error) {
172+
cfg := &packages.Config{
173+
Mode: packages.LoadAllSyntax,
174+
Tests: !c.WithoutTests,
175+
Flags: []string{fmt.Sprintf("-tags=%s", strings.Join(c.Tags, " "))},
176+
Error: func(error) {}, // don't print type check errors
187177
}
188-
189-
return loadcfg.Load()
178+
return loadPackages(cfg, paths...)
190179
}
191180

192181
var generatedCodeRegexp = regexp.MustCompile("^// Code generated .* DO NOT EDIT\\.$")
@@ -209,27 +198,28 @@ func (c *Checker) shouldSkipFile(file *ast.File) bool {
209198

210199
// CheckPackages checks packages for errors.
211200
func (c *Checker) CheckPackages(paths ...string) error {
212-
program, err := c.load(paths...)
201+
pkgs, err := c.load(paths...)
213202
if err != nil {
214-
return fmt.Errorf("could not type check: %s", err)
203+
return err
204+
}
205+
// Check for errors in the initial packages.
206+
for _, pkg := range pkgs {
207+
if len(pkg.Errors) > 0 {
208+
return fmt.Errorf("errors while loading package %s: %v", pkg.ID, pkg.Errors)
209+
}
215210
}
216211

217212
var wg sync.WaitGroup
218213
u := &UncheckedErrors{}
219-
for _, pkgInfo := range program.InitialPackages() {
220-
if pkgInfo.Pkg.Path() == "unsafe" { // not a real package
221-
continue
222-
}
223-
214+
for _, pkg := range pkgs {
224215
wg.Add(1)
225216

226-
go func(pkgInfo *loader.PackageInfo) {
217+
go func(pkg *packages.Package) {
227218
defer wg.Done()
228-
c.logf("Checking %s", pkgInfo.Pkg.Path())
219+
c.logf("Checking %s", pkg.Types.Path())
229220

230221
v := &visitor{
231-
prog: program,
232-
pkg: pkgInfo,
222+
pkg: pkg,
233223
ignore: c.Ignore,
234224
blank: c.Blank,
235225
asserts: c.Asserts,
@@ -238,28 +228,36 @@ func (c *Checker) CheckPackages(paths ...string) error {
238228
errors: []UncheckedError{},
239229
}
240230

241-
for _, astFile := range v.pkg.Files {
231+
for _, astFile := range v.pkg.Syntax {
242232
if c.shouldSkipFile(astFile) {
243233
continue
244234
}
245235
ast.Walk(v, astFile)
246236
}
247237
u.Append(v.errors...)
248-
}(pkgInfo)
238+
}(pkg)
249239
}
250240

251241
wg.Wait()
252242
if u.Len() > 0 {
243+
// Sort unchecked errors and remove duplicates. Duplicates may occur when a file
244+
// containing an unchecked error belongs to > 1 package.
253245
sort.Sort(byName{u})
246+
uniq := u.Errors[:0] // compact in-place
247+
for i, err := range u.Errors {
248+
if i == 0 || err != u.Errors[i-1] {
249+
uniq = append(uniq, err)
250+
}
251+
}
252+
u.Errors = uniq
254253
return u
255254
}
256255
return nil
257256
}
258257

259258
// visitor implements the errcheck algorithm
260259
type visitor struct {
261-
prog *loader.Program
262-
pkg *loader.PackageInfo
260+
pkg *packages.Package
263261
ignore map[string]*regexp.Regexp
264262
blank bool
265263
asserts bool
@@ -284,7 +282,7 @@ func (v *visitor) selectorAndFunc(call *ast.CallExpr) (*ast.SelectorExpr, *types
284282
return nil, nil, false
285283
}
286284

287-
fn, ok := v.pkg.ObjectOf(sel.Sel).(*types.Func)
285+
fn, ok := v.pkg.TypesInfo.ObjectOf(sel.Sel).(*types.Func)
288286
if !ok {
289287
// Shouldn't happen, but be paranoid
290288
return nil, nil, false
@@ -340,7 +338,7 @@ func (v *visitor) namesForExcludeCheck(call *ast.CallExpr) []string {
340338

341339
// This will be missing for functions without a receiver (like fmt.Printf),
342340
// so just fall back to the the function's fullName in that case.
343-
selection, ok := v.pkg.Selections[sel]
341+
selection, ok := v.pkg.TypesInfo.Selections[sel]
344342
if !ok {
345343
return []string{name}
346344
}
@@ -401,7 +399,7 @@ func (v *visitor) ignoreCall(call *ast.CallExpr) bool {
401399
return true
402400
}
403401

404-
if obj := v.pkg.Uses[id]; obj != nil {
402+
if obj := v.pkg.TypesInfo.Uses[id]; obj != nil {
405403
if pkg := obj.Pkg(); pkg != nil {
406404
if re, ok := v.ignore[pkg.Path()]; ok {
407405
return re.MatchString(id.Name)
@@ -435,7 +433,7 @@ func nonVendoredPkgPath(pkgPath string) (string, bool) {
435433
// len(s) == number of return types of call
436434
// s[i] == true iff return type at position i from left is an error type
437435
func (v *visitor) errorsByArg(call *ast.CallExpr) []bool {
438-
switch t := v.pkg.Types[call].Type.(type) {
436+
switch t := v.pkg.TypesInfo.Types[call].Type.(type) {
439437
case *types.Named:
440438
// Single return
441439
return []bool{isErrorType(t)}
@@ -477,15 +475,15 @@ func (v *visitor) callReturnsError(call *ast.CallExpr) bool {
477475
// isRecover returns true if the given CallExpr is a call to the built-in recover() function.
478476
func (v *visitor) isRecover(call *ast.CallExpr) bool {
479477
if fun, ok := call.Fun.(*ast.Ident); ok {
480-
if _, ok := v.pkg.Uses[fun].(*types.Builtin); ok {
478+
if _, ok := v.pkg.TypesInfo.Uses[fun].(*types.Builtin); ok {
481479
return fun.Name == "recover"
482480
}
483481
}
484482
return false
485483
}
486484

487485
func (v *visitor) addErrorAtPosition(position token.Pos, call *ast.CallExpr) {
488-
pos := v.prog.Fset.Position(position)
486+
pos := v.pkg.Fset.Position(position)
489487
lines, ok := v.lines[pos.Filename]
490488
if !ok {
491489
lines = readfile(pos.Filename)

0 commit comments

Comments
 (0)