1
0
mirror of https://github.com/golang/go synced 2024-11-22 09:04:42 -07:00

go/doc: clean rewrite of go/doc internals

The implementation is divided into 4 phases:
1) export filtering of an incoming AST if necessary (exports.go)
2) reading of a possibly filtered AST (reader.go: type reader)
3) method set computation (reader.go)
4) sorting and creation of final documentation (reader.go)

In contrast to the old implementation, the presentation data
(Names, Docs, Decls, etc.) are created immediately upon reading
the respective AST node. Also, all types are collected (embedded
or not) in a uniform way.

Once the entire AST has been processed, all methods and types
have been collected and the method sets for each type can be
computed (phase 3).

To produce the final documentation, the method sets and value
maps are sorted.

There are no API changes. Passes the existing test suite unchanged.

R=rsc, rogpeppe
CC=golang-dev
https://golang.org/cl/5554044
This commit is contained in:
Robert Griesemer 2012-01-25 09:53:26 -08:00
parent 6e3af46e7d
commit 6d68be46dd
6 changed files with 824 additions and 623 deletions

View File

@ -7,7 +7,7 @@ package doc
import ( import (
"go/ast" "go/ast"
"sort" "go/token"
) )
// Package is the documentation for an entire package. // Package is the documentation for an entire package.
@ -35,11 +35,12 @@ type Value struct {
order int order int
} }
// Method is the documentation for a method declaration.
type Method struct { type Method struct {
*Func *Func
// TODO(gri) The following fields are not set at the moment. // TODO(gri) The following fields are not set at the moment.
Origin *Type // original receiver base type Origin *Type // original receiver base type
Level int // embedding level; 0 means Func is not embedded Level int // embedding level; 0 means Method is not embedded
} }
// Type is the documentation for type declaration. // Type is the documentation for type declaration.
@ -54,9 +55,7 @@ type Type struct {
Funcs []*Func // sorted list of functions returning this type Funcs []*Func // sorted list of functions returning this type
Methods []*Method // sorted list of methods (including embedded ones) of this type Methods []*Method // sorted list of methods (including embedded ones) of this type
methods []*Func // top-level methods only order int
embedded methodSet // embedded methods only
order int
} }
// Func is the documentation for a func declaration. // Func is the documentation for a func declaration.
@ -77,27 +76,22 @@ const (
AllDecls Mode = 1 << iota AllDecls Mode = 1 << iota
) )
// New computes the package documentation for the given package. // New computes the package documentation for the given package AST.
func New(pkg *ast.Package, importpath string, mode Mode) *Package { func New(pkg *ast.Package, importPath string, mode Mode) *Package {
var r docReader var r reader
r.init(pkg.Name, mode) r.readPackage(pkg, mode)
filenames := make([]string, len(pkg.Files)) r.computeMethodSets()
// sort package files before reading them so that the r.cleanupTypes()
// result is the same on different machines (32/64bit) return &Package{
i := 0 Doc: r.doc,
for filename := range pkg.Files { Name: pkg.Name,
filenames[i] = filename ImportPath: importPath,
i++ Imports: sortedKeys(r.imports),
Filenames: r.filenames,
Bugs: r.bugs,
Consts: sortedValues(r.values, token.CONST),
Types: sortedTypes(r.types),
Vars: sortedValues(r.values, token.VAR),
Funcs: r.funcs.sortedFuncs(),
} }
sort.Strings(filenames)
// process files in sorted order
for _, filename := range filenames {
f := pkg.Files[filename]
if mode&AllDecls == 0 {
r.fileExports(f)
}
r.addFile(f)
}
return r.newDoc(importpath, filenames)
} }

View File

@ -8,6 +8,9 @@ package doc
import "go/ast" import "go/ast"
// filterIdentList removes unexported names from list in place
// and returns the resulting list.
//
func filterIdentList(list []*ast.Ident) []*ast.Ident { func filterIdentList(list []*ast.Ident) []*ast.Ident {
j := 0 j := 0
for _, x := range list { for _, x := range list {
@ -19,54 +22,46 @@ func filterIdentList(list []*ast.Ident) []*ast.Ident {
return list[0:j] return list[0:j]
} }
func baseName(x ast.Expr) *ast.Ident { // filterFieldList removes unexported fields (field names) from the field list
switch t := x.(type) { // in place and returns true if fields were removed. Removed fields that are
case *ast.Ident: // anonymous (embedded) fields are added as embedded types to base. filterType
return t // is called with the types of all remaining fields.
case *ast.SelectorExpr: //
if _, ok := t.X.(*ast.Ident); ok { func (r *reader) filterFieldList(base *baseType, fields *ast.FieldList) (removedFields bool) {
return t.Sel
}
case *ast.StarExpr:
return baseName(t.X)
}
return nil
}
func (doc *docReader) filterFieldList(tinfo *typeInfo, fields *ast.FieldList) (removedFields bool) {
if fields == nil { if fields == nil {
return false return
} }
list := fields.List list := fields.List
j := 0 j := 0
for _, f := range list { for _, field := range list {
keepField := false keepField := false
if len(f.Names) == 0 { if n := len(field.Names); n == 0 {
// anonymous field // anonymous field
name := baseName(f.Type) name, imp := baseTypeName(field.Type)
if name != nil && name.IsExported() { if ast.IsExported(name) {
// we keep the field - in this case doc.addDecl // we keep the field - in this case r.readDecl
// will take care of adding the embedded type // will take care of adding the embedded type
keepField = true keepField = true
} else if tinfo != nil { } else if base != nil && !imp {
// we don't keep the field - add it as an embedded // we don't keep the field - add it as an embedded
// type so we won't loose its methods, if any // type so we won't loose its methods, if any
if embedded := doc.lookupTypeInfo(name.Name); embedded != nil { if embedded := r.lookupType(name); embedded != nil {
_, ptr := f.Type.(*ast.StarExpr) _, ptr := field.Type.(*ast.StarExpr)
tinfo.addEmbeddedType(embedded, ptr) base.addEmbeddedType(embedded, ptr)
} }
} }
} else { } else {
n := len(f.Names) field.Names = filterIdentList(field.Names)
f.Names = filterIdentList(f.Names) if len(field.Names) < n {
if len(f.Names) < n {
removedFields = true removedFields = true
} }
keepField = len(f.Names) > 0 if len(field.Names) > 0 {
keepField = true
}
} }
if keepField { if keepField {
doc.filterType(nil, f.Type) r.filterType(nil, field.Type)
list[j] = f list[j] = field
j++ j++
} }
} }
@ -77,52 +72,48 @@ func (doc *docReader) filterFieldList(tinfo *typeInfo, fields *ast.FieldList) (r
return return
} }
func (doc *docReader) filterParamList(fields *ast.FieldList) bool { // filterParamList applies filterType to each parameter type in fields.
if fields == nil { //
return false func (r *reader) filterParamList(fields *ast.FieldList) {
} if fields != nil {
var b bool for _, f := range fields.List {
for _, f := range fields.List { r.filterType(nil, f.Type)
if doc.filterType(nil, f.Type) {
b = true
} }
} }
return b
} }
func (doc *docReader) filterType(tinfo *typeInfo, typ ast.Expr) bool { // filterType strips any unexported struct fields or method types from typ
// in place. If fields (or methods) have been removed, the corresponding
// struct or interface type has the Incomplete field set to true.
//
func (r *reader) filterType(base *baseType, typ ast.Expr) {
switch t := typ.(type) { switch t := typ.(type) {
case *ast.Ident: case *ast.Ident:
return ast.IsExported(t.Name) // nothing to do
case *ast.ParenExpr: case *ast.ParenExpr:
return doc.filterType(nil, t.X) r.filterType(nil, t.X)
case *ast.ArrayType: case *ast.ArrayType:
return doc.filterType(nil, t.Elt) r.filterType(nil, t.Elt)
case *ast.StructType: case *ast.StructType:
if doc.filterFieldList(tinfo, t.Fields) { if r.filterFieldList(base, t.Fields) {
t.Incomplete = true t.Incomplete = true
} }
return len(t.Fields.List) > 0
case *ast.FuncType: case *ast.FuncType:
b1 := doc.filterParamList(t.Params) r.filterParamList(t.Params)
b2 := doc.filterParamList(t.Results) r.filterParamList(t.Results)
return b1 || b2
case *ast.InterfaceType: case *ast.InterfaceType:
if doc.filterFieldList(tinfo, t.Methods) { if r.filterFieldList(base, t.Methods) {
t.Incomplete = true t.Incomplete = true
} }
return len(t.Methods.List) > 0
case *ast.MapType: case *ast.MapType:
b1 := doc.filterType(nil, t.Key) r.filterType(nil, t.Key)
b2 := doc.filterType(nil, t.Value) r.filterType(nil, t.Value)
return b1 || b2
case *ast.ChanType: case *ast.ChanType:
return doc.filterType(nil, t.Value) r.filterType(nil, t.Value)
} }
return false
} }
func (doc *docReader) filterSpec(spec ast.Spec) bool { func (r *reader) filterSpec(spec ast.Spec) bool {
switch s := spec.(type) { switch s := spec.(type) {
case *ast.ImportSpec: case *ast.ImportSpec:
// always keep imports so we can collect them // always keep imports so we can collect them
@ -130,22 +121,22 @@ func (doc *docReader) filterSpec(spec ast.Spec) bool {
case *ast.ValueSpec: case *ast.ValueSpec:
s.Names = filterIdentList(s.Names) s.Names = filterIdentList(s.Names)
if len(s.Names) > 0 { if len(s.Names) > 0 {
doc.filterType(nil, s.Type) r.filterType(nil, s.Type)
return true return true
} }
case *ast.TypeSpec: case *ast.TypeSpec:
if ast.IsExported(s.Name.Name) { if ast.IsExported(s.Name.Name) {
doc.filterType(doc.lookupTypeInfo(s.Name.Name), s.Type) r.filterType(r.lookupType(s.Name.Name), s.Type)
return true return true
} }
} }
return false return false
} }
func (doc *docReader) filterSpecList(list []ast.Spec) []ast.Spec { func (r *reader) filterSpecList(list []ast.Spec) []ast.Spec {
j := 0 j := 0
for _, s := range list { for _, s := range list {
if doc.filterSpec(s) { if r.filterSpec(s) {
list[j] = s list[j] = s
j++ j++
} }
@ -153,10 +144,10 @@ func (doc *docReader) filterSpecList(list []ast.Spec) []ast.Spec {
return list[0:j] return list[0:j]
} }
func (doc *docReader) filterDecl(decl ast.Decl) bool { func (r *reader) filterDecl(decl ast.Decl) bool {
switch d := decl.(type) { switch d := decl.(type) {
case *ast.GenDecl: case *ast.GenDecl:
d.Specs = doc.filterSpecList(d.Specs) d.Specs = r.filterSpecList(d.Specs)
return len(d.Specs) > 0 return len(d.Specs) > 0
case *ast.FuncDecl: case *ast.FuncDecl:
return ast.IsExported(d.Name.Name) return ast.IsExported(d.Name.Name)
@ -164,18 +155,15 @@ func (doc *docReader) filterDecl(decl ast.Decl) bool {
return false return false
} }
// fileExports trims the AST for a Go file in place such that // fileExports removes unexported declarations from src in place.
// only exported nodes remain. fileExports returns true if
// there are exported declarations; otherwise it returns false.
// //
func (doc *docReader) fileExports(src *ast.File) bool { func (r *reader) fileExports(src *ast.File) {
j := 0 j := 0
for _, d := range src.Decls { for _, d := range src.Decls {
if doc.filterDecl(d) { if r.filterDecl(d) {
src.Decls[j] = d src.Decls[j] = d
j++ j++
} }
} }
src.Decls = src.Decls[0:j] src.Decls = src.Decls[0:j]
return j > 0
} }

File diff suppressed because it is too large Load Diff

31
src/pkg/go/doc/testdata/e.0.golden vendored Normal file
View File

@ -0,0 +1,31 @@
//
PACKAGE e
IMPORTPATH
testdata/e
FILENAMES
testdata/e.go
TYPES
// T1 has no (top-level) M method due to conflict.
type T1 struct {
// contains filtered or unexported fields
}
// T2 has only M as top-level method.
type T2 struct {
// contains filtered or unexported fields
}
// T2.M should appear as method of T2.
func (T2) M()
// T3 has only M as top-level method.
type T3 struct {
// contains filtered or unexported fields
}
// T3.M should appear as method of T3.
func (T3) M()

61
src/pkg/go/doc/testdata/e.1.golden vendored Normal file
View File

@ -0,0 +1,61 @@
//
PACKAGE e
IMPORTPATH
testdata/e
FILENAMES
testdata/e.go
TYPES
// T1 has no (top-level) M method due to conflict.
type T1 struct {
t1
t2
}
// T2 has only M as top-level method.
type T2 struct {
t1
}
// T2.M should appear as method of T2.
func (T2) M()
// T3 has only M as top-level method.
type T3 struct {
t1e
t2e
}
// T3.M should appear as method of T3.
func (T3) M()
//
type t1 struct{}
// t1.M should not appear as method in a Tx type.
func (t1) M()
//
type t1e struct {
t1
}
// t1.M should not appear as method in a Tx type.
func (t1e) M()
//
type t2 struct{}
// t2.M should not appear as method in a Tx type.
func (t2) M()
//
type t2e struct {
t2
}
// t2.M should not appear as method in a Tx type.
func (t2e) M()

58
src/pkg/go/doc/testdata/e.go vendored Normal file
View File

@ -0,0 +1,58 @@
// Copyright 2011 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.
// Embedding tests.
// TODO(gri): This should be comprehensive.
package e
// ----------------------------------------------------------------------------
// Conflicting methods M must not show up.
type t1 struct{}
// t1.M should not appear as method in a Tx type.
func (t1) M() {}
type t2 struct{}
// t2.M should not appear as method in a Tx type.
func (t2) M() {}
// T1 has no (top-level) M method due to conflict.
type T1 struct {
t1
t2
}
// ----------------------------------------------------------------------------
// Higher-level method M wins over lower-level method M.
// T2 has only M as top-level method.
type T2 struct {
t1
}
// T2.M should appear as method of T2.
func (T2) M() {}
// ----------------------------------------------------------------------------
// Higher-level method M wins over lower-level conflicting methods M.
type t1e struct {
t1
}
type t2e struct {
t2
}
// T3 has only M as top-level method.
type T3 struct {
t1e
t2e
}
// T3.M should appear as method of T3.
func (T3) M() {}