Skip to content

Commit 0cc8df2

Browse files
committed
Switch from go/loader to go/packages
This removes the need for kisielk/gotool, too. While at it, remove mvdan.cc/lint, whose API no longer makes sense. Added one TODO for a weird bug I've encountered, where we end up parsing directories that don't contain any Go files. Also added a TODO where we deduplicate warnings at the end, since it seems somewhat buggy that we must do that. The only change to testdata is a warning, since go/packages doesn't use relative package paths. That seems fair.
1 parent d818a7e commit 0cc8df2

File tree

2 files changed

+48
-40
lines changed

2 files changed

+48
-40
lines changed

check/check.go

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,9 @@ import (
2424
"golang.org/x/tools/go/callgraph"
2525
"golang.org/x/tools/go/callgraph/cha"
2626
"golang.org/x/tools/go/callgraph/rta"
27-
"golang.org/x/tools/go/loader"
27+
"golang.org/x/tools/go/packages"
2828
"golang.org/x/tools/go/ssa"
2929
"golang.org/x/tools/go/ssa/ssautil"
30-
31-
"github.com/kisielk/gotool"
32-
"mvdan.cc/lint"
3330
)
3431

3532
// UnusedParams returns a list of human-readable issues that point out unused
@@ -55,7 +52,7 @@ func UnusedParams(tests bool, algo string, exported, debug bool, args ...string)
5552
// UnusedParams instead, unless you want to use a *loader.Program and
5653
// *ssa.Program directly.
5754
type Checker struct {
58-
lprog *loader.Program
55+
pkgs []*packages.Package
5956
prog *ssa.Program
6057
graph *callgraph.Graph
6158

@@ -66,48 +63,53 @@ type Checker struct {
6663
exported bool
6764
debugLog io.Writer
6865

69-
issues []lint.Issue
66+
issues []Issue
7067

7168
cachedDeclCounts map[string]map[string]int
7269

7370
callByPos map[token.Pos]*ast.CallExpr
7471
}
7572

76-
var (
77-
_ lint.Checker = (*Checker)(nil)
78-
_ lint.WithSSA = (*Checker)(nil)
79-
80-
errorType = types.Universe.Lookup("error").Type()
81-
)
73+
var errorType = types.Universe.Lookup("error").Type()
8274

8375
// lines runs the checker and returns the list of readable issues.
8476
func (c *Checker) lines(args ...string) ([]string, error) {
85-
paths := gotool.ImportPaths(args)
86-
conf := loader.Config{
87-
ParserMode: parser.ParseComments,
77+
cfg := &packages.Config{
78+
Mode: packages.LoadSyntax,
79+
Tests: c.tests,
8880
}
89-
if _, err := conf.FromArgs(paths, c.tests); err != nil {
90-
return nil, err
91-
}
92-
lprog, err := conf.Load()
81+
pkgs, err := packages.Load(cfg, args...)
9382
if err != nil {
9483
return nil, err
9584
}
96-
prog := ssautil.CreateProgram(lprog, 0)
85+
if packages.PrintErrors(pkgs) > 0 {
86+
return nil, fmt.Errorf("encountered errors")
87+
}
88+
89+
prog, _ := ssautil.Packages(pkgs, 0)
9790
prog.Build()
98-
c.Program(lprog)
91+
c.Packages(pkgs)
9992
c.ProgramSSA(prog)
10093
issues, err := c.Check()
10194
if err != nil {
10295
return nil, err
10396
}
104-
lines := make([]string, len(issues))
105-
for i, issue := range issues {
97+
lines := make([]string, 0, len(issues))
98+
prevLine := ""
99+
for _, issue := range issues {
106100
fpos := prog.Fset.Position(issue.Pos()).String()
107101
if strings.HasPrefix(fpos, c.wd) {
108102
fpos = fpos[len(c.wd)+1:]
109103
}
110-
lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message())
104+
line := fmt.Sprintf("%s: %s", fpos, issue.Message())
105+
if line == prevLine {
106+
// Deduplicate lines, since we may look at the same
107+
// package multiple times if tests are involved.
108+
// TODO: is there a better way to handle this?
109+
continue
110+
}
111+
prevLine = line
112+
lines = append(lines, fmt.Sprintf("%s: %s", fpos, issue.Message()))
111113
}
112114
return lines, nil
113115
}
@@ -123,8 +125,8 @@ func (i Issue) Pos() token.Pos { return i.pos }
123125
func (i Issue) Message() string { return i.fname + " - " + i.msg }
124126

125127
// Program supplies Checker with the needed *loader.Program.
126-
func (c *Checker) Program(lprog *loader.Program) {
127-
c.lprog = lprog
128+
func (c *Checker) Packages(pkgs []*packages.Package) {
129+
c.pkgs = pkgs
128130
}
129131

130132
// ProgramSSA supplies Checker with the needed *ssa.Program.
@@ -173,14 +175,15 @@ var stdSizes = types.SizesFor("gc", "amd64")
173175

174176
// Check runs the unused parameter check and returns the list of found issues,
175177
// and any error encountered.
176-
func (c *Checker) Check() ([]lint.Issue, error) {
178+
func (c *Checker) Check() ([]Issue, error) {
177179
c.cachedDeclCounts = make(map[string]map[string]int)
178180
c.callByPos = make(map[token.Pos]*ast.CallExpr)
179-
wantPkg := make(map[*types.Package]*loader.PackageInfo)
181+
wantPkg := make(map[*types.Package]*packages.Package)
180182
genFiles := make(map[string]bool)
181-
for _, info := range c.lprog.InitialPackages() {
182-
wantPkg[info.Pkg] = info
183-
for _, f := range info.Files {
183+
for _, pkg := range c.pkgs {
184+
fmt.Println(pkg)
185+
wantPkg[pkg.Types] = pkg
186+
for _, f := range pkg.Syntax {
184187
if len(f.Comments) > 0 && generatedDoc(f.Comments[0].Text()) {
185188
fname := c.prog.Fset.Position(f.Pos()).Filename
186189
genFiles[fname] = true
@@ -221,8 +224,8 @@ func (c *Checker) Check() ([]lint.Issue, error) {
221224
case len(fn.Blocks) == 0: // stub
222225
continue
223226
}
224-
pkgInfo := wantPkg[fn.Pkg.Pkg]
225-
if pkgInfo == nil { // not part of given pkgs
227+
pkg := wantPkg[fn.Pkg.Pkg]
228+
if pkg == nil { // not part of given pkgs
226229
continue
227230
}
228231
if c.exported || fn.Pkg.Pkg.Name() == "main" {
@@ -238,7 +241,7 @@ func (c *Checker) Check() ([]lint.Issue, error) {
238241
continue // generated file
239242
}
240243

241-
c.checkFunc(fn, pkgInfo)
244+
c.checkFunc(fn, pkg)
242245
}
243246
sort.Slice(c.issues, func(i, j int) bool {
244247
p1 := c.prog.Fset.Position(c.issues[i].Pos())
@@ -269,7 +272,7 @@ func constValueString(cnst *ssa.Const) string {
269272
}
270273

271274
// checkFunc checks a single function for unused parameters.
272-
func (c *Checker) checkFunc(fn *ssa.Function, pkgInfo *loader.PackageInfo) {
275+
func (c *Checker) checkFunc(fn *ssa.Function, pkg *packages.Package) {
273276
c.debug("func %s\n", fn.RelString(fn.Package().Pkg))
274277
if dummyImpl(fn.Blocks[0]) { // panic implementation
275278
c.debug(" skip - dummy implementation\n")
@@ -283,7 +286,7 @@ func (c *Checker) checkFunc(fn *ssa.Function, pkgInfo *loader.PackageInfo) {
283286
c.debug(" skip - type is required via call\n")
284287
return
285288
}
286-
if c.multipleImpls(pkgInfo, fn) {
289+
if c.multipleImpls(pkg, fn) {
287290
c.debug(" skip - multiple implementations via build tags\n")
288291
return
289292
}
@@ -395,7 +398,7 @@ resLoop:
395398
}
396399

397400
// mainPackages returns the subset of main packages within pkgSet.
398-
func mainPackages(prog *ssa.Program, pkgSet map[*types.Package]*loader.PackageInfo) ([]*ssa.Package, error) {
401+
func mainPackages(prog *ssa.Program, pkgSet map[*types.Package]*packages.Package) ([]*ssa.Package, error) {
399402
mains := make([]*ssa.Package, 0, len(pkgSet))
400403
for tpkg := range pkgSet {
401404
pkg := prog.Package(tpkg)
@@ -641,6 +644,11 @@ func (c *Checker) declCounts(pkgDir string, pkgName string) map[string]int {
641644
c.cachedDeclCounts[pkgDir] = nil
642645
return nil
643646
}
647+
if len(pkgs) == 0 {
648+
// TODO: investigate why this started happening after switching
649+
// to go/packages
650+
return nil
651+
}
644652
pkg := pkgs[pkgName]
645653
count := make(map[string]int)
646654
for _, file := range pkg.Files {
@@ -682,12 +690,12 @@ func recvPrefix(recv *ast.FieldList) string {
682690
// source code. For example, if there are different function bodies depending on
683691
// the operating system or architecture. That tends to mean that an unused
684692
// parameter in one implementation may not be unused in another.
685-
func (c *Checker) multipleImpls(info *loader.PackageInfo, fn *ssa.Function) bool {
693+
func (c *Checker) multipleImpls(pkg *packages.Package, fn *ssa.Function) bool {
686694
if fn.Parent() != nil { // nested func
687695
return false
688696
}
689697
path := c.prog.Fset.Position(fn.Pos()).Filename
690-
count := c.declCounts(filepath.Dir(path), info.Pkg.Name())
698+
count := c.declCounts(filepath.Dir(path), pkg.Types.Name())
691699
name := fn.Name()
692700
if fn.Signature.Recv() != nil {
693701
tp := fn.Params[0].Type()

check/testdata/log

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ testdata/closure.go:6:19: ClosureUse$1 - v is unused
22
testdata/extractparams.go:15:24: funcResultAsParam - a is unused
33
testdata/extractparams.go:20:38: (FooType).methodResultAsParam - a is unused
44
testdata/extractparams.go:27:29: funcResultsAsParams - b is unused
5-
testdata/extractparams.go:45:26: returnResultsOwn - result 0 (./testdata.FooType) is never used
5+
testdata/extractparams.go:45:26: returnResultsOwn - result 0 (mvdan.cc/unparam/check/testdata.FooType) is never used
66
testdata/funclit.go:4:20: parent$1 - f is unused
77
testdata/ignoredrets.go:5:22: singleIgnored - result 0 (rune) is never used
88
testdata/ignoredrets.go:26:27: singleIgnoredName - result r is never used

0 commit comments

Comments
 (0)