Commit 2aa58744 authored by Rob Pike's avatar Rob Pike

cmd/doc: search the tree in breadth-first order

This is a simple change to the command that should resolve problems like finding
vendored packages before their non-vendored siblings. By searching in breadth-first
order, we find the matching package lowest in the hierarchy, which is more likely
to be correct than the deeper one, such as a vendored package, that will be found
in a depth-first scan.

This may be sufficient to resolve the issue, and has the merit that it is very easy
to explain. I will leave the issue open for now in case my intuition is wrong.

Update #12423

Change-Id: Icf69e8beb1845277203fcb7d19ffb7cca9fa41f5
Reviewed-on: https://go-review.googlesource.com/17691Reviewed-by: 's avatarRuss Cox <rsc@golang.org>
parent 732e2cd7
...@@ -6,6 +6,7 @@ package main ...@@ -6,6 +6,7 @@ package main
import ( import (
"go/build" "go/build"
"log"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
...@@ -54,65 +55,61 @@ func (d *Dirs) Next() (string, bool) { ...@@ -54,65 +55,61 @@ func (d *Dirs) Next() (string, bool) {
// walk walks the trees in GOROOT and GOPATH. // walk walks the trees in GOROOT and GOPATH.
func (d *Dirs) walk() { func (d *Dirs) walk() {
d.walkRoot(build.Default.GOROOT) d.bfsWalkRoot(build.Default.GOROOT)
for _, root := range splitGopath() { for _, root := range splitGopath() {
d.walkRoot(root) d.bfsWalkRoot(root)
} }
close(d.scan) close(d.scan)
} }
// walkRoot walks a single directory. Each Go source directory it finds is // bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order.
// delivered on d.scan. // Each Go source directory it finds is delivered on d.scan.
func (d *Dirs) walkRoot(root string) { func (d *Dirs) bfsWalkRoot(root string) {
root = path.Join(root, "src") root = path.Join(root, "src")
slashDot := string(filepath.Separator) + "."
// We put a slash on the pkg so can use simple string comparison below
// yet avoid inadvertent matches, like /foobar matching bar.
visit := func(pathName string, f os.FileInfo, err error) error { // this is the queue of directories to examine in this pass.
if err != nil { this := []string{}
return nil // next is the queue of directories to examine in the next pass.
} next := []string{root}
// One package per directory. Ignore the files themselves.
if !f.IsDir() {
return nil
}
// No .git or other dot nonsense please.
if strings.Contains(pathName, slashDot) {
return filepath.SkipDir
}
// Does the directory contain any Go files? If so, it's a candidate.
if hasGoFiles(pathName) {
d.scan <- pathName
return nil
}
return nil
}
filepath.Walk(root, visit)
}
// hasGoFiles tests whether the directory contains at least one file with ".go" for len(next) > 0 {
// extension this, next = next, this[0:0]
func hasGoFiles(path string) bool { for _, dir := range this {
dir, err := os.Open(path) fd, err := os.Open(dir)
if err != nil { if err != nil {
// ignore unreadable directories log.Printf("error opening %s: %v", dir, err)
return false return // TODO? There may be entry before the error.
} }
defer dir.Close() entries, err := fd.Readdir(0)
fd.Close()
names, err := dir.Readdirnames(0) if err != nil {
if err != nil { log.Printf("error reading %s: %v", dir, err)
// ignore unreadable directories return // TODO? There may be entry before the error.
return false }
} hasGoFiles := false
for _, entry := range entries {
for _, name := range names { name := entry.Name()
if strings.HasSuffix(name, ".go") { // For plain files, remember if this directory contains any .go
return true // source files, but ignore them otherwise.
if !entry.IsDir() {
if !hasGoFiles && strings.HasSuffix(name, ".go") {
hasGoFiles = true
}
continue
}
// Entry is a directory.
// No .git or other dot nonsense please.
if strings.HasPrefix(name, ".") {
continue
}
// Remember this (fully qualified) directory for the next pass.
next = append(next, filepath.Join(dir, name))
}
if hasGoFiles {
// It's a candidate.
d.scan <- dir
}
} }
}
return false }
} }
...@@ -236,8 +236,10 @@ The first item in this list matched by the argument is the one whose documentati ...@@ -236,8 +236,10 @@ The first item in this list matched by the argument is the one whose documentati
is printed. (See the examples below.) However, if the argument starts with a capital is printed. (See the examples below.) However, if the argument starts with a capital
letter it is assumed to identify a symbol or method in the current directory. letter it is assumed to identify a symbol or method in the current directory.
For packages, the order of scanning is determined lexically, but the GOROOT tree For packages, the order of scanning is determined lexically in breadth-first order.
is always scanned before GOPATH. That is, the package presented is the one that matches the search and is nearest
the root and lexically first at its level of the hierarchy. The GOROOT tree is
always scanned in its entirety before GOPATH.
If there is no package specified or matched, the package in the current If there is no package specified or matched, the package in the current
directory is selected, so "go doc Foo" shows the documentation for symbol Foo in directory is selected, so "go doc Foo" shows the documentation for symbol Foo in
......
...@@ -39,8 +39,10 @@ The first item in this list matched by the argument is the one whose documentati ...@@ -39,8 +39,10 @@ The first item in this list matched by the argument is the one whose documentati
is printed. (See the examples below.) However, if the argument starts with a capital is printed. (See the examples below.) However, if the argument starts with a capital
letter it is assumed to identify a symbol or method in the current directory. letter it is assumed to identify a symbol or method in the current directory.
For packages, the order of scanning is determined lexically, but the GOROOT tree For packages, the order of scanning is determined lexically in breadth-first order.
is always scanned before GOPATH. That is, the package presented is the one that matches the search and is nearest
the root and lexically first at its level of the hierarchy. The GOROOT tree is
always scanned in its entirety before GOPATH.
If there is no package specified or matched, the package in the current If there is no package specified or matched, the package in the current
directory is selected, so "go doc Foo" shows the documentation for symbol Foo in directory is selected, so "go doc Foo" shows the documentation for symbol Foo in
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment