1
// Copyright 2009 The Go Authors. All rights reserved.
2
// Use of this source code is governed by a BSD-style
3
// license that can be found in the LICENSE file.
5
// The doc package extracts source code documentation from a Go AST.
16
// ----------------------------------------------------------------------------
19
// len(decl.Specs) == 1, and the element type is *ast.TypeSpec
20
// if the type declaration hasn't been seen yet, decl is nil
22
// values, factory functions, and methods associated with the type
23
values []*ast.GenDecl // consts and vars
24
factories map[string]*ast.FuncDecl
25
methods map[string]*ast.FuncDecl
29
// docReader accumulates documentation for a single package.
30
// It modifies the AST: Comments (declaration documentation)
31
// that have been collected by the DocReader are set to nil
32
// in the respective AST nodes so that they are not printed
33
// twice (once when printing the documentation and once when
34
// printing the corresponding AST node).
36
type docReader struct {
37
doc *ast.CommentGroup // package documentation, if any
39
values []*ast.GenDecl // consts and vars
40
types map[string]*typeDoc
41
funcs map[string]*ast.FuncDecl
42
bugs []*ast.CommentGroup
46
func (doc *docReader) init(pkgName string) {
48
doc.types = make(map[string]*typeDoc)
49
doc.funcs = make(map[string]*ast.FuncDecl)
53
func (doc *docReader) addDoc(comments *ast.CommentGroup) {
55
// common case: just one package comment
60
// More than one package comment: Usually there will be only
61
// one file with a package comment, but it's better to collect
62
// all comments than drop them on the floor.
63
// (This code isn't particularly clever - no amortized doubling is
64
// used - but this situation occurs rarely and is not time-critical.)
65
n1 := len(doc.doc.List)
66
n2 := len(comments.List)
67
list := make([]*ast.Comment, n1+1+n2) // + 1 for separator line
68
copy(list, doc.doc.List)
69
list[n1] = &ast.Comment{token.NoPos, []byte("//")} // separator line
70
copy(list[n1+1:], comments.List)
71
doc.doc = &ast.CommentGroup{list}
75
func (doc *docReader) addType(decl *ast.GenDecl) {
76
spec := decl.Specs[0].(*ast.TypeSpec)
77
typ := doc.lookupTypeDoc(spec.Name.Name)
78
// typ should always be != nil since declared types
79
// are always named - be conservative and check
81
// a type should be added at most once, so typ.decl
82
// should be nil - if it isn't, simply overwrite it
88
func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
90
return nil // no type docs for anonymous types
92
if tdoc, found := doc.types[name]; found {
95
// type wasn't found - add one without declaration
96
tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl)}
97
doc.types[name] = tdoc
102
func baseTypeName(typ ast.Expr) string {
103
switch t := typ.(type) {
105
// if the type is not exported, the effect to
106
// a client is as if there were no type name
108
return string(t.Name)
111
return baseTypeName(t.X)
117
func (doc *docReader) addValue(decl *ast.GenDecl) {
118
// determine if decl should be associated with a type
119
// Heuristic: For each typed entry, determine the type name, if any.
120
// If there is exactly one type name that is sufficiently
121
// frequent, associate the decl with the respective type.
125
for _, s := range decl.Specs {
126
if v, ok := s.(*ast.ValueSpec); ok {
130
// a type is present; determine its name
131
name = baseTypeName(v.Type)
132
case decl.Tok == token.CONST:
133
// no type is present but we have a constant declaration;
134
// use the previous type name (w/o more type information
135
// we cannot handle the case of unnamed variables with
136
// initializer expressions except for some trivial cases)
140
// entry has a named type
141
if domName != "" && domName != name {
142
// more than one type name - do not associate
154
// determine values list
155
const threshold = 0.75
156
values := &doc.values
157
if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) {
158
// typed entries are sufficiently frequent
159
typ := doc.lookupTypeDoc(domName)
161
values = &typ.values // associate with that type
165
*values = append(*values, decl)
169
// Helper function to set the table entry for function f. Makes sure that
170
// at least one f with associated documentation is stored in table, if there
171
// are multiple f's with the same name.
172
func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) {
174
if g, exists := table[name]; exists && g.Doc != nil {
175
// a function with the same name has already been registered;
176
// since it has documentation, assume f is simply another
177
// implementation and ignore it
178
// TODO(gri) consider collecting all functions, or at least
182
// function doesn't exist or has no documentation; use f
187
func (doc *docReader) addFunc(fun *ast.FuncDecl) {
188
name := fun.Name.Name
190
// determine if it should be associated with a type
193
typ := doc.lookupTypeDoc(baseTypeName(fun.Recv.List[0].Type))
195
// exported receiver type
196
setFunc(typ.methods, fun)
198
// otherwise don't show the method
199
// TODO(gri): There may be exported methods of non-exported types
200
// that can be called because of exported values (consts, vars, or
201
// function results) of that type. Could determine if that is the
202
// case and then show those methods in an appropriate section.
206
// perhaps a factory function
207
// determine result type, if any
208
if fun.Type.Results.NumFields() >= 1 {
209
res := fun.Type.Results.List[0]
210
if len(res.Names) <= 1 {
211
// exactly one (named or anonymous) result associated
212
// with the first type in result signature (there may
213
// be more than one result)
214
tname := baseTypeName(res.Type)
215
typ := doc.lookupTypeDoc(tname)
217
// named and exported result type
219
// Work-around for failure of heuristic: In package os
220
// too many functions are considered factory functions
221
// for the Error type. Eliminate manually for now as
222
// this appears to be the only important case in the
223
// current library where the heuristic fails.
224
if doc.pkgName == "os" && tname == "Error" &&
225
name != "NewError" && name != "NewSyscallError" {
226
// not a factory function for os.Error
227
setFunc(doc.funcs, fun) // treat as ordinary function
231
setFunc(typ.factories, fun)
238
setFunc(doc.funcs, fun)
242
func (doc *docReader) addDecl(decl ast.Decl) {
243
switch d := decl.(type) {
245
if len(d.Specs) > 0 {
247
case token.CONST, token.VAR:
248
// constants and variables are always handled as a group
251
// types are handled individually
252
for _, spec := range d.Specs {
253
// make a (fake) GenDecl node for this TypeSpec
254
// (we need to do this here - as opposed to just
255
// for printing - so we don't lose the GenDecl
258
// TODO(gri): Consider just collecting the TypeSpec
259
// node (and copy in the GenDecl.doc if there is no
260
// doc in the TypeSpec - this is currently done in
261
// makeTypeDocs below). Simpler data structures, but
262
// would lose GenDecl documentation if the TypeSpec
263
// has documentation as well.
264
doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos})
265
// A new GenDecl node is created, no need to nil out d.Doc.
275
func copyCommentList(list []*ast.Comment) []*ast.Comment {
276
return append([]*ast.Comment(nil), list...)
280
bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*") // BUG(uid):
281
bug_content = regexp.MustCompile("[^ \n\r\t]+") // at least one non-whitespace char
285
// addFile adds the AST for a source file to the docReader.
286
// Adding the same AST multiple times is a no-op.
288
func (doc *docReader) addFile(src *ast.File) {
289
// add package documentation
292
src.Doc = nil // doc consumed - remove from ast.File node
295
// add all declarations
296
for _, decl := range src.Decls {
300
// collect BUG(...) comments
301
for _, c := range src.Comments {
302
text := c.List[0].Text
303
if m := bug_markers.FindIndex(text); m != nil {
304
// found a BUG comment; maybe empty
305
if btxt := text[m[1]:]; bug_content.Match(btxt) {
306
// non-empty BUG comment; collect comment without BUG prefix
307
list := copyCommentList(c.List)
308
list[0].Text = text[m[1]:]
309
doc.bugs = append(doc.bugs, &ast.CommentGroup{list})
313
src.Comments = nil // consumed unassociated comments - remove from ast.File node
317
func NewFileDoc(file *ast.File) *PackageDoc {
319
r.init(file.Name.Name)
321
return r.newDoc("", nil)
325
func NewPackageDoc(pkg *ast.Package, importpath string) *PackageDoc {
328
filenames := make([]string, len(pkg.Files))
330
for filename, f := range pkg.Files {
332
filenames[i] = filename
335
return r.newDoc(importpath, filenames)
339
// ----------------------------------------------------------------------------
340
// Conversion to external representation
342
// ValueDoc is the documentation for a group of declared
343
// values, either vars or consts.
345
type ValueDoc struct {
351
type sortValueDoc []*ValueDoc
353
func (p sortValueDoc) Len() int { return len(p) }
354
func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
357
func declName(d *ast.GenDecl) string {
358
if len(d.Specs) != 1 {
362
switch v := d.Specs[0].(type) {
364
return v.Names[0].Name
373
func (p sortValueDoc) Less(i, j int) bool {
375
// pull blocks (name = "") up to top
377
if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj {
380
return p[i].order < p[j].order
384
func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc {
385
d := make([]*ValueDoc, len(list)) // big enough in any case
387
for i, decl := range list {
389
d[n] = &ValueDoc{CommentText(decl.Doc), decl, i}
391
decl.Doc = nil // doc consumed - removed from AST
395
sort.Sort(sortValueDoc(d))
400
// FuncDoc is the documentation for a func declaration,
401
// either a top-level function or a method function.
403
type FuncDoc struct {
405
Recv ast.Expr // TODO(rsc): Would like string here
410
type sortFuncDoc []*FuncDoc
412
func (p sortFuncDoc) Len() int { return len(p) }
413
func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
414
func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name }
417
func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc {
418
d := make([]*FuncDoc, len(m))
420
for _, f := range m {
422
doc.Doc = CommentText(f.Doc)
423
f.Doc = nil // doc consumed - remove from ast.FuncDecl node
425
doc.Recv = f.Recv.List[0].Type
427
doc.Name = f.Name.Name
432
sort.Sort(sortFuncDoc(d))
437
// TypeDoc is the documentation for a declared type.
438
// Consts and Vars are sorted lists of constants and variables of (mostly) that type.
439
// Factories is a sorted list of factory functions that return that type.
440
// Methods is a sorted list of method functions on that type.
441
type TypeDoc struct {
452
type sortTypeDoc []*TypeDoc
454
func (p sortTypeDoc) Len() int { return len(p) }
455
func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
456
func (p sortTypeDoc) Less(i, j int) bool {
458
// pull blocks (name = "") up to top
460
if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj {
463
return p[i].order < p[j].order
467
// NOTE(rsc): This would appear not to be correct for type ( )
468
// blocks, but the doc extractor above has split them into
469
// individual declarations.
470
func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
471
d := make([]*TypeDoc, len(m))
473
for _, old := range m {
474
// all typeDocs should have a declaration associated with
475
// them after processing an entire package - be conservative
477
if decl := old.decl; decl != nil {
478
typespec := decl.Specs[0].(*ast.TypeSpec)
481
typespec.Doc = nil // doc consumed - remove from ast.TypeSpec node
483
// no doc associated with the spec, use the declaration doc, if any
486
decl.Doc = nil // doc consumed - remove from ast.Decl node
487
t.Doc = CommentText(doc)
489
t.Consts = makeValueDocs(old.values, token.CONST)
490
t.Vars = makeValueDocs(old.values, token.VAR)
491
t.Factories = makeFuncDocs(old.factories)
492
t.Methods = makeFuncDocs(old.methods)
498
// no corresponding type declaration found - move any associated
499
// values, factory functions, and methods back to the top-level
500
// so that they are not lost (this should only happen if a package
501
// file containing the explicit type declaration is missing or if
502
// an unqualified type name was used after a "." import)
504
doc.values = append(doc.values, old.values...)
505
// 2) move factory functions
506
for name, f := range old.factories {
510
for name, f := range old.methods {
511
// don't overwrite functions with the same name
512
if _, found := doc.funcs[name]; !found {
518
d = d[0:i] // some types may have been ignored
519
sort.Sort(sortTypeDoc(d))
524
func makeBugDocs(list []*ast.CommentGroup) []string {
525
d := make([]string, len(list))
526
for i, g := range list {
527
d[i] = CommentText(g)
533
// PackageDoc is the documentation for an entire package.
535
type PackageDoc struct {
548
// newDoc returns the accumulated documentation for the package.
550
func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc {
552
p.PackageName = doc.pkgName
553
p.ImportPath = importpath
554
sort.SortStrings(filenames)
555
p.Filenames = filenames
556
p.Doc = CommentText(doc.doc)
557
// makeTypeDocs may extend the list of doc.values and
558
// doc.funcs and thus must be called before any other
559
// function consuming those lists
560
p.Types = doc.makeTypeDocs(doc.types)
561
p.Consts = makeValueDocs(doc.values, token.CONST)
562
p.Vars = makeValueDocs(doc.values, token.VAR)
563
p.Funcs = makeFuncDocs(doc.funcs)
564
p.Bugs = makeBugDocs(doc.bugs)
569
// ----------------------------------------------------------------------------
572
type Filter func(string) bool
575
func matchDecl(d *ast.GenDecl, f Filter) bool {
576
for _, d := range d.Specs {
577
switch v := d.(type) {
579
for _, name := range v.Names {
594
func filterValueDocs(a []*ValueDoc, f Filter) []*ValueDoc {
596
for _, vd := range a {
597
if matchDecl(vd.Decl, f) {
606
func filterFuncDocs(a []*FuncDoc, f Filter) []*FuncDoc {
608
for _, fd := range a {
618
func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc {
620
for _, td := range a {
621
n := 0 // number of matches
622
if matchDecl(td.Decl, f) {
625
// type name doesn't match, but we may have matching consts, vars, factories or methods
626
td.Consts = filterValueDocs(td.Consts, f)
627
td.Vars = filterValueDocs(td.Vars, f)
628
td.Factories = filterFuncDocs(td.Factories, f)
629
td.Methods = filterFuncDocs(td.Methods, f)
630
n += len(td.Consts) + len(td.Vars) + len(td.Factories) + len(td.Methods)
641
// Filter eliminates documentation for names that don't pass through the filter f.
642
// TODO: Recognize "Type.Method" as a name.
644
func (p *PackageDoc) Filter(f Filter) {
645
p.Consts = filterValueDocs(p.Consts, f)
646
p.Vars = filterValueDocs(p.Vars, f)
647
p.Types = filterTypeDocs(p.Types, f)
648
p.Funcs = filterFuncDocs(p.Funcs, f)
649
p.Doc = "" // don't show top-level package doc