mirror of
https://github.com/golang/go
synced 2024-11-22 03:24:41 -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:
parent
6e3af46e7d
commit
6d68be46dd
@ -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)
|
|
||||||
}
|
}
|
||||||
|
@ -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
31
src/pkg/go/doc/testdata/e.0.golden
vendored
Normal 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
61
src/pkg/go/doc/testdata/e.1.golden
vendored
Normal 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
58
src/pkg/go/doc/testdata/e.go
vendored
Normal 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() {}
|
Loading…
Reference in New Issue
Block a user