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 }
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 }