godoc: index import counts, package name to path(s), and exported symbols
R=golang-dev, crawshaw
CC=golang-dev
https://golang.org/cl/22190047
diff --git a/godoc/index.go b/godoc/index.go
index 34c4c35..c99b8e7 100644
--- a/godoc/index.go
+++ b/godoc/index.go
@@ -55,6 +55,7 @@
"regexp"
"runtime"
"sort"
+ "strconv"
"strings"
"sync"
"time"
@@ -377,16 +378,29 @@
fset *token.FileSet // file set for all indexed files
fsOpenGate chan bool // send pre fs.Open; receive on close
- mu sync.Mutex // guards all the following
- sources bytes.Buffer // concatenated sources
- packages map[string]*Pak // map of canonicalized *Paks
- words map[string]*IndexResult // RunLists of Spots
- snippets []*Snippet // indices are stored in SpotInfos
- current *token.File // last file added to file set
- file *File // AST for current file
- decl ast.Decl // AST for current decl
- stats Statistics
- throttle *util.Throttle
+ mu sync.Mutex // guards all the following
+ sources bytes.Buffer // concatenated sources
+ strings map[string]string // interned string
+ packages map[Pak]*Pak // interned *Paks
+ words map[string]*IndexResult // RunLists of Spots
+ snippets []*Snippet // indices are stored in SpotInfos
+ current *token.File // last file added to file set
+ file *File // AST for current file
+ decl ast.Decl // AST for current decl
+ stats Statistics
+ throttle *util.Throttle
+ importCount map[string]int // package path ("net/http") => count
+ packagePath map[string]map[string]bool // "template" => "text/template" => true
+ exports map[string]map[string]SpotKind // "net/http" => "ListenAndServe" => FuncDecl
+ curPkgExports map[string]SpotKind
+}
+
+func (x *Indexer) intern(s string) string {
+ if s, ok := x.strings[s]; ok {
+ return s
+ }
+ x.strings[s] = s
+ return s
}
func (x *Indexer) lookupPackage(path, name string) *Pak {
@@ -394,10 +408,10 @@
// live in the same directory. For the packages map, construct
// a key that includes both the directory path and the package
// name.
- key := path + ":" + name
+ key := Pak{Path: x.intern(path), Name: x.intern(name)}
pak := x.packages[key]
if pak == nil {
- pak = &Pak{path, name}
+ pak = &key
x.packages[key] = pak
}
return pak
@@ -410,26 +424,34 @@
}
func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) {
- if id != nil {
- lists, found := x.words[id.Name]
- if !found {
- lists = new(IndexResult)
- x.words[id.Name] = lists
- }
-
- if kind == Use || x.decl == nil {
- // not a declaration or no snippet required
- info := makeSpotInfo(kind, x.current.Line(id.Pos()), false)
- lists.Others = append(lists.Others, Spot{x.file, info})
- } else {
- // a declaration with snippet
- index := x.addSnippet(NewSnippet(x.fset, x.decl, id))
- info := makeSpotInfo(kind, index, true)
- lists.Decls = append(lists.Decls, Spot{x.file, info})
- }
-
- x.stats.Spots++
+ if id == nil {
+ return
}
+ name := x.intern(id.Name)
+
+ switch kind {
+ case TypeDecl, FuncDecl:
+ x.curPkgExports[name] = kind
+ }
+
+ lists, found := x.words[name]
+ if !found {
+ lists = new(IndexResult)
+ x.words[name] = lists
+ }
+
+ if kind == Use || x.decl == nil {
+ // not a declaration or no snippet required
+ info := makeSpotInfo(kind, x.current.Line(id.Pos()), false)
+ lists.Others = append(lists.Others, Spot{x.file, info})
+ } else {
+ // a declaration with snippet
+ index := x.addSnippet(NewSnippet(x.fset, x.decl, id))
+ info := makeSpotInfo(kind, index, true)
+ lists.Decls = append(lists.Decls, Spot{x.file, info})
+ }
+
+ x.stats.Spots++
}
func (x *Indexer) visitFieldList(kind SpotKind, flist *ast.FieldList) {
@@ -447,7 +469,11 @@
switch n := spec.(type) {
case *ast.ImportSpec:
x.visitIdent(ImportDecl, n.Name)
- // ignore path - not indexed at the moment
+ if n.Path != nil {
+ if imp, err := strconv.Unquote(n.Path.Value); err == nil {
+ x.importCount[x.intern(imp)]++
+ }
+ }
case *ast.ValueSpec:
for _, n := range n.Names {
@@ -678,6 +704,7 @@
x.throttle.Throttle()
+ x.curPkgExports = make(map[string]SpotKind)
file, fast := x.addFile(f, filename, goFile)
if file == nil {
return // addFile failed
@@ -689,6 +716,26 @@
pak := x.lookupPackage(dirname, fast.Name.Name)
x.file = &File{fi.Name(), pak}
ast.Walk(x, fast)
+
+ ppKey := x.intern(fast.Name.Name)
+ if _, ok := x.packagePath[ppKey]; !ok {
+ x.packagePath[ppKey] = make(map[string]bool)
+ }
+ pkgPath := x.intern(strings.TrimPrefix(dirname, "/src/pkg/"))
+ x.packagePath[ppKey][pkgPath] = true
+
+ // Merge in exported symbols found walking this file into
+ // the map for that package.
+ if len(x.curPkgExports) > 0 {
+ dest, ok := x.exports[pkgPath]
+ if !ok {
+ dest = make(map[string]SpotKind)
+ x.exports[pkgPath] = dest
+ }
+ for k, v := range x.curPkgExports {
+ dest[k] = v
+ }
+ }
}
// update statistics
@@ -706,12 +753,15 @@
}
type Index struct {
- fset *token.FileSet // file set used during indexing; nil if no textindex
- suffixes *suffixarray.Index // suffixes for concatenated sources; nil if no textindex
- words map[string]*LookupResult // maps words to hit lists
- alts map[string]*AltWords // maps canonical(words) to lists of alternative spellings
- snippets []*Snippet // all snippets, indexed by snippet index
- stats Statistics
+ fset *token.FileSet // file set used during indexing; nil if no textindex
+ suffixes *suffixarray.Index // suffixes for concatenated sources; nil if no textindex
+ words map[string]*LookupResult // maps words to hit lists
+ alts map[string]*AltWords // maps canonical(words) to lists of alternative spellings
+ snippets []*Snippet // all snippets, indexed by snippet index
+ stats Statistics
+ importCount map[string]int // package path ("net/http") => count
+ packagePath map[string]map[string]bool // "template" => "text/template" => true
+ exports map[string]map[string]SpotKind // "net/http" => "ListenAndServe" => FuncDecl
}
func canonical(w string) string { return strings.ToLower(w) }
@@ -733,12 +783,16 @@
// initialize Indexer
// (use some reasonably sized maps to start)
x := &Indexer{
- c: c,
- fset: token.NewFileSet(),
- fsOpenGate: make(chan bool, maxOpenFiles),
- packages: make(map[string]*Pak, 256),
- words: make(map[string]*IndexResult, 8192),
- throttle: util.NewThrottle(throttle, 100*time.Millisecond), // run at least 0.1s at a time
+ c: c,
+ fset: token.NewFileSet(),
+ fsOpenGate: make(chan bool, maxOpenFiles),
+ strings: make(map[string]string),
+ packages: make(map[Pak]*Pak, 256),
+ words: make(map[string]*IndexResult, 8192),
+ throttle: util.NewThrottle(throttle, 100*time.Millisecond), // run at least 0.1s at a time
+ importCount: make(map[string]int),
+ packagePath: make(map[string]map[string]bool),
+ exports: make(map[string]map[string]SpotKind),
}
// index all files in the directories given by dirnames
@@ -813,7 +867,17 @@
suffixes = suffixarray.New(x.sources.Bytes())
}
- return &Index{x.fset, suffixes, words, alts, x.snippets, x.stats}
+ return &Index{
+ fset: x.fset,
+ suffixes: suffixes,
+ words: words,
+ alts: alts,
+ snippets: x.snippets,
+ stats: x.stats,
+ importCount: x.importCount,
+ packagePath: x.packagePath,
+ exports: x.exports,
+ }
}
type fileIndex struct {
@@ -890,11 +954,28 @@
return nil
}
-// Stats() returns index statistics.
+// Stats returns index statistics.
func (x *Index) Stats() Statistics {
return x.stats
}
+// ImportCount returns a map from import paths to how many times they were seen.
+func (x *Index) ImportCount() map[string]int {
+ return x.importCount
+}
+
+// PackagePath returns a map from short package name to a set
+// of full package path names that use that short package name.
+func (x *Index) PackagePath() map[string]map[string]bool {
+ return x.packagePath
+}
+
+// Exports returns a map from full package path to exported
+// symbol name to its type.
+func (x *Index) Exports() map[string]map[string]SpotKind {
+ return x.exports
+}
+
func (x *Index) lookupWord(w string) (match *LookupResult, alt *AltWords) {
match = x.words[w]
alt = x.alts[canonical(w)]
diff --git a/godoc/index_test.go b/godoc/index_test.go
index 001d2a7..8a5a473 100644
--- a/godoc/index_test.go
+++ b/godoc/index_test.go
@@ -17,6 +17,8 @@
"src/pkg/foo/foo.go": `// Package foo is an example.
package foo
+import "bar"
+
// Foo is stuff.
type Foo struct{}
@@ -27,6 +29,10 @@
"src/pkg/bar/bar.go": `// Package bar is another example to test races.
package bar
`,
+ "src/pkg/other/bar/bar.go": `// Package bar is another bar package.
+package bar
+func X() {}
+`,
"src/pkg/skip/skip.go": `// Package skip should be skipped.
package skip
func Skip() {}
@@ -46,11 +52,43 @@
t.Fatal("no index")
}
t.Logf("Got: %#v", ix)
- wantStats := Statistics{Bytes: 179, Files: 2, Lines: 11, Words: 5, Spots: 7}
+
+ wantStats := Statistics{Bytes: 256, Files: 3, Lines: 16, Words: 6, Spots: 9}
if !reflect.DeepEqual(ix.Stats(), wantStats) {
t.Errorf("Stats = %#v; want %#v", ix.Stats(), wantStats)
}
+
if _, ok := ix.words["Skip"]; ok {
t.Errorf("the word Skip was found; expected it to be skipped")
}
+
+ if got, want := ix.ImportCount(), map[string]int{
+ "bar": 1,
+ }; !reflect.DeepEqual(got, want) {
+ t.Errorf("ImportCount = %v; want %v", got, want)
+ }
+
+ if got, want := ix.PackagePath(), map[string]map[string]bool{
+ "foo": map[string]bool{
+ "foo": true,
+ },
+ "bar": map[string]bool{
+ "bar": true,
+ "other/bar": true,
+ },
+ }; !reflect.DeepEqual(got, want) {
+ t.Errorf("PackagePath = %v; want %v", got, want)
+ }
+
+ if got, want := ix.Exports(), map[string]map[string]SpotKind{
+ "foo": map[string]SpotKind{
+ "Foo": TypeDecl,
+ "New": FuncDecl,
+ },
+ "other/bar": map[string]SpotKind{
+ "X": FuncDecl,
+ },
+ }; !reflect.DeepEqual(got, want) {
+ t.Errorf("Exports = %v; want %v", got, want)
+ }
}