Esempio n. 1
0
func (g *Grapher) Graph(pkgInfo *loader.PackageInfo) error {
	if len(pkgInfo.Files) == 0 {
		log.Printf("warning: attempted to graph package %+v with no files", pkgInfo)
		return nil
	}

	seen := make(map[ast.Node]struct{})
	skipResolveObjs := make(map[types.Object]struct{})

	for node, obj := range pkgInfo.Implicits {
		if importSpec, ok := node.(*ast.ImportSpec); ok {
			ref, err := g.NewRef(importSpec, obj)
			if err != nil {
				return err
			}
			g.addRef(ref)
			seen[importSpec] = struct{}{}
		} else if x, ok := node.(*ast.Ident); ok {
			g.skipResolve[x] = struct{}{}
		} else if _, ok := node.(*ast.CaseClause); ok {
			// type-specific *Var for each type switch case clause
			skipResolveObjs[obj] = struct{}{}
		}
	}

	pkgDef, err := g.NewPackageDef(pkgInfo, pkgInfo.Pkg)
	if err != nil {
		return err
	}
	g.addDef(pkgDef)

	for ident, obj := range pkgInfo.Defs {
		_, isLabel := obj.(*types.Label)
		if obj == nil || ident.Name == "_" || isLabel {
			g.skipResolve[ident] = struct{}{}
			continue
		}

		if v, isVar := obj.(*types.Var); isVar && obj.Pos() != ident.Pos() && !v.IsField() {
			// If this is an assign statement reassignment of existing var, treat this as a
			// use (not a def).
			pkgInfo.Uses[ident] = obj
			continue
		}

		// don't treat import aliases as things that belong to this package
		_, isPkg := obj.(*types.PkgName)

		if !isPkg {
			def, err := g.NewDef(obj, ident)
			if err != nil {
				return err
			}
			g.addDef(def)
		}

		ref, err := g.NewRef(ident, obj)
		if err != nil {
			return err
		}
		ref.IsDef = true
		g.addRef(ref)
	}

	for ident, obj := range pkgInfo.Uses {
		if _, isLabel := obj.(*types.Label); isLabel {
			g.skipResolve[ident] = struct{}{}
			continue
		}

		if obj == nil || ident == nil || ident.Name == "_" {
			continue
		}

		if _, skip := skipResolveObjs[obj]; skip {
			g.skipResolve[ident] = struct{}{}
		}

		if _, seen := seen[ident]; seen {
			continue
		}

		if _, isLabel := obj.(*types.Label); isLabel {
			continue
		}

		ref, err := g.NewRef(ident, obj)
		if err != nil {
			return err
		}
		g.addRef(ref)
	}

	// Create a ref that represent the name of the package ("package foo")
	// for each file.
	for _, f := range pkgInfo.Files {
		pkgObj := types.NewPkgName(f.Name.Pos(), pkgInfo.Pkg, pkgInfo.Pkg.Name(), pkgInfo.Pkg)
		ref, err := g.NewRef(f.Name, pkgObj)
		if err != nil {
			return err
		}
		g.addRef(ref)
	}

	if !g.SkipDocs {
		err = g.emitDocs(pkgInfo)
		if err != nil {
			return err
		}
	}

	return nil
}
// Referrers reports all identifiers that resolve to the same object
// as the queried identifier, within any package in the analysis scope.
func referrers(q *Query) error {
	lconf := loader.Config{Build: q.Build}
	allowErrors(&lconf)

	if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
		return err
	}

	var id *ast.Ident
	var obj types.Object
	var lprog *loader.Program
	var pass2 bool
	var qpos *queryPos
	for {
		// Load/parse/type-check the program.
		var err error
		lprog, err = lconf.Load()
		if err != nil {
			return err
		}
		q.Fset = lprog.Fset

		qpos, err = parseQueryPos(lprog, q.Pos, false)
		if err != nil {
			return err
		}

		id, _ = qpos.path[0].(*ast.Ident)
		if id == nil {
			return fmt.Errorf("no identifier here")
		}

		obj = qpos.info.ObjectOf(id)
		if obj == nil {
			// Happens for y in "switch y := x.(type)",
			// the package declaration,
			// and unresolved identifiers.
			if _, ok := qpos.path[1].(*ast.File); ok { // package decl?
				pkg := qpos.info.Pkg
				obj = types.NewPkgName(id.Pos(), pkg, pkg.Name(), pkg)
			} else {
				return fmt.Errorf("no object for identifier: %T", qpos.path[1])
			}
		}

		if pass2 {
			break
		}

		// If the identifier is exported, we must load all packages that
		// depend transitively upon the package that defines it.
		// Treat PkgNames as exported, even though they're lowercase.
		if _, isPkg := obj.(*types.PkgName); !(isPkg || obj.Exported()) {
			break // not exported
		}

		// Scan the workspace and build the import graph.
		// Ignore broken packages.
		_, rev, _ := importgraph.Build(q.Build)

		// Re-load the larger program.
		// Create a new file set so that ...
		// External test packages are never imported,
		// so they will never appear in the graph.
		// (We must reset the Config here, not just reset the Fset field.)
		lconf = loader.Config{
			Fset:  token.NewFileSet(),
			Build: q.Build,
		}
		allowErrors(&lconf)
		for path := range rev.Search(obj.Pkg().Path()) {
			lconf.ImportWithTests(path)
		}
		pass2 = true
	}

	// Iterate over all go/types' Uses facts for the entire program.
	var refs []*ast.Ident
	for _, info := range lprog.AllPackages {
		for id2, obj2 := range info.Uses {
			if sameObj(obj, obj2) {
				refs = append(refs, id2)
			}
		}
	}
	sort.Sort(byNamePos{q.Fset, refs})

	q.result = &referrersResult{
		qpos:  qpos,
		query: id,
		obj:   obj,
		refs:  refs,
	}
	return nil
}
Esempio n. 3
0
func (g *Grapher) emitDocs(pkgInfo *loader.PackageInfo) error {
	objOf := make(map[token.Position]types.Object, len(pkgInfo.Defs))
	for ident, obj := range pkgInfo.Defs {
		objOf[g.program.Fset.Position(ident.Pos())] = obj
	}

	var filenames []string
	for _, f := range pkgInfo.Files {
		name := g.program.Fset.Position(f.Name.Pos()).Filename
		if filepath.Base(name) == "C" {
			// skip cgo-generated file
			continue
		}
		if path.Ext(name) == ".go" {
			filenames = append(filenames, name)
		}
	}
	sort.Strings(filenames)
	files, err := parseFiles(g.program.Fset, filenames)
	if err != nil {
		return err
	}

	// First we collect all of the Doc comments from the files,
	// which will make up the Doc for the package. If more than
	// one file has a doc associated with it, append them
	// together.
	pkgDoc := ""
	for _, f := range files {
		if f.Doc == nil {
			continue
		}
		if pkgDoc == "" {
			pkgDoc = f.Doc.Text()
			continue
		}
		pkgDoc += "\n" + f.Doc.Text()
	}
	g.emitDoc(types.NewPkgName(0, pkgInfo.Pkg, pkgInfo.Pkg.Path(), pkgInfo.Pkg), nil, pkgDoc, "")

	// We walk the AST for comments attached to nodes.
	for filename, f := range files {
		// docSeen is a map from the starting byte of a doc to
		// an empty struct.
		docSeen := make(map[token.Pos]struct{})
		ast.Inspect(f, func(node ast.Node) bool {
			switch n := node.(type) {
			case *ast.Field:
				if n.Doc == nil {
					return true
				}
				for _, i := range n.Names {
					if g.emitDoc(objOf[g.program.Fset.Position(i.Pos())], n.Doc, n.Doc.Text(), filename) {
						docSeen[n.Doc.Pos()] = struct{}{}
					}
				}
			case *ast.FuncDecl:
				if n.Doc == nil || n.Name == nil {
					return true
				}
				if g.emitDoc(objOf[g.program.Fset.Position(n.Name.Pos())], n.Doc, n.Doc.Text(), filename) {
					docSeen[n.Doc.Pos()] = struct{}{}
				}
			case *ast.GenDecl:
				for _, spec := range n.Specs {
					switch spec := spec.(type) {
					case *ast.ValueSpec:
						for _, name := range spec.Names {
							c := firstNonNil(spec.Doc, spec.Comment, n.Doc)
							if g.emitDoc(objOf[g.program.Fset.Position(name.Pos())], c, c.Text(), filename) {
								docSeen[c.Pos()] = struct{}{}
							}
						}
					case *ast.TypeSpec:
						c := firstNonNil(spec.Doc, spec.Comment, n.Doc)
						if g.emitDoc(objOf[g.program.Fset.Position(spec.Name.Pos())], c, c.Text(), filename) {
							docSeen[c.Pos()] = struct{}{}
						}
					}
				}
			case *ast.ImportSpec:
				if n.Doc == nil || n.Name == nil {
					return true
				}
				if g.emitDoc(objOf[g.program.Fset.Position(n.Name.Pos())], n.Doc, n.Doc.Text(), filename) {
					docSeen[n.Doc.Pos()] = struct{}{}
				}
			case *ast.TypeSpec:
				if n.Doc == nil || n.Name == nil {
					return true
				}
				if g.emitDoc(objOf[g.program.Fset.Position(n.Name.Pos())], n.Doc, n.Doc.Text(), filename) {
					docSeen[n.Doc.Pos()] = struct{}{}
				}
			case *ast.ValueSpec:
				if n.Doc == nil {
					return true
				}
				for _, i := range n.Names {
					if g.emitDoc(objOf[g.program.Fset.Position(i.Pos())], n.Doc, n.Doc.Text(), filename) {
						docSeen[n.Doc.Pos()] = struct{}{}
					}
				}
			}
			return true
		})
		// Add comments that haven't already been seen.
		for _, c := range f.Comments {
			if _, seen := docSeen[c.Pos()]; !seen {
				g.emitDoc(nil, c, c.Text(), filename)
			}
		}
	}
	return nil
}