Skip to content

Commit ba76b40

Browse files
authored
typecheck with gopackages (#40)
* Support go modules Use golang.org/x/tools/go/packages to load and type check packages to get docs. Provides support for go modules in addition to GOPATH and local packages. * clean up tests * load test packages for _test.go files * temporarily set GOPATH for tests The tests for vendoring need to be run from inside of a GOPATH. go/packages can now take into account the current environment, so it is not necessary to put the test into an existing GOPATH, and instead just set the GOPATH before calling load. * Update to latest version of go/packages * Fix lint warnings
1 parent 1009587 commit ba76b40

File tree

7 files changed

+246
-250
lines changed

7 files changed

+246
-250
lines changed

builtin.go

+21-15
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,44 @@
11
package main
22

33
import (
4-
"go/build"
4+
"go/ast"
55
"go/doc"
66
"go/parser"
77
"go/token"
88
"go/types"
9-
"os"
9+
"log"
1010

11-
"golang.org/x/tools/go/loader"
11+
"golang.org/x/tools/go/packages"
1212
)
1313

1414
func builtinPackage() *doc.Package {
15-
buildPkg, err := build.Import("builtin", "", build.ImportComment)
16-
// should never fail
15+
pkgs, err := packages.Load(&packages.Config{Mode: packages.LoadFiles}, "builtin")
1716
if err != nil {
18-
panic(err)
19-
}
20-
include := func(info os.FileInfo) bool {
21-
return info.Name() == "builtin.go"
17+
log.Fatalf("error getting metadata of builtin: %v", err)
2218
}
19+
pkg := pkgs[0]
20+
2321
fs := token.NewFileSet()
24-
astPkgs, err := parser.ParseDir(fs, buildPkg.Dir, include, parser.ParseComments)
25-
if err != nil {
26-
panic(err)
22+
fileMap := make(map[string]*ast.File)
23+
for _, filename := range pkg.GoFiles {
24+
file, err := parser.ParseFile(fs, filename, nil, parser.ParseComments)
25+
if err != nil {
26+
log.Fatal(err)
27+
}
28+
fileMap[filename] = file
29+
}
30+
31+
astPkg := &ast.Package{
32+
Name: pkg.Name,
33+
Files: fileMap,
2734
}
28-
astPkg := astPkgs["builtin"]
29-
return doc.New(astPkg, buildPkg.ImportPath, doc.AllDecls)
35+
return doc.New(astPkg, "builtin", doc.AllDecls)
3036
}
3137

3238
// findInBuiltin searches for an identifier in the builtin package.
3339
// It searches in the following order: funcs, constants and variables,
3440
// and finally types.
35-
func findInBuiltin(name string, obj types.Object, prog *loader.Program) (docstring, decl string) {
41+
func findInBuiltin(name string, obj types.Object, prog *packages.Package) (docstring, decl string) {
3642
pkg := builtinPackage()
3743

3844
consts := make([]*doc.Value, 0, 2*len(pkg.Consts))

ident.go

+45-9
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import (
44
"bytes"
55
"fmt"
66
"go/ast"
7-
"go/build"
87
"go/printer"
98
"go/token"
109
"go/types"
1110
"strings"
1211

13-
"golang.org/x/tools/go/loader"
12+
"golang.org/x/tools/go/ast/astutil"
13+
"golang.org/x/tools/go/packages"
1414
)
1515

1616
func findTypeSpec(decl *ast.GenDecl, pos token.Pos) *ast.TypeSpec {
@@ -35,7 +35,7 @@ func findVarSpec(decl *ast.GenDecl, pos token.Pos) *ast.ValueSpec {
3535
return nil
3636
}
3737

38-
func formatNode(n ast.Node, obj types.Object, prog *loader.Program) string {
38+
func formatNode(n ast.Node, obj types.Object, prog *packages.Package) string {
3939
//fmt.Printf("formatting %T node\n", n)
4040
var nc ast.Node
4141
// Render a copy of the node with no documentation.
@@ -113,7 +113,7 @@ func formatNode(n ast.Node, obj types.Object, prog *loader.Program) string {
113113
}
114114

115115
// IdentDoc attempts to get the documentation for a *ast.Ident.
116-
func IdentDoc(ctx *build.Context, id *ast.Ident, info *loader.PackageInfo, prog *loader.Program) (*Doc, error) {
116+
func IdentDoc(id *ast.Ident, info *types.Info, prog *packages.Package) (*Doc, error) {
117117
// get definition of identifier
118118
obj := info.ObjectOf(id)
119119

@@ -128,17 +128,17 @@ func IdentDoc(ctx *build.Context, id *ast.Ident, info *loader.PackageInfo, prog
128128
}
129129

130130
pkgPath, pkgName := "", ""
131-
if obj.Pkg() != nil {
132-
pkgPath = obj.Pkg().Path()
133-
pkgName = obj.Pkg().Name()
131+
if op := obj.Pkg(); op != nil {
132+
pkgPath = op.Path()
133+
pkgName = op.Name()
134134
}
135135

136136
// handle packages imported under a different name
137137
if p, ok := obj.(*types.PkgName); ok {
138-
return PackageDoc(ctx, prog.Fset, "", p.Imported().Path()) // SRCDIR TODO TODO
138+
return PackageDoc(prog, p.Imported().Path())
139139
}
140140

141-
_, nodes, _ := prog.PathEnclosingInterval(obj.Pos(), obj.Pos())
141+
_, nodes := pathEnclosingInterval(prog, obj.Pos(), obj.Pos())
142142
if len(nodes) == 0 {
143143
// special case - builtins
144144
doc, decl := findInBuiltin(obj.Name(), obj, prog)
@@ -231,6 +231,42 @@ func IdentDoc(ctx *build.Context, id *ast.Ident, info *loader.PackageInfo, prog
231231
return doc, nil
232232
}
233233

234+
// pathEnclosingInterval returns the types.Info of the package and ast.Node that
235+
// contain source interval [start, end), and all the node's ancestors
236+
// up to the AST root. It searches the ast.Files of initPkg and the packages it imports.
237+
//
238+
// Modified from golang.org/x/tools/go/loader.
239+
func pathEnclosingInterval(initPkg *packages.Package, start, end token.Pos) (*types.Info, []ast.Node) {
240+
pkgs := []*packages.Package{initPkg}
241+
for _, pkg := range initPkg.Imports {
242+
pkgs = append(pkgs, pkg)
243+
}
244+
245+
for _, pkg := range pkgs {
246+
for _, f := range pkg.Syntax {
247+
if f.Pos() == token.NoPos {
248+
// This can happen if the parser saw
249+
// too many errors and bailed out.
250+
// (Use parser.AllErrors to prevent that.)
251+
continue
252+
}
253+
if !tokenFileContainsPos(pkg.Fset.File(f.Pos()), start) {
254+
continue
255+
}
256+
if path, _ := astutil.PathEnclosingInterval(f, start, end); path != nil {
257+
return pkg.TypesInfo, path
258+
}
259+
}
260+
}
261+
return nil, nil
262+
}
263+
264+
func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
265+
p := int(pos)
266+
base := f.Base()
267+
return base <= p && p < base+f.Size()
268+
}
269+
234270
func stripVendorFromImportPath(ip string) string {
235271
vendor := "/vendor/"
236272
l := len(vendor)

0 commit comments

Comments
 (0)