Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

typecheck with gopackages #40

Merged
merged 6 commits into from
Sep 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions builtin.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
package main

import (
"go/build"
"go/ast"
"go/doc"
"go/parser"
"go/token"
"go/types"
"os"
"log"

"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/packages"
)

func builtinPackage() *doc.Package {
buildPkg, err := build.Import("builtin", "", build.ImportComment)
// should never fail
pkgs, err := packages.Load(&packages.Config{Mode: packages.LoadFiles}, "builtin")
if err != nil {
panic(err)
}
include := func(info os.FileInfo) bool {
return info.Name() == "builtin.go"
log.Fatalf("error getting metadata of builtin: %v", err)
}
pkg := pkgs[0]

fs := token.NewFileSet()
astPkgs, err := parser.ParseDir(fs, buildPkg.Dir, include, parser.ParseComments)
if err != nil {
panic(err)
fileMap := make(map[string]*ast.File)
for _, filename := range pkg.GoFiles {
file, err := parser.ParseFile(fs, filename, nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
fileMap[filename] = file
}

astPkg := &ast.Package{
Name: pkg.Name,
Files: fileMap,
}
astPkg := astPkgs["builtin"]
return doc.New(astPkg, buildPkg.ImportPath, doc.AllDecls)
return doc.New(astPkg, "builtin", doc.AllDecls)
}

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

consts := make([]*doc.Value, 0, 2*len(pkg.Consts))
Expand Down
54 changes: 45 additions & 9 deletions ident.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/printer"
"go/token"
"go/types"
"strings"

"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/packages"
)

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

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

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

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

pkgPath, pkgName := "", ""
if obj.Pkg() != nil {
pkgPath = obj.Pkg().Path()
pkgName = obj.Pkg().Name()
if op := obj.Pkg(); op != nil {
pkgPath = op.Path()
pkgName = op.Name()
}

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

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

// pathEnclosingInterval returns the types.Info of the package and ast.Node that
// contain source interval [start, end), and all the node's ancestors
// up to the AST root. It searches the ast.Files of initPkg and the packages it imports.
//
// Modified from golang.org/x/tools/go/loader.
func pathEnclosingInterval(initPkg *packages.Package, start, end token.Pos) (*types.Info, []ast.Node) {
pkgs := []*packages.Package{initPkg}
for _, pkg := range initPkg.Imports {
pkgs = append(pkgs, pkg)
}

for _, pkg := range pkgs {
for _, f := range pkg.Syntax {
if f.Pos() == token.NoPos {
// This can happen if the parser saw
// too many errors and bailed out.
// (Use parser.AllErrors to prevent that.)
continue
}
if !tokenFileContainsPos(pkg.Fset.File(f.Pos()), start) {
continue
}
if path, _ := astutil.PathEnclosingInterval(f, start, end); path != nil {
return pkg.TypesInfo, path
}
}
}
return nil, nil
}

func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
p := int(pos)
base := f.Base()
return base <= p && p < base+f.Size()
}

func stripVendorFromImportPath(ip string) string {
vendor := "/vendor/"
l := len(vendor)
Expand Down
Loading