mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-04 07:49:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			814 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			814 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2015 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package parser
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"go/ast"
 | 
						|
	"go/build"
 | 
						|
	"go/parser"
 | 
						|
	"go/token"
 | 
						|
	tc "go/types"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"os/exec"
 | 
						|
	"path"
 | 
						|
	"path/filepath"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/golang/glog"
 | 
						|
	"k8s.io/gengo/types"
 | 
						|
)
 | 
						|
 | 
						|
// This clarifies when a pkg path has been canonicalized.
 | 
						|
type importPathString string
 | 
						|
 | 
						|
// Builder lets you add all the go files in all the packages that you care
 | 
						|
// about, then constructs the type source data.
 | 
						|
type Builder struct {
 | 
						|
	context *build.Context
 | 
						|
 | 
						|
	// Map of package names to more canonical information about the package.
 | 
						|
	// This might hold the same value for multiple names, e.g. if someone
 | 
						|
	// referenced ./pkg/name or in the case of vendoring, which canonicalizes
 | 
						|
	// differently that what humans would type.
 | 
						|
	buildPackages map[string]*build.Package
 | 
						|
 | 
						|
	fset *token.FileSet
 | 
						|
	// map of package path to list of parsed files
 | 
						|
	parsed map[importPathString][]parsedFile
 | 
						|
	// map of package path to absolute path (to prevent overlap)
 | 
						|
	absPaths map[importPathString]string
 | 
						|
 | 
						|
	// Set by typeCheckPackage(), used by importPackage() and friends.
 | 
						|
	typeCheckedPackages map[importPathString]*tc.Package
 | 
						|
 | 
						|
	// Map of package path to whether the user requested it or it was from
 | 
						|
	// an import.
 | 
						|
	userRequested map[importPathString]bool
 | 
						|
 | 
						|
	// All comments from everywhere in every parsed file.
 | 
						|
	endLineToCommentGroup map[fileLine]*ast.CommentGroup
 | 
						|
 | 
						|
	// map of package to list of packages it imports.
 | 
						|
	importGraph map[importPathString]map[string]struct{}
 | 
						|
}
 | 
						|
 | 
						|
// parsedFile is for tracking files with name
 | 
						|
type parsedFile struct {
 | 
						|
	name string
 | 
						|
	file *ast.File
 | 
						|
}
 | 
						|
 | 
						|
// key type for finding comments.
 | 
						|
type fileLine struct {
 | 
						|
	file string
 | 
						|
	line int
 | 
						|
}
 | 
						|
 | 
						|
// New constructs a new builder.
 | 
						|
