godoc: index import counts, package name to path(s), and exported symbols
R=golang-dev, crawshaw CC=golang-dev https://golang.org/cl/22190047
This commit is contained in:
parent
42513df8b8
commit
56a1b4d0b7
173
godoc/index.go
173
godoc/index.go
|
@ -55,6 +55,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -377,16 +378,29 @@ type Indexer struct {
|
||||||
fset *token.FileSet // file set for all indexed files
|
fset *token.FileSet // file set for all indexed files
|
||||||
fsOpenGate chan bool // send pre fs.Open; receive on close
|
fsOpenGate chan bool // send pre fs.Open; receive on close
|
||||||
|
|
||||||
mu sync.Mutex // guards all the following
|
mu sync.Mutex // guards all the following
|
||||||
sources bytes.Buffer // concatenated sources
|
sources bytes.Buffer // concatenated sources
|
||||||
packages map[string]*Pak // map of canonicalized *Paks
|
strings map[string]string // interned string
|
||||||
words map[string]*IndexResult // RunLists of Spots
|
packages map[Pak]*Pak // interned *Paks
|
||||||
snippets []*Snippet // indices are stored in SpotInfos
|
words map[string]*IndexResult // RunLists of Spots
|
||||||
current *token.File // last file added to file set
|
snippets []*Snippet // indices are stored in SpotInfos
|
||||||
file *File // AST for current file
|
current *token.File // last file added to file set
|
||||||
decl ast.Decl // AST for current decl
|
file *File // AST for current file
|
||||||
stats Statistics
|
decl ast.Decl // AST for current decl
|
||||||
throttle *util.Throttle
|
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 {
|
func (x *Indexer) lookupPackage(path, name string) *Pak {
|
||||||
|
@ -394,10 +408,10 @@ func (x *Indexer) lookupPackage(path, name string) *Pak {
|
||||||
// live in the same directory. For the packages map, construct
|
// live in the same directory. For the packages map, construct
|
||||||
// a key that includes both the directory path and the package
|
// a key that includes both the directory path and the package
|
||||||
// name.
|
// name.
|
||||||
key := path + ":" + name
|
key := Pak{Path: x.intern(path), Name: x.intern(name)}
|
||||||
pak := x.packages[key]
|
pak := x.packages[key]
|
||||||
if pak == nil {
|
if pak == nil {
|
||||||
pak = &Pak{path, name}
|
pak = &key
|
||||||
x.packages[key] = pak
|
x.packages[key] = pak
|
||||||
}
|
}
|
||||||
return pak
|
return pak
|
||||||
|
@ -410,26 +424,34 @@ func (x *Indexer) addSnippet(s *Snippet) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) {
|
func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) {
|
||||||
if id != nil {
|
if id == nil {
|
||||||
lists, found := x.words[id.Name]
|
return
|
||||||
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++
|
|
||||||
}
|
}
|
||||||
|
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) {
|
func (x *Indexer) visitFieldList(kind SpotKind, flist *ast.FieldList) {
|
||||||
|
@ -447,7 +469,11 @@ func (x *Indexer) visitSpec(kind SpotKind, spec ast.Spec) {
|
||||||
switch n := spec.(type) {
|
switch n := spec.(type) {
|
||||||
case *ast.ImportSpec:
|
case *ast.ImportSpec:
|
||||||
x.visitIdent(ImportDecl, n.Name)
|
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:
|
case *ast.ValueSpec:
|
||||||
for _, n := range n.Names {
|
for _, n := range n.Names {
|
||||||
|
@ -678,6 +704,7 @@ func (x *Indexer) visitFile(dirname string, fi os.FileInfo, fulltextIndex bool)
|
||||||
|
|
||||||
x.throttle.Throttle()
|
x.throttle.Throttle()
|
||||||
|
|
||||||
|
x.curPkgExports = make(map[string]SpotKind)
|
||||||
file, fast := x.addFile(f, filename, goFile)
|
file, fast := x.addFile(f, filename, goFile)
|
||||||
if file == nil {
|
if file == nil {
|
||||||
return // addFile failed
|
return // addFile failed
|
||||||
|
@ -689,6 +716,26 @@ func (x *Indexer) visitFile(dirname string, fi os.FileInfo, fulltextIndex bool)
|
||||||
pak := x.lookupPackage(dirname, fast.Name.Name)
|
pak := x.lookupPackage(dirname, fast.Name.Name)
|
||||||
x.file = &File{fi.Name(), pak}
|
x.file = &File{fi.Name(), pak}
|
||||||
ast.Walk(x, fast)
|
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
|
// update statistics
|
||||||
|
@ -706,12 +753,15 @@ type LookupResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Index struct {
|
type Index struct {
|
||||||
fset *token.FileSet // file set used during indexing; nil if no textindex
|
fset *token.FileSet // file set used during indexing; nil if no textindex
|
||||||
suffixes *suffixarray.Index // suffixes for concatenated sources; nil if no textindex
|
suffixes *suffixarray.Index // suffixes for concatenated sources; nil if no textindex
|
||||||
words map[string]*LookupResult // maps words to hit lists
|
words map[string]*LookupResult // maps words to hit lists
|
||||||
alts map[string]*AltWords // maps canonical(words) to lists of alternative spellings
|
alts map[string]*AltWords // maps canonical(words) to lists of alternative spellings
|
||||||
snippets []*Snippet // all snippets, indexed by snippet index
|
snippets []*Snippet // all snippets, indexed by snippet index
|
||||||
stats Statistics
|
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) }
|
func canonical(w string) string { return strings.ToLower(w) }
|
||||||
|
@ -733,12 +783,16 @@ func NewIndex(c *Corpus, dirnames <-chan string, fulltextIndex bool, throttle fl
|
||||||
// initialize Indexer
|
// initialize Indexer
|
||||||
// (use some reasonably sized maps to start)
|
// (use some reasonably sized maps to start)
|
||||||
x := &Indexer{
|
x := &Indexer{
|
||||||
c: c,
|
c: c,
|
||||||
fset: token.NewFileSet(),
|
fset: token.NewFileSet(),
|
||||||
fsOpenGate: make(chan bool, maxOpenFiles),
|
fsOpenGate: make(chan bool, maxOpenFiles),
|
||||||
packages: make(map[string]*Pak, 256),
|
strings: make(map[string]string),
|
||||||
words: make(map[string]*IndexResult, 8192),
|
packages: make(map[Pak]*Pak, 256),
|
||||||
throttle: util.NewThrottle(throttle, 100*time.Millisecond), // run at least 0.1s at a time
|
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
|
// index all files in the directories given by dirnames
|
||||||
|
@ -813,7 +867,17 @@ func NewIndex(c *Corpus, dirnames <-chan string, fulltextIndex bool, throttle fl
|
||||||
suffixes = suffixarray.New(x.sources.Bytes())
|
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 {
|
type fileIndex struct {
|
||||||
|
@ -890,11 +954,28 @@ func (x *Index) Read(r io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats() returns index statistics.
|
// Stats returns index statistics.
|
||||||
func (x *Index) Stats() Statistics {
|
func (x *Index) Stats() Statistics {
|
||||||
return x.stats
|
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) {
|
func (x *Index) lookupWord(w string) (match *LookupResult, alt *AltWords) {
|
||||||
match = x.words[w]
|
match = x.words[w]
|
||||||
alt = x.alts[canonical(w)]
|
alt = x.alts[canonical(w)]
|
||||||
|
|
|
@ -17,6 +17,8 @@ func TestIndex(t *testing.T) {
|
||||||
"src/pkg/foo/foo.go": `// Package foo is an example.
|
"src/pkg/foo/foo.go": `// Package foo is an example.
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
|
import "bar"
|
||||||
|
|
||||||
// Foo is stuff.
|
// Foo is stuff.
|
||||||
type Foo struct{}
|
type Foo struct{}
|
||||||
|
|
||||||
|
@ -26,6 +28,10 @@ func New() *Foo {
|
||||||
`,
|
`,
|
||||||
"src/pkg/bar/bar.go": `// Package bar is another example to test races.
|
"src/pkg/bar/bar.go": `// Package bar is another example to test races.
|
||||||
package bar
|
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.
|
"src/pkg/skip/skip.go": `// Package skip should be skipped.
|
||||||
package skip
|
package skip
|
||||||
|
@ -46,11 +52,43 @@ func Skip() {}
|
||||||
t.Fatal("no index")
|
t.Fatal("no index")
|
||||||
}
|
}
|
||||||
t.Logf("Got: %#v", ix)
|
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) {
|
if !reflect.DeepEqual(ix.Stats(), wantStats) {
|
||||||
t.Errorf("Stats = %#v; want %#v", ix.Stats(), wantStats)
|
t.Errorf("Stats = %#v; want %#v", ix.Stats(), wantStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := ix.words["Skip"]; ok {
|
if _, ok := ix.words["Skip"]; ok {
|
||||||
t.Errorf("the word Skip was found; expected it to be skipped")
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue