From 22dfc77c99687c973b116f88e4295d88e6c11d7d Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Mon, 9 Jan 2012 16:14:01 -0800 Subject: [PATCH] go/doc: first steps towards cleaning up go/doc - separated exported data structures from doc reader by extracting all exported data structures into doc.go and moving the implementation into reader.go - added missing documentation comments - no API or semantic changes (but moved positions of PackageDoc.Doc and TypeDoc.Decl field up for consistency) - runs all tests R=golang-dev, r CC=golang-dev https://golang.org/cl/5527063 --- src/buildscript_darwin_386.sh | 2 +- src/buildscript_darwin_amd64.sh | 2 +- src/buildscript_freebsd_386.sh | 2 +- src/buildscript_freebsd_amd64.sh | 2 +- src/buildscript_linux_386.sh | 2 +- src/buildscript_linux_amd64.sh | 2 +- src/buildscript_linux_arm.sh | 2 +- src/buildscript_netbsd_386.sh | 2 +- src/buildscript_netbsd_amd64.sh | 2 +- src/buildscript_openbsd_386.sh | 2 +- src/buildscript_openbsd_amd64.sh | 2 +- src/buildscript_plan9_386.sh | 2 +- src/buildscript_windows_386.sh | 2 +- src/buildscript_windows_amd64.sh | 2 +- src/pkg/go/doc/Makefile | 1 + src/pkg/go/doc/doc.go | 684 ++----------------------------- src/pkg/go/doc/reader.go | 613 +++++++++++++++++++++++++++ 17 files changed, 666 insertions(+), 660 deletions(-) create mode 100644 src/pkg/go/doc/reader.go diff --git a/src/buildscript_darwin_386.sh b/src/buildscript_darwin_386.sh index 44c33cef1f..bb292d1f3a 100755 --- a/src/buildscript_darwin_386.sh +++ b/src/buildscript_darwin_386.sh @@ -460,7 +460,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/darwin_386/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.8 cp "$WORK"/go/doc.a "$GOROOT"/pkg/darwin_386/go/doc.a diff --git a/src/buildscript_darwin_amd64.sh b/src/buildscript_darwin_amd64.sh index fcc697552c..c537848a53 100755 --- a/src/buildscript_darwin_amd64.sh +++ b/src/buildscript_darwin_amd64.sh @@ -459,7 +459,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/darwin_amd64/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.6 cp "$WORK"/go/doc.a "$GOROOT"/pkg/darwin_amd64/go/doc.a diff --git a/src/buildscript_freebsd_386.sh b/src/buildscript_freebsd_386.sh index a444e94771..4f5cfe6dae 100755 --- a/src/buildscript_freebsd_386.sh +++ b/src/buildscript_freebsd_386.sh @@ -460,7 +460,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/freebsd_386/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.8 cp "$WORK"/go/doc.a "$GOROOT"/pkg/freebsd_386/go/doc.a diff --git a/src/buildscript_freebsd_amd64.sh b/src/buildscript_freebsd_amd64.sh index d5dca47505..d296eb37f5 100755 --- a/src/buildscript_freebsd_amd64.sh +++ b/src/buildscript_freebsd_amd64.sh @@ -459,7 +459,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/freebsd_amd64/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.6 cp "$WORK"/go/doc.a "$GOROOT"/pkg/freebsd_amd64/go/doc.a diff --git a/src/buildscript_linux_386.sh b/src/buildscript_linux_386.sh index 09260ae9b3..7d26ba5774 100755 --- a/src/buildscript_linux_386.sh +++ b/src/buildscript_linux_386.sh @@ -460,7 +460,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/linux_386/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.8 cp "$WORK"/go/doc.a "$GOROOT"/pkg/linux_386/go/doc.a diff --git a/src/buildscript_linux_amd64.sh b/src/buildscript_linux_amd64.sh index cc1ab834ea..87ce176f2f 100755 --- a/src/buildscript_linux_amd64.sh +++ b/src/buildscript_linux_amd64.sh @@ -459,7 +459,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/linux_amd64/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.6 cp "$WORK"/go/doc.a "$GOROOT"/pkg/linux_amd64/go/doc.a diff --git a/src/buildscript_linux_arm.sh b/src/buildscript_linux_arm.sh index 9e18fe6fdd..d063ba0f27 100755 --- a/src/buildscript_linux_arm.sh +++ b/src/buildscript_linux_arm.sh @@ -463,7 +463,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/linux_arm/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -5g -o "$WORK"/go/doc/_obj/_go_.5 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +5g -o "$WORK"/go/doc/_obj/_go_.5 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.5 cp "$WORK"/go/doc.a "$GOROOT"/pkg/linux_arm/go/doc.a diff --git a/src/buildscript_netbsd_386.sh b/src/buildscript_netbsd_386.sh index 4112d905e6..fb5836b18e 100755 --- a/src/buildscript_netbsd_386.sh +++ b/src/buildscript_netbsd_386.sh @@ -460,7 +460,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/netbsd_386/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.8 cp "$WORK"/go/doc.a "$GOROOT"/pkg/netbsd_386/go/doc.a diff --git a/src/buildscript_netbsd_amd64.sh b/src/buildscript_netbsd_amd64.sh index c83a2dadf1..c75062d604 100755 --- a/src/buildscript_netbsd_amd64.sh +++ b/src/buildscript_netbsd_amd64.sh @@ -459,7 +459,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/netbsd_amd64/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.6 cp "$WORK"/go/doc.a "$GOROOT"/pkg/netbsd_amd64/go/doc.a diff --git a/src/buildscript_openbsd_386.sh b/src/buildscript_openbsd_386.sh index e63b97aad6..0435a2fdbc 100755 --- a/src/buildscript_openbsd_386.sh +++ b/src/buildscript_openbsd_386.sh @@ -460,7 +460,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/openbsd_386/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.8 cp "$WORK"/go/doc.a "$GOROOT"/pkg/openbsd_386/go/doc.a diff --git a/src/buildscript_openbsd_amd64.sh b/src/buildscript_openbsd_amd64.sh index 8099e999a7..89907c0a93 100755 --- a/src/buildscript_openbsd_amd64.sh +++ b/src/buildscript_openbsd_amd64.sh @@ -459,7 +459,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/openbsd_amd64/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.6 cp "$WORK"/go/doc.a "$GOROOT"/pkg/openbsd_amd64/go/doc.a diff --git a/src/buildscript_plan9_386.sh b/src/buildscript_plan9_386.sh index 07cb12a3ce..304c12e58a 100755 --- a/src/buildscript_plan9_386.sh +++ b/src/buildscript_plan9_386.sh @@ -460,7 +460,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/plan9_386/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.8 cp "$WORK"/go/doc.a "$GOROOT"/pkg/plan9_386/go/doc.a diff --git a/src/buildscript_windows_386.sh b/src/buildscript_windows_386.sh index 8518fd19e2..0aeacfba0f 100755 --- a/src/buildscript_windows_386.sh +++ b/src/buildscript_windows_386.sh @@ -462,7 +462,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/windows_386/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +8g -o "$WORK"/go/doc/_obj/_go_.8 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.8 cp "$WORK"/go/doc.a "$GOROOT"/pkg/windows_386/go/doc.a diff --git a/src/buildscript_windows_amd64.sh b/src/buildscript_windows_amd64.sh index d27df78664..f2d3557c20 100755 --- a/src/buildscript_windows_amd64.sh +++ b/src/buildscript_windows_amd64.sh @@ -461,7 +461,7 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/windows_amd64/text/template.a mkdir -p "$WORK"/go/doc/_obj/ cd "$GOROOT"/src/pkg/go/doc -6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go +6g -o "$WORK"/go/doc/_obj/_go_.6 -p go/doc -I "$WORK" ./comment.go ./doc.go ./example.go ./exports.go ./filter.go ./reader.go gopack grc "$WORK"/go/doc.a "$WORK"/go/doc/_obj/_go_.6 cp "$WORK"/go/doc.a "$GOROOT"/pkg/windows_amd64/go/doc.a diff --git a/src/pkg/go/doc/Makefile b/src/pkg/go/doc/Makefile index b27b85abea..ac2eeb9627 100644 --- a/src/pkg/go/doc/Makefile +++ b/src/pkg/go/doc/Makefile @@ -11,6 +11,7 @@ GOFILES=\ example.go\ exports.go\ filter.go\ + reader.go\ include ../../../Make.pkg diff --git a/src/pkg/go/doc/doc.go b/src/pkg/go/doc/doc.go index 1bb22416c7..044e996a9e 100644 --- a/src/pkg/go/doc/doc.go +++ b/src/pkg/go/doc/doc.go @@ -5,321 +5,55 @@ // Package doc extracts source code documentation from a Go AST. package doc -import ( - "go/ast" - "go/token" - "regexp" - "sort" -) +import "go/ast" -// ---------------------------------------------------------------------------- -// Collection of documentation info - -// embeddedType describes the type of an anonymous field. -// -type embeddedType struct { - typ *typeInfo // the corresponding base type - ptr bool // if set, the anonymous field type is a pointer -} - -type typeInfo struct { - // len(decl.Specs) == 1, and the element type is *ast.TypeSpec - // if the type declaration hasn't been seen yet, decl is nil - decl *ast.GenDecl - embedded []embeddedType - forward *TypeDoc // forward link to processed type documentation - - // declarations associated with the type - values []*ast.GenDecl // consts and vars - factories map[string]*ast.FuncDecl - methods map[string]*ast.FuncDecl -} - -func (info *typeInfo) addEmbeddedType(embedded *typeInfo, isPtr bool) { - info.embedded = append(info.embedded, embeddedType{embedded, isPtr}) -} - -// docReader accumulates documentation for a single package. -// It modifies the AST: Comments (declaration documentation) -// that have been collected by the DocReader are set to nil -// in the respective AST nodes so that they are not printed -// twice (once when printing the documentation and once when -// printing the corresponding AST node). -// -type docReader struct { - doc *ast.CommentGroup // package documentation, if any - pkgName string - values []*ast.GenDecl // consts and vars - types map[string]*typeInfo - embedded map[string]*typeInfo // embedded types, possibly not exported - funcs map[string]*ast.FuncDecl - bugs []*ast.CommentGroup -} - -func (doc *docReader) init(pkgName string) { - doc.pkgName = pkgName - doc.types = make(map[string]*typeInfo) - doc.embedded = make(map[string]*typeInfo) - doc.funcs = make(map[string]*ast.FuncDecl) -} - -func (doc *docReader) addDoc(comments *ast.CommentGroup) { - if doc.doc == nil { - // common case: just one package comment - doc.doc = comments - return - } - // More than one package comment: Usually there will be only - // one file with a package comment, but it's better to collect - // all comments than drop them on the floor. - blankComment := &ast.Comment{token.NoPos, "//"} - list := append(doc.doc.List, blankComment) - doc.doc.List = append(list, comments.List...) -} - -func (doc *docReader) lookupTypeInfo(name string) *typeInfo { - if name == "" || name == "_" { - return nil // no type docs for anonymous types - } - if info, found := doc.types[name]; found { - return info - } - // type wasn't found - add one without declaration - info := &typeInfo{ - factories: make(map[string]*ast.FuncDecl), - methods: make(map[string]*ast.FuncDecl), - } - doc.types[name] = info - return info -} - -func baseTypeName(typ ast.Expr, allTypes bool) string { - switch t := typ.(type) { - case *ast.Ident: - // if the type is not exported, the effect to - // a client is as if there were no type name - if t.IsExported() || allTypes { - return t.Name - } - case *ast.StarExpr: - return baseTypeName(t.X, allTypes) - } - return "" -} - -func (doc *docReader) addValue(decl *ast.GenDecl) { - // determine if decl should be associated with a type - // Heuristic: For each typed entry, determine the type name, if any. - // If there is exactly one type name that is sufficiently - // frequent, associate the decl with the respective type. - domName := "" - domFreq := 0 - prev := "" - for _, s := range decl.Specs { - if v, ok := s.(*ast.ValueSpec); ok { - name := "" - switch { - case v.Type != nil: - // a type is present; determine its name - name = baseTypeName(v.Type, false) - case decl.Tok == token.CONST: - // no type is present but we have a constant declaration; - // use the previous type name (w/o more type information - // we cannot handle the case of unnamed variables with - // initializer expressions except for some trivial cases) - name = prev - } - if name != "" { - // entry has a named type - if domName != "" && domName != name { - // more than one type name - do not associate - // with any type - domName = "" - break - } - domName = name - domFreq++ - } - prev = name - } - } - - // determine values list - const threshold = 0.75 - values := &doc.values - if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) { - // typed entries are sufficiently frequent - typ := doc.lookupTypeInfo(domName) - if typ != nil { - values = &typ.values // associate with that type - } - } - - *values = append(*values, decl) -} - -// Helper function to set the table entry for function f. Makes sure that -// at least one f with associated documentation is stored in table, if there -// are multiple f's with the same name. -func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) { - name := f.Name.Name - if g, exists := table[name]; exists && g.Doc != nil { - // a function with the same name has already been registered; - // since it has documentation, assume f is simply another - // implementation and ignore it - // TODO(gri) consider collecting all functions, or at least - // all comments - return - } - // function doesn't exist or has no documentation; use f - table[name] = f +// PackageDoc is the documentation for an entire package. +type PackageDoc struct { + Doc string + PackageName string + ImportPath string + Filenames []string + Consts []*ValueDoc + Types []*TypeDoc + Vars []*ValueDoc + Funcs []*FuncDoc + Bugs []string } -func (doc *docReader) addFunc(fun *ast.FuncDecl) { - // strip function body - fun.Body = nil - - // determine if it should be associated with a type - if fun.Recv != nil { - // method - typ := doc.lookupTypeInfo(baseTypeName(fun.Recv.List[0].Type, false)) - if typ != nil { - // exported receiver type - setFunc(typ.methods, fun) - } - // otherwise don't show the method - // TODO(gri): There may be exported methods of non-exported types - // that can be called because of exported values (consts, vars, or - // function results) of that type. Could determine if that is the - // case and then show those methods in an appropriate section. - return - } - - // perhaps a factory function - // determine result type, if any - if fun.Type.Results.NumFields() >= 1 { - res := fun.Type.Results.List[0] - if len(res.Names) <= 1 { - // exactly one (named or anonymous) result associated - // with the first type in result signature (there may - // be more than one result) - tname := baseTypeName(res.Type, false) - typ := doc.lookupTypeInfo(tname) - if typ != nil { - // named and exported result type - setFunc(typ.factories, fun) - return - } - } - } +// Value is the documentation for a (possibly grouped) var or const declaration. +type ValueDoc struct { + Doc string + Decl *ast.GenDecl - // ordinary function - setFunc(doc.funcs, fun) + order int } -func (doc *docReader) addDecl(decl ast.Decl) { - switch d := decl.(type) { - case *ast.GenDecl: - if len(d.Specs) > 0 { - switch d.Tok { - case token.CONST, token.VAR: - // constants and variables are always handled as a group - doc.addValue(d) - case token.TYPE: - // types are handled individually - for _, spec := range d.Specs { - tspec := spec.(*ast.TypeSpec) - // add the type to the documentation - info := doc.lookupTypeInfo(tspec.Name.Name) - if info == nil { - continue // no name - ignore the type - } - // Make a (fake) GenDecl node for this TypeSpec - // (we need to do this here - as opposed to just - // for printing - so we don't lose the GenDecl - // documentation). Since a new GenDecl node is - // created, there's no need to nil out d.Doc. - // - // TODO(gri): Consider just collecting the TypeSpec - // node (and copy in the GenDecl.doc if there is no - // doc in the TypeSpec - this is currently done in - // makeTypeDocs below). Simpler data structures, but - // would lose GenDecl documentation if the TypeSpec - // has documentation as well. - fake := &ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, - []ast.Spec{tspec}, token.NoPos} - // A type should be added at most once, so info.decl - // should be nil - if it isn't, simply overwrite it. - info.decl = fake - // Look for anonymous fields that might contribute methods. - var fields *ast.FieldList - switch typ := spec.(*ast.TypeSpec).Type.(type) { - case *ast.StructType: - fields = typ.Fields - case *ast.InterfaceType: - fields = typ.Methods - } - if fields != nil { - for _, field := range fields.List { - if len(field.Names) == 0 { - // anonymous field - add corresponding type - // to the info and collect it in doc - name := baseTypeName(field.Type, true) - if embedded := doc.lookupTypeInfo(name); embedded != nil { - _, ptr := field.Type.(*ast.StarExpr) - info.addEmbeddedType(embedded, ptr) - } - } - } - } - } - } - } - case *ast.FuncDecl: - doc.addFunc(d) - } -} +// TypeDoc is the documentation for type declaration. +type TypeDoc struct { + Doc string + Type *ast.TypeSpec + Decl *ast.GenDecl + Consts []*ValueDoc // sorted list of constants of (mostly) this type + Vars []*ValueDoc // sorted list of variables of (mostly) this type + Factories []*FuncDoc // sorted list of functions returning this type + Methods []*FuncDoc // sorted list of methods (including embedded ones) of this type -func copyCommentList(list []*ast.Comment) []*ast.Comment { - return append([]*ast.Comment(nil), list...) + methods []*FuncDoc // top-level methods only + embedded methodSet // embedded methods only + order int } -var ( - bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*") // BUG(uid): - bug_content = regexp.MustCompile("[^ \n\r\t]+") // at least one non-whitespace char -) - -// addFile adds the AST for a source file to the docReader. -// Adding the same AST multiple times is a no-op. -// -func (doc *docReader) addFile(src *ast.File) { - // add package documentation - if src.Doc != nil { - doc.addDoc(src.Doc) - src.Doc = nil // doc consumed - remove from ast.File node - } - - // add all declarations - for _, decl := range src.Decls { - doc.addDecl(decl) - } - - // collect BUG(...) comments - for _, c := range src.Comments { - text := c.List[0].Text - if m := bug_markers.FindStringIndex(text); m != nil { - // found a BUG comment; maybe empty - if btxt := text[m[1]:]; bug_content.MatchString(btxt) { - // non-empty BUG comment; collect comment without BUG prefix - list := copyCommentList(c.List) - list[0].Text = text[m[1]:] - doc.bugs = append(doc.bugs, &ast.CommentGroup{list}) - } - } - } - src.Comments = nil // consumed unassociated comments - remove from ast.File node +// Func is the documentation for a func declaration. +type FuncDoc struct { + Doc string + Recv ast.Expr // TODO(rsc): Would like string here + Name string + Decl *ast.FuncDecl } +// NewPackageDoc computes the package documentation for the given package +// and import path. If exportsOnly is set, only exported objects are +// included in the documentation. func NewPackageDoc(pkg *ast.Package, importpath string, exportsOnly bool) *PackageDoc { var r docReader r.init(pkg.Name) @@ -335,345 +69,3 @@ func NewPackageDoc(pkg *ast.Package, importpath string, exportsOnly bool) *Packa } return r.newDoc(importpath, filenames) } - -// ---------------------------------------------------------------------------- -// Conversion to external representation - -// ValueDoc is the documentation for a group of declared -// values, either vars or consts. -// -type ValueDoc struct { - Doc string - Decl *ast.GenDecl - order int -} - -type sortValueDoc []*ValueDoc - -func (p sortValueDoc) Len() int { return len(p) } -func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -func declName(d *ast.GenDecl) string { - if len(d.Specs) != 1 { - return "" - } - - switch v := d.Specs[0].(type) { - case *ast.ValueSpec: - return v.Names[0].Name - case *ast.TypeSpec: - return v.Name.Name - } - - return "" -} - -func (p sortValueDoc) Less(i, j int) bool { - // sort by name - // pull blocks (name = "") up to top - // in original order - if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj { - return ni < nj - } - return p[i].order < p[j].order -} - -func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc { - d := make([]*ValueDoc, len(list)) // big enough in any case - n := 0 - for i, decl := range list { - if decl.Tok == tok { - d[n] = &ValueDoc{CommentText(decl.Doc), decl, i} - n++ - decl.Doc = nil // doc consumed - removed from AST - } - } - d = d[0:n] - sort.Sort(sortValueDoc(d)) - return d -} - -// FuncDoc is the documentation for a func declaration, -// either a top-level function or a method function. -// -type FuncDoc struct { - Doc string - Recv ast.Expr // TODO(rsc): Would like string here - Name string - Decl *ast.FuncDecl -} - -type sortFuncDoc []*FuncDoc - -func (p sortFuncDoc) Len() int { return len(p) } -func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name } - -func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc { - d := make([]*FuncDoc, len(m)) - i := 0 - for _, f := range m { - doc := new(FuncDoc) - doc.Doc = CommentText(f.Doc) - f.Doc = nil // doc consumed - remove from ast.FuncDecl node - if f.Recv != nil { - doc.Recv = f.Recv.List[0].Type - } - doc.Name = f.Name.Name - doc.Decl = f - d[i] = doc - i++ - } - sort.Sort(sortFuncDoc(d)) - return d -} - -type methodSet map[string]*FuncDoc - -func (mset methodSet) add(m *FuncDoc) { - if mset[m.Name] == nil { - mset[m.Name] = m - } -} - -func (mset methodSet) sortedList() []*FuncDoc { - list := make([]*FuncDoc, len(mset)) - i := 0 - for _, m := range mset { - list[i] = m - i++ - } - sort.Sort(sortFuncDoc(list)) - return list -} - -// TypeDoc is the documentation for a declared type. -// Consts and Vars are sorted lists of constants and variables of (mostly) that type. -// Factories is a sorted list of factory functions that return that type. -// Methods is a sorted list of method functions on that type. -type TypeDoc struct { - Doc string - Type *ast.TypeSpec - Consts []*ValueDoc - Vars []*ValueDoc - Factories []*FuncDoc - methods []*FuncDoc // top-level methods only - embedded methodSet // embedded methods only - Methods []*FuncDoc // all methods including embedded ones - Decl *ast.GenDecl - order int -} - -type sortTypeDoc []*TypeDoc - -func (p sortTypeDoc) Len() int { return len(p) } -func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p sortTypeDoc) Less(i, j int) bool { - // sort by name - // pull blocks (name = "") up to top - // in original order - if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj { - return ni < nj - } - return p[i].order < p[j].order -} - -// NOTE(rsc): This would appear not to be correct for type ( ) -// blocks, but the doc extractor above has split them into -// individual declarations. -func (doc *docReader) makeTypeDocs(m map[string]*typeInfo) []*TypeDoc { - // TODO(gri) Consider computing the embedded method information - // before calling makeTypeDocs. Then this function can - // be single-phased again. Also, it might simplify some - // of the logic. - // - // phase 1: associate collected declarations with TypeDocs - list := make([]*TypeDoc, len(m)) - i := 0 - for _, old := range m { - // all typeInfos should have a declaration associated with - // them after processing an entire package - be conservative - // and check - if decl := old.decl; decl != nil { - typespec := decl.Specs[0].(*ast.TypeSpec) - t := new(TypeDoc) - doc := typespec.Doc - typespec.Doc = nil // doc consumed - remove from ast.TypeSpec node - if doc == nil { - // no doc associated with the spec, use the declaration doc, if any - doc = decl.Doc - } - decl.Doc = nil // doc consumed - remove from ast.Decl node - t.Doc = CommentText(doc) - t.Type = typespec - t.Consts = makeValueDocs(old.values, token.CONST) - t.Vars = makeValueDocs(old.values, token.VAR) - t.Factories = makeFuncDocs(old.factories) - t.methods = makeFuncDocs(old.methods) - // The list of embedded types' methods is computed from the list - // of embedded types, some of which may not have been processed - // yet (i.e., their forward link is nil) - do this in a 2nd phase. - // The final list of methods can only be computed after that - - // do this in a 3rd phase. - t.Decl = old.decl - t.order = i - old.forward = t // old has been processed - list[i] = t - i++ - } else { - // no corresponding type declaration found - move any associated - // values, factory functions, and methods back to the top-level - // so that they are not lost (this should only happen if a package - // file containing the explicit type declaration is missing or if - // an unqualified type name was used after a "." import) - // 1) move values - doc.values = append(doc.values, old.values...) - // 2) move factory functions - for name, f := range old.factories { - doc.funcs[name] = f - } - // 3) move methods - for name, f := range old.methods { - // don't overwrite functions with the same name - if _, found := doc.funcs[name]; !found { - doc.funcs[name] = f - } - } - } - } - list = list[0:i] // some types may have been ignored - - // phase 2: collect embedded methods for each processed typeInfo - for _, old := range m { - if t := old.forward; t != nil { - // old has been processed into t; collect embedded - // methods for t from the list of processed embedded - // types in old (and thus for which the methods are known) - typ := t.Type - if _, ok := typ.Type.(*ast.StructType); ok { - // struct - t.embedded = make(methodSet) - collectEmbeddedMethods(t.embedded, old, typ.Name.Name) - } else { - // interface - // TODO(gri) fix this - } - } - } - - // phase 3: compute final method set for each TypeDoc - for _, d := range list { - if len(d.embedded) > 0 { - // there are embedded methods - exclude - // the ones with names conflicting with - // non-embedded methods - mset := make(methodSet) - // top-level methods have priority - for _, m := range d.methods { - mset.add(m) - } - // add non-conflicting embedded methods - for _, m := range d.embedded { - mset.add(m) - } - d.Methods = mset.sortedList() - } else { - // no embedded methods - d.Methods = d.methods - } - } - - sort.Sort(sortTypeDoc(list)) - return list -} - -// collectEmbeddedMethods collects the embedded methods from all -// processed embedded types found in info in mset. It considers -// embedded types at the most shallow level first so that more -// deeply nested embedded methods with conflicting names are -// excluded. -// -func collectEmbeddedMethods(mset methodSet, info *typeInfo, recvTypeName string) { - for _, e := range info.embedded { - if e.typ.forward != nil { // == e was processed - for _, m := range e.typ.forward.methods { - mset.add(customizeRecv(m, e.ptr, recvTypeName)) - } - collectEmbeddedMethods(mset, e.typ, recvTypeName) - } - } -} - -func customizeRecv(m *FuncDoc, embeddedIsPtr bool, recvTypeName string) *FuncDoc { - if m == nil || m.Decl == nil || m.Decl.Recv == nil || len(m.Decl.Recv.List) != 1 { - return m // shouldn't happen, but be safe - } - - // copy existing receiver field and set new type - // TODO(gri) is receiver type computation correct? - // what about deeply nested embeddings? - newField := *m.Decl.Recv.List[0] - _, origRecvIsPtr := newField.Type.(*ast.StarExpr) - var typ ast.Expr = ast.NewIdent(recvTypeName) - if embeddedIsPtr || origRecvIsPtr { - typ = &ast.StarExpr{token.NoPos, typ} - } - newField.Type = typ - - // copy existing receiver field list and set new receiver field - newFieldList := *m.Decl.Recv - newFieldList.List = []*ast.Field{&newField} - - // copy existing function declaration and set new receiver field list - newFuncDecl := *m.Decl - newFuncDecl.Recv = &newFieldList - - // copy existing function documentation and set new declaration - newM := *m - newM.Decl = &newFuncDecl - newM.Recv = typ - - return &newM -} - -func makeBugDocs(list []*ast.CommentGroup) []string { - d := make([]string, len(list)) - for i, g := range list { - d[i] = CommentText(g) - } - return d -} - -// PackageDoc is the documentation for an entire package. -// -type PackageDoc struct { - PackageName string - ImportPath string - Filenames []string - Doc string - Consts []*ValueDoc - Types []*TypeDoc - Vars []*ValueDoc - Funcs []*FuncDoc - Bugs []string -} - -// newDoc returns the accumulated documentation for the package. -// -func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc { - p := new(PackageDoc) - p.PackageName = doc.pkgName - p.ImportPath = importpath - sort.Strings(filenames) - p.Filenames = filenames - p.Doc = CommentText(doc.doc) - // makeTypeDocs may extend the list of doc.values and - // doc.funcs and thus must be called before any other - // function consuming those lists - p.Types = doc.makeTypeDocs(doc.types) - p.Consts = makeValueDocs(doc.values, token.CONST) - p.Vars = makeValueDocs(doc.values, token.VAR) - p.Funcs = makeFuncDocs(doc.funcs) - p.Bugs = makeBugDocs(doc.bugs) - return p -} diff --git a/src/pkg/go/doc/reader.go b/src/pkg/go/doc/reader.go new file mode 100644 index 0000000000..4e4ed584b1 --- /dev/null +++ b/src/pkg/go/doc/reader.go @@ -0,0 +1,613 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package doc extracts source code documentation from a Go AST. +package doc + +import ( + "go/ast" + "go/token" + "regexp" + "sort" +) + +// ---------------------------------------------------------------------------- +// Collection of documentation info + +// embeddedType describes the type of an anonymous field. +// +type embeddedType struct { + typ *typeInfo // the corresponding base type + ptr bool // if set, the anonymous field type is a pointer +} + +type typeInfo struct { + // len(decl.Specs) == 1, and the element type is *ast.TypeSpec + // if the type declaration hasn't been seen yet, decl is nil + decl *ast.GenDecl + embedded []embeddedType + forward *TypeDoc // forward link to processed type documentation + + // declarations associated with the type + values []*ast.GenDecl // consts and vars + factories map[string]*ast.FuncDecl + methods map[string]*ast.FuncDecl +} + +func (info *typeInfo) addEmbeddedType(embedded *typeInfo, isPtr bool) { + info.embedded = append(info.embedded, embeddedType{embedded, isPtr}) +} + +// docReader accumulates documentation for a single package. +// It modifies the AST: Comments (declaration documentation) +// that have been collected by the DocReader are set to nil +// in the respective AST nodes so that they are not printed +// twice (once when printing the documentation and once when +// printing the corresponding AST node). +// +type docReader struct { + doc *ast.CommentGroup // package documentation, if any + pkgName string + values []*ast.GenDecl // consts and vars + types map[string]*typeInfo + embedded map[string]*typeInfo // embedded types, possibly not exported + funcs map[string]*ast.FuncDecl + bugs []*ast.CommentGroup +} + +func (doc *docReader) init(pkgName string) { + doc.pkgName = pkgName + doc.types = make(map[string]*typeInfo) + doc.embedded = make(map[string]*typeInfo) + doc.funcs = make(map[string]*ast.FuncDecl) +} + +func (doc *docReader) addDoc(comments *ast.CommentGroup) { + if doc.doc == nil { + // common case: just one package comment + doc.doc = comments + return + } + // More than one package comment: Usually there will be only + // one file with a package comment, but it's better to collect + // all comments than drop them on the floor. + blankComment := &ast.Comment{token.NoPos, "//"} + list := append(doc.doc.List, blankComment) + doc.doc.List = append(list, comments.List...) +} + +func (doc *docReader) lookupTypeInfo(name string) *typeInfo { + if name == "" || name == "_" { + return nil // no type docs for anonymous types + } + if info, found := doc.types[name]; found { + return info + } + // type wasn't found - add one without declaration + info := &typeInfo{ + factories: make(map[string]*ast.FuncDecl), + methods: make(map[string]*ast.FuncDecl), + } + doc.types[name] = info + return info +} + +func baseTypeName(typ ast.Expr, allTypes bool) string { + switch t := typ.(type) { + case *ast.Ident: + // if the type is not exported, the effect to + // a client is as if there were no type name + if t.IsExported() || allTypes { + return t.Name + } + case *ast.StarExpr: + return baseTypeName(t.X, allTypes) + } + return "" +} + +func (doc *docReader) addValue(decl *ast.GenDecl) { + // determine if decl should be associated with a type + // Heuristic: For each typed entry, determine the type name, if any. + // If there is exactly one type name that is sufficiently + // frequent, associate the decl with the respective type. + domName := "" + domFreq := 0 + prev := "" + for _, s := range decl.Specs { + if v, ok := s.(*ast.ValueSpec); ok { + name := "" + switch { + case v.Type != nil: + // a type is present; determine its name + name = baseTypeName(v.Type, false) + case decl.Tok == token.CONST: + // no type is present but we have a constant declaration; + // use the previous type name (w/o more type information + // we cannot handle the case of unnamed variables with + // initializer expressions except for some trivial cases) + name = prev + } + if name != "" { + // entry has a named type + if domName != "" && domName != name { + // more than one type name - do not associate + // with any type + domName = "" + break + } + domName = name + domFreq++ + } + prev = name + } + } + + // determine values list + const threshold = 0.75 + values := &doc.values + if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) { + // typed entries are sufficiently frequent + typ := doc.lookupTypeInfo(domName) + if typ != nil { + values = &typ.values // associate with that type + } + } + + *values = append(*values, decl) +} + +// Helper function to set the table entry for function f. Makes sure that +// at least one f with associated documentation is stored in table, if there +// are multiple f's with the same name. +func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) { + name := f.Name.Name + if g, exists := table[name]; exists && g.Doc != nil { + // a function with the same name has already been registered; + // since it has documentation, assume f is simply another + // implementation and ignore it + // TODO(gri) consider collecting all functions, or at least + // all comments + return + } + // function doesn't exist or has no documentation; use f + table[name] = f +} + +func (doc *docReader) addFunc(fun *ast.FuncDecl) { + // strip function body + fun.Body = nil + + // determine if it should be associated with a type + if fun.Recv != nil { + // method + typ := doc.lookupTypeInfo(baseTypeName(fun.Recv.List[0].Type, false)) + if typ != nil { + // exported receiver type + setFunc(typ.methods, fun) + } + // otherwise don't show the method + // TODO(gri): There may be exported methods of non-exported types + // that can be called because of exported values (consts, vars, or + // function results) of that type. Could determine if that is the + // case and then show those methods in an appropriate section. + return + } + + // perhaps a factory function + // determine result type, if any + if fun.Type.Results.NumFields() >= 1 { + res := fun.Type.Results.List[0] + if len(res.Names) <= 1 { + // exactly one (named or anonymous) result associated + // with the first type in result signature (there may + // be more than one result) + tname := baseTypeName(res.Type, false) + typ := doc.lookupTypeInfo(tname) + if typ != nil { + // named and exported result type + setFunc(typ.factories, fun) + return + } + } + } + + // ordinary function + setFunc(doc.funcs, fun) +} + +func (doc *docReader) addDecl(decl ast.Decl) { + switch d := decl.(type) { + case *ast.GenDecl: + if len(d.Specs) > 0 { + switch d.Tok { + case token.CONST, token.VAR: + // constants and variables are always handled as a group + doc.addValue(d) + case token.TYPE: + // types are handled individually + for _, spec := range d.Specs { + tspec := spec.(*ast.TypeSpec) + // add the type to the documentation + info := doc.lookupTypeInfo(tspec.Name.Name) + if info == nil { + continue // no name - ignore the type + } + // Make a (fake) GenDecl node for this TypeSpec + // (we need to do this here - as opposed to just + // for printing - so we don't lose the GenDecl + // documentation). Since a new GenDecl node is + // created, there's no need to nil out d.Doc. + // + // TODO(gri): Consider just collecting the TypeSpec + // node (and copy in the GenDecl.doc if there is no + // doc in the TypeSpec - this is currently done in + // makeTypeDocs below). Simpler data structures, but + // would lose GenDecl documentation if the TypeSpec + // has documentation as well. + fake := &ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, + []ast.Spec{tspec}, token.NoPos} + // A type should be added at most once, so info.decl + // should be nil - if it isn't, simply overwrite it. + info.decl = fake + // Look for anonymous fields that might contribute methods. + var fields *ast.FieldList + switch typ := spec.(*ast.TypeSpec).Type.(type) { + case *ast.StructType: + fields = typ.Fields + case *ast.InterfaceType: + fields = typ.Methods + } + if fields != nil { + for _, field := range fields.List { + if len(field.Names) == 0 { + // anonymous field - add corresponding type + // to the info and collect it in doc + name := baseTypeName(field.Type, true) + if embedded := doc.lookupTypeInfo(name); embedded != nil { + _, ptr := field.Type.(*ast.StarExpr) + info.addEmbeddedType(embedded, ptr) + } + } + } + } + } + } + } + case *ast.FuncDecl: + doc.addFunc(d) + } +} + +func copyCommentList(list []*ast.Comment) []*ast.Comment { + return append([]*ast.Comment(nil), list...) +} + +var ( + bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*") // BUG(uid): + bug_content = regexp.MustCompile("[^ \n\r\t]+") // at least one non-whitespace char +) + +// addFile adds the AST for a source file to the docReader. +// Adding the same AST multiple times is a no-op. +// +func (doc *docReader) addFile(src *ast.File) { + // add package documentation + if src.Doc != nil { + doc.addDoc(src.Doc) + src.Doc = nil // doc consumed - remove from ast.File node + } + + // add all declarations + for _, decl := range src.Decls { + doc.addDecl(decl) + } + + // collect BUG(...) comments + for _, c := range src.Comments { + text := c.List[0].Text + if m := bug_markers.FindStringIndex(text); m != nil { + // found a BUG comment; maybe empty + if btxt := text[m[1]:]; bug_content.MatchString(btxt) { + // non-empty BUG comment; collect comment without BUG prefix + list := copyCommentList(c.List) + list[0].Text = text[m[1]:] + doc.bugs = append(doc.bugs, &ast.CommentGroup{list}) + } + } + } + src.Comments = nil // consumed unassociated comments - remove from ast.File node +} + +// ---------------------------------------------------------------------------- +// Conversion to external representation + +type sortValueDoc []*ValueDoc + +func (p sortValueDoc) Len() int { return len(p) } +func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +func declName(d *ast.GenDecl) string { + if len(d.Specs) != 1 { + return "" + } + + switch v := d.Specs[0].(type) { + case *ast.ValueSpec: + return v.Names[0].Name + case *ast.TypeSpec: + return v.Name.Name + } + + return "" +} + +func (p sortValueDoc) Less(i, j int) bool { + // sort by name + // pull blocks (name = "") up to top + // in original order + if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj { + return ni < nj + } + return p[i].order < p[j].order +} + +func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc { + d := make([]*ValueDoc, len(list)) // big enough in any case + n := 0 + for i, decl := range list { + if decl.Tok == tok { + d[n] = &ValueDoc{CommentText(decl.Doc), decl, i} + n++ + decl.Doc = nil // doc consumed - removed from AST + } + } + d = d[0:n] + sort.Sort(sortValueDoc(d)) + return d +} + +type sortFuncDoc []*FuncDoc + +func (p sortFuncDoc) Len() int { return len(p) } +func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name } + +func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc { + d := make([]*FuncDoc, len(m)) + i := 0 + for _, f := range m { + doc := new(FuncDoc) + doc.Doc = CommentText(f.Doc) + f.Doc = nil // doc consumed - remove from ast.FuncDecl node + if f.Recv != nil { + doc.Recv = f.Recv.List[0].Type + } + doc.Name = f.Name.Name + doc.Decl = f + d[i] = doc + i++ + } + sort.Sort(sortFuncDoc(d)) + return d +} + +type methodSet map[string]*FuncDoc + +func (mset methodSet) add(m *FuncDoc) { + if mset[m.Name] == nil { + mset[m.Name] = m + } +} + +func (mset methodSet) sortedList() []*FuncDoc { + list := make([]*FuncDoc, len(mset)) + i := 0 + for _, m := range mset { + list[i] = m + i++ + } + sort.Sort(sortFuncDoc(list)) + return list +} + +type sortTypeDoc []*TypeDoc + +func (p sortTypeDoc) Len() int { return len(p) } +func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p sortTypeDoc) Less(i, j int) bool { + // sort by name + // pull blocks (name = "") up to top + // in original order + if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj { + return ni < nj + } + return p[i].order < p[j].order +} + +// NOTE(rsc): This would appear not to be correct for type ( ) +// blocks, but the doc extractor above has split them into +// individual declarations. +func (doc *docReader) makeTypeDocs(m map[string]*typeInfo) []*TypeDoc { + // TODO(gri) Consider computing the embedded method information + // before calling makeTypeDocs. Then this function can + // be single-phased again. Also, it might simplify some + // of the logic. + // + // phase 1: associate collected declarations with TypeDocs + list := make([]*TypeDoc, len(m)) + i := 0 + for _, old := range m { + // all typeInfos should have a declaration associated with + // them after processing an entire package - be conservative + // and check + if decl := old.decl; decl != nil { + typespec := decl.Specs[0].(*ast.TypeSpec) + t := new(TypeDoc) + doc := typespec.Doc + typespec.Doc = nil // doc consumed - remove from ast.TypeSpec node + if doc == nil { + // no doc associated with the spec, use the declaration doc, if any + doc = decl.Doc + } + decl.Doc = nil // doc consumed - remove from ast.Decl node + t.Doc = CommentText(doc) + t.Type = typespec + t.Consts = makeValueDocs(old.values, token.CONST) + t.Vars = makeValueDocs(old.values, token.VAR) + t.Factories = makeFuncDocs(old.factories) + t.methods = makeFuncDocs(old.methods) + // The list of embedded types' methods is computed from the list + // of embedded types, some of which may not have been processed + // yet (i.e., their forward link is nil) - do this in a 2nd phase. + // The final list of methods can only be computed after that - + // do this in a 3rd phase. + t.Decl = old.decl + t.order = i + old.forward = t // old has been processed + list[i] = t + i++ + } else { + // no corresponding type declaration found - move any associated + // values, factory functions, and methods back to the top-level + // so that they are not lost (this should only happen if a package + // file containing the explicit type declaration is missing or if + // an unqualified type name was used after a "." import) + // 1) move values + doc.values = append(doc.values, old.values...) + // 2) move factory functions + for name, f := range old.factories { + doc.funcs[name] = f + } + // 3) move methods + for name, f := range old.methods { + // don't overwrite functions with the same name + if _, found := doc.funcs[name]; !found { + doc.funcs[name] = f + } + } + } + } + list = list[0:i] // some types may have been ignored + + // phase 2: collect embedded methods for each processed typeInfo + for _, old := range m { + if t := old.forward; t != nil { + // old has been processed into t; collect embedded + // methods for t from the list of processed embedded + // types in old (and thus for which the methods are known) + typ := t.Type + if _, ok := typ.Type.(*ast.StructType); ok { + // struct + t.embedded = make(methodSet) + collectEmbeddedMethods(t.embedded, old, typ.Name.Name) + } else { + // interface + // TODO(gri) fix this + } + } + } + + // phase 3: compute final method set for each TypeDoc + for _, d := range list { + if len(d.embedded) > 0 { + // there are embedded methods - exclude + // the ones with names conflicting with + // non-embedded methods + mset := make(methodSet) + // top-level methods have priority + for _, m := range d.methods { + mset.add(m) + } + // add non-conflicting embedded methods + for _, m := range d.embedded { + mset.add(m) + } + d.Methods = mset.sortedList() + } else { + // no embedded methods + d.Methods = d.methods + } + } + + sort.Sort(sortTypeDoc(list)) + return list +} + +// collectEmbeddedMethods collects the embedded methods from all +// processed embedded types found in info in mset. It considers +// embedded types at the most shallow level first so that more +// deeply nested embedded methods with conflicting names are +// excluded. +// +func collectEmbeddedMethods(mset methodSet, info *typeInfo, recvTypeName string) { + for _, e := range info.embedded { + if e.typ.forward != nil { // == e was processed + for _, m := range e.typ.forward.methods { + mset.add(customizeRecv(m, e.ptr, recvTypeName)) + } + collectEmbeddedMethods(mset, e.typ, recvTypeName) + } + } +} + +func customizeRecv(m *FuncDoc, embeddedIsPtr bool, recvTypeName string) *FuncDoc { + if m == nil || m.Decl == nil || m.Decl.Recv == nil || len(m.Decl.Recv.List) != 1 { + return m // shouldn't happen, but be safe + } + + // copy existing receiver field and set new type + // TODO(gri) is receiver type computation correct? + // what about deeply nested embeddings? + newField := *m.Decl.Recv.List[0] + _, origRecvIsPtr := newField.Type.(*ast.StarExpr) + var typ ast.Expr = ast.NewIdent(recvTypeName) + if embeddedIsPtr || origRecvIsPtr { + typ = &ast.StarExpr{token.NoPos, typ} + } + newField.Type = typ + + // copy existing receiver field list and set new receiver field + newFieldList := *m.Decl.Recv + newFieldList.List = []*ast.Field{&newField} + + // copy existing function declaration and set new receiver field list + newFuncDecl := *m.Decl + newFuncDecl.Recv = &newFieldList + + // copy existing function documentation and set new declaration + newM := *m + newM.Decl = &newFuncDecl + newM.Recv = typ + + return &newM +} + +func makeBugDocs(list []*ast.CommentGroup) []string { + d := make([]string, len(list)) + for i, g := range list { + d[i] = CommentText(g) + } + return d +} + +// newDoc returns the accumulated documentation for the package. +// +func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc { + p := new(PackageDoc) + p.PackageName = doc.pkgName + p.ImportPath = importpath + sort.Strings(filenames) + p.Filenames = filenames + p.Doc = CommentText(doc.doc) + // makeTypeDocs may extend the list of doc.values and + // doc.funcs and thus must be called before any other + // function consuming those lists + p.Types = doc.makeTypeDocs(doc.types) + p.Consts = makeValueDocs(doc.values, token.CONST) + p.Vars = makeValueDocs(doc.values, token.VAR) + p.Funcs = makeFuncDocs(doc.funcs) + p.Bugs = makeBugDocs(doc.bugs) + return p +} -- 2.50.0