func New() *Builder {
 | 
						|
	c := build.Default
 | 
						|
	if c.GOROOT == "" {
 | 
						|
		if p, err := exec.Command("which", "go").CombinedOutput(); err == nil {
 | 
						|
			// The returned string will have some/path/bin/go, so remove the last two elements.
 | 
						|
			c.GOROOT = filepath.Dir(filepath.Dir(strings.Trim(string(p), "\n")))
 | 
						|
		} else {
 | 
						|
			glog.Warningf("Warning: $GOROOT not set, and unable to run `which go` to find it: %v\n", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// Force this to off, since we don't properly parse CGo.  All symbols must
 | 
						|
	// have non-CGo equivalents.
 | 
						|
	c.CgoEnabled = false
 | 
						|
	return &Builder{
 | 
						|
		context:               &c,
 | 
						|
		buildPackages:         map[string]*build.Package{},
 | 
						|
		typeCheckedPackages:   map[importPathString]*tc.Package{},
 | 
						|
		fset:                  token.NewFileSet(),
 | 
						|
		parsed:                map[importPathString][]parsedFile{},
 | 
						|
		absPaths:              map[importPathString]string{},
 | 
						|
		userRequested:         map[importPathString]bool{},
 | 
						|
		endLineToCommentGroup: map[fileLine]*ast.CommentGroup{},
 | 
						|
		importGraph:           map[importPathString]map[string]struct{}{},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// AddBuildTags adds the specified build tags to the parse context.
 | 
						|
func (b *Builder) AddBuildTags(tags ...string) {
 | 
						|
	b.context.BuildTags = append(b.context.BuildTags, tags...)
 | 
						|
}
 | 
						|
 | 
						|
// Get package information from the go/build package. Automatically excludes
 | 
						|
// e.g. test files and files for other platforms-- there is quite a bit of
 | 
						|
// logic of that nature in the build package.
 | 
						|
func (b *Builder) importBuildPackage(dir string) (*build.Package, error) {
 | 
						|
	if buildPkg, ok := b.buildPackages[dir]; ok {
 | 
						|
		return buildPkg, nil
 | 
						|
	}
 | 
						|
	// This validates the `package foo // github.com/bar/foo` comments.
 | 
						|
	buildPkg, err := b.importWithMode(dir, build.ImportComment)
 | 
						|
	if err != nil {
 | 
						|
		if _, ok := err.(*build.NoGoError); !ok {
 | 
						|
			return nil, fmt.Errorf("unable to import %q: %v", dir, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if buildPkg == nil {
 | 
						|
		// Might be an empty directory. Try to just find the dir.
 | 
						|
		buildPkg, err = b.importWithMode(dir, build.FindOnly)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Remember it under the user-provided name.
 | 
						|
	glog.V(5).Infof("saving buildPackage %s", dir)
 | 
						|
	b.buildPackages[dir] = buildPkg
 | 
						|
	canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath)
 | 
						|
	if dir != string(canonicalPackage) {
 | 
						|
		// Since `dir` is not the canonical name, see if we knew it under another name.
 | 
						|
		if buildPkg, ok := b.buildPackages[string(canonicalPackage)]; ok {
 | 
						|
			return buildPkg, nil
 | 
						|
		}
 | 
						|
		// Must be new, save it under the canonical name, too.
 | 
						|
		glog.V(5).Infof("saving buildPackage %s", canonicalPackage)
 | 
						|
		b.buildPackages[string(canonicalPackage)] = buildPkg
 | 
						|
	}
 | 
						|
 | 
						|
	return buildPkg, nil
 | 
						|
}
 | 
						|
 | 
						|
// AddFileForTest adds a file to the set, without verifying that the provided
 | 
						|
// pkg actually exists on disk. The pkg must be of the form "canonical/pkg/path"
 | 
						|
// and the path must be the absolute path to the file.  Because this bypasses
 | 
						|
// the normal recursive finding of package dependencies (on disk), test should
 | 
						|
// sort their test files topologically first, so all deps are resolved by the
 | 
						|
// time we need them.
 | 
						|
func (b *Builder) AddFileForTest(pkg string, path string, src []byte) error {
 | 
						|
	if err := b.addFile(importPathString(pkg), path, src, true); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if _, err := b.typeCheckPackage(importPathString(pkg)); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// addFile adds a file to the set. The pkgPath must be of the form
 | 
						|
// "canonical/pkg/path" and the path must be the absolute path to the file. A
 | 
						|
// flag indicates whether this file was user-requested or just from following
 | 
						|
// the import graph.
 | 
						|
func (b *Builder) addFile(pkgPath importPathString, path string, src []byte, userRequested bool) error {
 | 
						|
	for _, p := range b.parsed[pkgPath] {
 | 
						|
		if path == p.name {
 | 
						|
			glog.V(5).Infof("addFile %s %s already parsed, skipping", pkgPath, path)
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	glog.V(6).Infof("addFile %s %s", pkgPath, path)
 | 
						|
	p, err := parser.ParseFile(b.fset, path, src, parser.DeclarationErrors|parser.ParseComments)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// This is redundant with addDir, but some tests call AddFileForTest, which
 | 
						|
	// call into here without calling addDir.
 | 
						|
	b.userRequested[pkgPath] = userRequested || b.userRequested[pkgPath]
 | 
						|
 | 
						|
	b.parsed[pkgPath] = append(b.parsed[pkgPath], parsedFile{path, p})
 | 
						|
	for _, c := range p.Comments {
 | 
						|
		position := b.fset.Position(c.End())
 | 
						|
		b.endLineToCommentGroup[fileLine{position.Filename, position.Line}] = c
 | 
						|
	}
 | 
						|
 | 
						|
	// We have to get the packages from this specific file, in case the
 | 
						|
	// user added individual files instead of entire directories.
 | 
						|
	if b.importGraph[pkgPath] == nil {
 | 
						|
		b.importGraph[pkgPath] = map[string]struct{}{}
 | 
						|
	}
 | 
						|
	for _, im := range p.Imports {
 | 
						|
		importedPath := strings.Trim(im.Path.Value, `"`)
 | 
						|
		b.importGraph[pkgPath][importedPath] = struct{}{}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// AddDir adds an entire directory, scanning it for go files. 'dir' should have
 | 
						|
// a single go package in it. GOPATH, GOROOT, and the location of your go
 | 
						|
// binary (`which go`) will all be searched if dir doesn't literally resolve.
 | 
						|
func (b *Builder) AddDir(dir string) error {
 | 
						|
	_, err := b.importPackage(dir, true)
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// AddDirRecursive is just like AddDir, but it also recursively adds
 | 
						|
// subdirectories; it returns an error only if the path couldn't be resolved;
 | 
						|
// any directories recursed into without go source are ignored.
 | 
						|
func (b *Builder) AddDirRecursive(dir string) error {
 | 
						|
	// Add the root.
 | 
						|
	if _, err := b.importPackage(dir, true); err != nil {
 | 
						|
		glog.Warningf("Ignoring directory %v: %v", dir, err)
 | 
						|
	}
 | 
						|
 | 
						|
	// filepath.Walk includes the root dir, but we already did that, so we'll
 | 
						|
	// remove that prefix and rebuild a package import path.
 | 
						|
	prefix := b.buildPackages[dir].Dir
 | 
						|
	fn := func(filePath string, info os.FileInfo, err error) error {
 | 
						|
		if info != nil && info.IsDir() {
 | 
						|
			rel := filepath.ToSlash(strings.TrimPrefix(filePath, prefix))
 | 
						|
			if rel != "" {
 | 
						|
				// Make a pkg path.
 | 
						|
				pkg := path.Join(string(canonicalizeImportPath(b.buildPackages[dir].ImportPath)), rel)
 | 
						|
 | 
						|
				// Add it.
 | 
						|
				if _, err := b.importPackage(pkg, true); err != nil {
 | 
						|
					glog.Warningf("Ignoring child directory %v: %v", pkg, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if err := filepath.Walk(b.buildPackages[dir].Dir, fn); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// AddDirTo adds an entire directory to a given Universe. Unlike AddDir, this
 | 
						|
// processes the package immediately, which makes it safe to use from within a
 | 
						|
// generator (rather than just at init time. 'dir' must be a single go package.
 | 
						|
// GOPATH, GOROOT, and the location of your go binary (`which go`) will all be
 | 
						|
// searched if dir doesn't literally resolve.
 | 
						|
// Deprecated. Please use AddDirectoryTo.
 | 
						|
func (b *Builder) AddDirTo(dir string, u *types.Universe) error {
 | 
						|
	// We want all types from this package, as if they were directly added
 | 
						|
	// by the user.  They WERE added by the user, in effect.
 | 
						|
	if _, err := b.importPackage(dir, true); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return b.findTypesIn(canonicalizeImportPath(b.buildPackages[dir].ImportPath), u)
 | 
						|
}
 | 
						|
 | 
						|
// AddDirectoryTo adds an entire directory to a given Universe. Unlike AddDir,
 | 
						|
// this processes the package immediately, which makes it safe to use from
 | 
						|
// within a generator (rather than just at init time. 'dir' must be a single go
 | 
						|
// package. GOPATH, GOROOT, and the location of your go binary (`which go`)
 | 
						|
// will all be searched if dir doesn't literally resolve.
 | 
						|
func (b *Builder) AddDirectoryTo(dir string, u *types.Universe) (*types.Package, error) {
 | 
						|
	// We want all types from this package, as if they were directly added
 | 
						|
	// by the user.  They WERE added by the user, in effect.
 | 
						|
	if _, err := b.importPackage(dir, true); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	path := canonicalizeImportPath(b.buildPackages[dir].ImportPath)
 | 
						|
	if err := b.findTypesIn(path, u); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return u.Package(string(path)), nil
 | 
						|
}
 | 
						|
 | 
						|
// The implementation of AddDir. A flag indicates whether this directory was
 | 
						|
// user-requested or just from following the import graph.
 | 
						|
func (b *Builder) addDir(dir string, userRequested bool) error {
 | 
						|
	glog.V(5).Infof("addDir %s", dir)
 | 
						|
	buildPkg, err := b.importBuildPackage(dir)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath)
 | 
						|
	pkgPath := canonicalPackage
 | 
						|
	if dir != string(canonicalPackage) {
 | 
						|
		glog.V(5).Infof("addDir %s, canonical path is %s", dir, pkgPath)
 | 
						|
	}
 | 
						|
 | 
						|
	// Sanity check the pkg dir has not changed.
 | 
						|
	if prev, found := b.absPaths[pkgPath]; found {
 | 
						|
		if buildPkg.Dir != prev {
 | 
						|
			return fmt.Errorf("package %q (%s) previously resolved to %s", pkgPath, buildPkg.Dir, prev)
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		b.absPaths[pkgPath] = buildPkg.Dir
 | 
						|
	}
 | 
						|
 | 
						|
	for _, n := range buildPkg.GoFiles {
 | 
						|
		if !strings.HasSuffix(n, ".go") {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		absPath := filepath.Join(buildPkg.Dir, n)
 | 
						|
		data, err := ioutil.ReadFile(absPath)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("while loading %q: %v", absPath, err)
 | 
						|
		}
 | 
						|
		err = b.addFile(pkgPath, absPath, data, userRequested)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("while parsing %q: %v", absPath, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// importPackage is a function that will be called by the type check package when it
 | 
						|
// needs to import a go package. 'path' is the import path.
 | 
						|
func (b *Builder) importPackage(dir string, userRequested bool) (*tc.Package, error) {
 | 
						|
	glog.V(5).Infof("importPackage %s", dir)
 | 
						|
	var pkgPath = importPathString(dir)
 | 
						|
 | 
						|
	// Get the canonical path if we can.
 | 
						|
	if buildPkg := b.buildPackages[dir]; buildPkg != nil {
 | 
						|
		canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath)
 | 
						|
		glog.V(5).Infof("importPackage %s, canonical path is %s", dir, canonicalPackage)
 | 
						|
		pkgPath = canonicalPackage
 | 
						|
	}
 | 
						|
 | 
						|
	// If we have not seen this before, process it now.
 | 
						|
	ignoreError := false
 | 
						|
	if _, found := b.parsed[pkgPath]; !found {
 | 
						|
		// Ignore errors in paths that we're importing solely because
 | 
						|
		// they're referenced by other packages.
 | 
						|
		ignoreError = true
 | 
						|
 | 
						|
		// Add it.
 | 
						|
		if err := b.addDir(dir, userRequested); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		// Get the canonical path now that it has been added.
 | 
						|
		if buildPkg := b.buildPackages[dir]; buildPkg != nil {
 | 
						|
			canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath)
 | 
						|
			glog.V(5).Infof("importPackage %s, canonical path is %s", dir, canonicalPackage)
 | 
						|
			pkgPath = canonicalPackage
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If it was previously known, just check that the user-requestedness hasn't
 | 
						|
	// changed.
 | 
						|
	b.userRequested[pkgPath] = userRequested || b.userRequested[pkgPath]
 | 
						|
 | 
						|
	// Run the type checker.  We may end up doing this to pkgs that are already
 | 
						|
	// done, or are in the queue to be done later, but it will short-circuit,
 | 
						|
	// and we can't miss pkgs that are only depended on.
 | 
						|
	pkg, err := b.typeCheckPackage(pkgPath)
 | 
						|
	if err != nil {
 | 
						|
		switch {
 | 
						|
		case ignoreError && pkg != nil:
 | 
						|
			glog.V(2).Infof("type checking encountered some issues in %q, but ignoring.\n", pkgPath)
 | 
						|
		case !ignoreError && pkg != nil:
 | 
						|
			glog.V(2).Infof("type checking encountered some errors in %q\n", pkgPath)
 | 
						|
			return nil, err
 | 
						|
		default:
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return pkg, nil
 | 
						|
}
 | 
						|
 | 
						|
type importAdapter struct {
 | 
						|
	b *Builder
 | 
						|
}
 | 
						|
 | 
						|
func (a importAdapter) Import(path string) (*tc.Package, error) {
 | 
						|
	return a.b.importPackage(path, false)
 | 
						|
}
 | 
						|
 | 
						|
// typeCheckPackage will attempt to return the package even if there are some
 | 
						|
// errors, so you may check whether the package is nil or not even if you get
 | 
						|
// an error.
 | 
						|
func (b *Builder) typeCheckPackage(pkgPath importPathString) (*tc.Package, error) {
 | 
						|
	glog.V(5).Infof("typeCheckPackage %s", pkgPath)
 | 
						|
	if pkg, ok := b.typeCheckedPackages[pkgPath]; ok {
 | 
						|
		if pkg != nil {
 | 
						|
			glog.V(6).Infof("typeCheckPackage %s already done", pkgPath)
 | 
						|
			return pkg, nil
 | 
						|
		}
 | 
						|
		// We store a nil right before starting work on a package. So
 | 
						|
		// if we get here and it's present and nil, that means there's
 | 
						|
		// another invocation of this function on the call stack
 | 
						|
		// already processing this package.
 | 
						|
		return nil, fmt.Errorf("circular dependency for %q", pkgPath)
 | 
						|
	}
 | 
						|
	parsedFiles, ok := b.parsed[pkgPath]
 | 
						|
	if !ok {
 | 
						|
		return nil, fmt.Errorf("No files for pkg %q: %#v", pkgPath, b.parsed)
 | 
						|
	}
 | 
						|
	files := make([]*ast.File, len(parsedFiles))
 | 
						|
	for i := range parsedFiles {
 | 
						|
		files[i] = parsedFiles[i].file
 | 
						|
	}
 | 
						|
	b.typeCheckedPackages[pkgPath] = nil
 | 
						|
	c := tc.Config{
 | 
						|
		IgnoreFuncBodies: true,
 | 
						|
		// Note that importAdapter can call b.importPackage which calls this
 | 
						|
		// method. So there can't be cycles in the import graph.
 | 
						|
		Importer: importAdapter{b},
 | 
						|
		Error: func(err error) {
 | 
						|
			glog.V(2).Infof("type checker: %v\n", err)
 | 
						|
		},
 | 
						|
	}
 | 
						|
	pkg, err := c.Check(string(pkgPath), b.fset, files, nil)
 | 
						|
	b.typeCheckedPackages[pkgPath] = pkg // record the result whether or not there was an error
 | 
						|
	return pkg, err
 | 
						|
}
 | 
						|
 | 
						|
// FindPackages fetches a list of the user-imported packages.
 | 
						|
// Note that you need to call b.FindTypes() first.
 | 
						|
func (b *Builder) FindPackages() []string {
 | 
						|
	// Iterate packages in a predictable order.
 | 
						|
	pkgPaths := []string{}
 | 
						|
	for k := range b.typeCheckedPackages {
 | 
						|
		pkgPaths = append(pkgPaths, string(k))
 | 
						|
	}
 | 
						|
	sort.Strings(pkgPaths)
 | 
						|
 | 
						|
	result := []string{}
 | 
						|
	for _, pkgPath := range pkgPaths {
 | 
						|
		if b.userRequested[importPathString(pkgPath)] {
 | 
						|
			// Since walkType is recursive, all types that are in packages that
 | 
						|
			// were directly mentioned will be included.  We don't need to
 | 
						|
			// include all types in all transitive packages, though.
 | 
						|
			result = append(result, pkgPath)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result
 | 
						|
}
 | 
						|
 | 
						|
// FindTypes finalizes the package imports, and searches through all the
 | 
						|
// packages for types.
 | 
						|
func (b *Builder) FindTypes() (types.Universe, error) {
 | 
						|
	// Take a snapshot of pkgs to iterate, since this will recursively mutate
 | 
						|
	// b.parsed. Iterate in a predictable order.
 | 
						|
	pkgPaths := []string{}
 | 
						|
	for pkgPath := range b.parsed {
 | 
						|
		pkgPaths = append(pkgPaths, string(pkgPath))
 | 
						|
	}
 | 
						|
	sort.Strings(pkgPaths)
 | 
						|
 | 
						|
	u := types.Universe{}
 | 
						|
	for _, pkgPath := range pkgPaths {
 | 
						|
		if err := b.findTypesIn(importPathString(pkgPath), &u); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return u, nil
 | 
						|
}
 | 
						|
 | 
						|
// findTypesIn finalizes the package import and searches through the package
 | 
						|
// for types.
 | 
						|
func (b *Builder) findTypesIn(pkgPath importPathString, u *types.Universe) error {
 | 
						|
	glog.V(5).Infof("findTypesIn %s", pkgPath)
 | 
						|
	pkg := b.typeCheckedPackages[pkgPath]
 | 
						|
	if pkg == nil {
 | 
						|
		return fmt.Errorf("findTypesIn(%s): package is not known", pkgPath)
 | 
						|
	}
 | 
						|
	if !b.userRequested[pkgPath] {
 | 
						|
		// Since walkType is recursive, all types that the
 | 
						|
		// packages they asked for depend on will be included.
 | 
						|
		// But we don't need to include all types in all
 | 
						|
		// *packages* they depend on.
 | 
						|
		glog.V(5).Infof("findTypesIn %s: package is not user requested", pkgPath)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// We're keeping this package.  This call will create the record.
 | 
						|
	u.Package(string(pkgPath)).Name = pkg.Name()
 | 
						|
	u.Package(string(pkgPath)).Path = pkg.Path()
 | 
						|
	u.Package(string(pkgPath)).SourcePath = b.absPaths[pkgPath]
 | 
						|
 | 
						|
	for _, f := range b.parsed[pkgPath] {
 | 
						|
		if _, fileName := filepath.Split(f.name); fileName == "doc.go" {
 | 
						|
			tp := u.Package(string(pkgPath))
 | 
						|
			// findTypesIn might be called multiple times. Clean up tp.Comments
 | 
						|
			// to avoid repeatedly fill same comments to it.
 | 
						|
			tp.Comments = []string{}
 | 
						|
			for i := range f.file.Comments {
 | 
						|
				tp.Comments = append(tp.Comments, splitLines(f.file.Comments[i].Text())...)
 | 
						|
			}
 | 
						|
			if f.file.Doc != nil {
 | 
						|
				tp.DocComments = splitLines(f.file.Doc.Text())
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	s := pkg.Scope()
 | 
						|
	for _, n := range s.Names() {
 | 
						|
		obj := s.Lookup(n)
 | 
						|
		tn, ok := obj.(*tc.TypeName)
 | 
						|
		if ok {
 | 
						|
			t := b.walkType(*u, nil, tn.Type())
 | 
						|
			c1 := b.priorCommentLines(obj.Pos(), 1)
 | 
						|
			// c1.Text() is safe if c1 is nil
 | 
						|
			t.CommentLines = splitLines(c1.Text())
 | 
						|
			if c1 == nil {
 | 
						|
				t.SecondClosestCommentLines = splitLines(b.priorCommentLines(obj.Pos(), 2).Text())
 | 
						|
			} else {
 | 
						|
				t.SecondClosestCommentLines = splitLines(b.priorCommentLines(c1.List[0].Slash, 2).Text())
 | 
						|
			}
 | 
						|
		}
 | 
						|
		tf, ok := obj.(*tc.Func)
 | 
						|
		// We only care about functions, not concrete/abstract methods.
 | 
						|
		if ok && tf.Type() != nil && tf.Type().(*tc.Signature).Recv() == nil {
 | 
						|
			t := b.addFunction(*u, nil, tf)
 | 
						|
			c1 := b.priorCommentLines(obj.Pos(), 1)
 | 
						|
			// c1.Text() is safe if c1 is nil
 | 
						|
			t.CommentLines = splitLines(c1.Text())
 | 
						|
			if c1 == nil {
 | 
						|
				t.SecondClosestCommentLines = splitLines(b.priorCommentLines(obj.Pos(), 2).Text())
 | 
						|
			} else {
 | 
						|
				t.SecondClosestCommentLines = splitLines(b.priorCommentLines(c1.List[0].Slash, 2).Text())
 | 
						|
			}
 | 
						|
		}
 | 
						|
		tv, ok := obj.(*tc.Var)
 | 
						|
		if ok && !tv.IsField() {
 | 
						|
			b.addVariable(*u, nil, tv)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	importedPkgs := []string{}
 | 
						|
	for k := range b.importGraph[pkgPath] {
 | 
						|
		importedPkgs = append(importedPkgs, string(k))
 | 
						|
	}
 | 
						|
	sort.Strings(importedPkgs)
 | 
						|
	for _, p := range importedPkgs {
 | 
						|
		u.AddImports(string(pkgPath), p)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (b *Builder) importWithMode(dir string, mode build.ImportMode) (*build.Package, error) {
 | 
						|
	// This is a bit of a hack.  The srcDir argument to Import() should
 | 
						|
	// properly be the dir of the file which depends on the package to be
 | 
						|
	// imported, so that vendoring can work properly and local paths can
 | 
						|
	// resolve.  We assume that there is only one level of vendoring, and that
 | 
						|
	// the CWD is inside the GOPATH, so this should be safe. Nobody should be
 | 
						|
	// using local (relative) paths except on the CLI, so CWD is also
 | 
						|
	// sufficient.
 | 
						|
	cwd, err := os.Getwd()
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("unable to get current directory: %v", err)
 | 
						|
	}
 | 
						|
	buildPkg, err := b.context.Import(dir, cwd, mode)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return buildPkg, nil
 | 
						|
}
 | 
						|
 | 
						|
// if there's a comment on the line `lines` before pos, return its text, otherwise "".
 | 
						|
func (b *Builder) priorCommentLines(pos token.Pos, lines int) *ast.CommentGroup {
 | 
						|
	position := b.fset.Position(pos)
 | 
						|
	key := fileLine{position.Filename, position.Line - lines}
 | 
						|
	return b.endLineToCommentGroup[key]
 | 
						|
}
 | 
						|
 | 
						|
func splitLines(str string) []string {
 | 
						|
	return strings.Split(strings.TrimRight(str, "\n"), "\n")
 | 
						|
}
 | 
						|
 | 
						|
func tcFuncNameToName(in string) types.Name {
 | 
						|
	name := strings.TrimPrefix(in, "func ")
 | 
						|
	nameParts := strings.Split(name, "(")
 | 
						|
	return tcNameToName(nameParts[0])
 | 
						|
}
 | 
						|
 | 
						|
func tcVarNameToName(in string) types.Name {
 | 
						|
	nameParts := strings.Split(in, " ")
 | 
						|
	// nameParts[0] is "var".
 | 
						|
	// nameParts[2:] is the type of the variable, we ignore it for now.
 | 
						|
	return tcNameToName(nameParts[1])
 | 
						|
}
 | 
						|
 | 
						|
func tcNameToName(in string) types.Name {
 | 
						|
	// Detect anonymous type names. (These may have '.' characters because
 | 
						|
	// embedded types may have packages, so we detect them specially.)
 | 
						|
	if strings.HasPrefix(in, "struct{") ||
 | 
						|
		strings.HasPrefix(in, "<-chan") ||
 | 
						|
		strings.HasPrefix(in, "chan<-") ||
 | 
						|
		strings.HasPrefix(in, "chan ") ||
 | 
						|
		strings.HasPrefix(in, "func(") ||
 | 
						|
		strings.HasPrefix(in, "*") ||
 | 
						|
		strings.HasPrefix(in, "map[") ||
 | 
						|
		strings.HasPrefix(in, "[") {
 | 
						|
		return types.Name{Name: in}
 | 
						|
	}
 | 
						|
 | 
						|
	// Otherwise, if there are '.' characters present, the name has a
 | 
						|
	// package path in front.
 | 
						|
	nameParts := strings.Split(in, ".")
 | 
						|
	name := types.Name{Name: in}
 | 
						|
	if n := len(nameParts); n >= 2 {
 | 
						|
		// The final "." is the name of the type--previous ones must
 | 
						|
		// have been in the package path.
 | 
						|
		name.Package, name.Name = strings.Join(nameParts[:n-1], "."), nameParts[n-1]
 | 
						|
	}
 | 
						|
	return name
 | 
						|
}
 | 
						|
 | 
						|
func (b *Builder) convertSignature(u types.Universe, t *tc.Signature) *types.Signature {
 | 
						|
	signature := &types.Signature{}
 | 
						|
	for i := 0; i < t.Params().Len(); i++ {
 | 
						|
		signature.Parameters = append(signature.Parameters, b.walkType(u, nil, t.Params().At(i).Type()))
 | 
						|
	}
 | 
						|
	for i := 0; i < t.Results().Len(); i++ {
 | 
						|
		signature.Results = append(signature.Results, b.walkType(u, nil, t.Results().At(i).Type()))
 | 
						|
	}
 | 
						|
	if r := t.Recv(); r != nil {
 | 
						|
		signature.Receiver = b.walkType(u, nil, r.Type())
 | 
						|
	}
 | 
						|
	signature.Variadic = t.Variadic()
 | 
						|
	return signature
 | 
						|
}
 | 
						|
 | 
						|
// walkType adds the type, and any necessary child types.
 | 
						|
func (b *Builder) walkType(u types.Universe, useName *types.Name, in tc.Type) *types.Type {
 | 
						|
	// Most of the cases are underlying types of the named type.
 | 
						|
	name := tcNameToName(in.String())
 | 
						|
	if useName != nil {
 | 
						|
		name = *useName
 | 
						|
	}
 | 
						|
 | 
						|
	switch t := in.(type) {
 | 
						|
	case *tc.Struct:
 | 
						|
		out := u.Type(name)
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Struct
 | 
						|
		for i := 0; i < t.NumFields(); i++ {
 | 
						|
			f := t.Field(i)
 | 
						|
			m := types.Member{
 | 
						|
				Name:         f.Name(),
 | 
						|
				Embedded:     f.Anonymous(),
 | 
						|
				Tags:         t.Tag(i),
 | 
						|
				Type:         b.walkType(u, nil, f.Type()),
 | 
						|
				CommentLines: splitLines(b.priorCommentLines(f.Pos(), 1).Text()),
 | 
						|
			}
 | 
						|
			out.Members = append(out.Members, m)
 | 
						|
		}
 | 
						|
		return out
 | 
						|
	case *tc.Map:
 | 
						|
		out := u.Type(name)
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Map
 | 
						|
		out.Elem = b.walkType(u, nil, t.Elem())
 | 
						|
		out.Key = b.walkType(u, nil, t.Key())
 | 
						|
		return out
 | 
						|
	case *tc.Pointer:
 | 
						|
		out := u.Type(name)
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Pointer
 | 
						|
		out.Elem = b.walkType(u, nil, t.Elem())
 | 
						|
		return out
 | 
						|
	case *tc.Slice:
 | 
						|
		out := u.Type(name)
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Slice
 | 
						|
		out.Elem = b.walkType(u, nil, t.Elem())
 | 
						|
		return out
 | 
						|
	case *tc.Array:
 | 
						|
		out := u.Type(name)
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Array
 | 
						|
		out.Elem = b.walkType(u, nil, t.Elem())
 | 
						|
		// TODO: need to store array length, otherwise raw type name
 | 
						|
		// cannot be properly written.
 | 
						|
		return out
 | 
						|
	case *tc.Chan:
 | 
						|
		out := u.Type(name)
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Chan
 | 
						|
		out.Elem = b.walkType(u, nil, t.Elem())
 | 
						|
		// TODO: need to store direction, otherwise raw type name
 | 
						|
		// cannot be properly written.
 | 
						|
		return out
 | 
						|
	case *tc.Basic:
 | 
						|
		out := u.Type(types.Name{
 | 
						|
			Package: "",
 | 
						|
			Name:    t.Name(),
 | 
						|
		})
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Unsupported
 | 
						|
		return out
 | 
						|
	case *tc.Signature:
 | 
						|
		out := u.Type(name)
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Func
 | 
						|
		out.Signature = b.convertSignature(u, t)
 | 
						|
		return out
 | 
						|
	case *tc.Interface:
 | 
						|
		out := u.Type(name)
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Interface
 | 
						|
		t.Complete()
 | 
						|
		for i := 0; i < t.NumMethods(); i++ {
 | 
						|
			if out.Methods == nil {
 | 
						|
				out.Methods = map[string]*types.Type{}
 | 
						|
			}
 | 
						|
			out.Methods[t.Method(i).Name()] = b.walkType(u, nil, t.Method(i).Type())
 | 
						|
		}
 | 
						|
		return out
 | 
						|
	case *tc.Named:
 | 
						|
		var out *types.Type
 | 
						|
		switch t.Underlying().(type) {
 | 
						|
		case *tc.Named, *tc.Basic, *tc.Map, *tc.Slice:
 | 
						|
			name := tcNameToName(t.String())
 | 
						|
			out = u.Type(name)
 | 
						|
			if out.Kind != types.Unknown {
 | 
						|
				return out
 | 
						|
			}
 | 
						|
			out.Kind = types.Alias
 | 
						|
			out.Underlying = b.walkType(u, nil, t.Underlying())
 | 
						|
		default:
 | 
						|
			// tc package makes everything "named" with an
 | 
						|
			// underlying anonymous type--we remove that annoying
 | 
						|
			// "feature" for users. This flattens those types
 | 
						|
			// together.
 | 
						|
			name := tcNameToName(t.String())
 | 
						|
			if out := u.Type(name); out.Kind != types.Unknown {
 | 
						|
				return out // short circuit if we've already made this.
 | 
						|
			}
 | 
						|
			out = b.walkType(u, &name, t.Underlying())
 | 
						|
		}
 | 
						|
		// If the underlying type didn't already add methods, add them.
 | 
						|
		// (Interface types will have already added methods.)
 | 
						|
		if len(out.Methods) == 0 {
 | 
						|
			for i := 0; i < t.NumMethods(); i++ {
 | 
						|
				if out.Methods == nil {
 | 
						|
					out.Methods = map[string]*types.Type{}
 | 
						|
				}
 | 
						|
				out.Methods[t.Method(i).Name()] = b.walkType(u, nil, t.Method(i).Type())
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return out
 | 
						|
	default:
 | 
						|
		out := u.Type(name)
 | 
						|
		if out.Kind != types.Unknown {
 | 
						|
			return out
 | 
						|
		}
 | 
						|
		out.Kind = types.Unsupported
 | 
						|
		glog.Warningf("Making unsupported type entry %q for: %#v\n", out, t)
 | 
						|
		return out
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (b *Builder) addFunction(u types.Universe, useName *types.Name, in *tc.Func) *types.Type {
 | 
						|
	name := tcFuncNameToName(in.String())
 | 
						|
	if useName != nil {
 | 
						|
		name = *useName
 | 
						|
	}
 | 
						|
	out := u.Function(name)
 | 
						|
	out.Kind = types.DeclarationOf
 | 
						|
	out.Underlying = b.walkType(u, nil, in.Type())
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
func (b *Builder) addVariable(u types.Universe, useName *types.Name, in *tc.Var) *types.Type {
 | 
						|
	name := tcVarNameToName(in.String())
 | 
						|
	if useName != nil {
 | 
						|
		name = *useName
 | 
						|
	}
 | 
						|
	out := u.Variable(name)
 | 
						|
	out.Kind = types.DeclarationOf
 | 
						|
	out.Underlying = b.walkType(u, nil, in.Type())
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
// canonicalizeImportPath takes an import path and returns the actual package.
 | 
						|
// It doesn't support nested vendoring.
 | 
						|
func canonicalizeImportPath(importPath string) importPathString {
 | 
						|
	if !strings.Contains(importPath, "/vendor/") {
 | 
						|
		return importPathString(importPath)
 | 
						|
	}
 | 
						|
 | 
						|
	return importPathString(importPath[strings.Index(importPath, "/vendor/")+len("/vendor/"):])
 | 
						|
}
 |