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 // Package doc extracts source code documentation from a Go AST.
15 // ----------------------------------------------------------------------------
18 // len(decl.Specs) == 1, and the element type is *ast.TypeSpec
19 // if the type declaration hasn't been seen yet, decl is nil
21 // values, factory functions, and methods associated with the type
22 values []*ast.GenDecl // consts and vars
23 factories map[string]*ast.FuncDecl
24 methods map[string]*ast.FuncDecl
27 // docReader accumulates documentation for a single package.
28 // It modifies the AST: Comments (declaration documentation)
29 // that have been collected by the DocReader are set to nil
30 // in the respective AST nodes so that they are not printed
31 // twice (once when printing the documentation and once when
32 // printing the corresponding AST node).
34 type docReader struct {
35 doc *ast.CommentGroup // package documentation, if any
37 values []*ast.GenDecl // consts and vars
38 types map[string]*typeDoc
39 funcs map[string]*ast.FuncDecl
40 bugs []*ast.CommentGroup
43 func (doc *docReader) init(pkgName string) {
45 doc.types = make(map[string]*typeDoc)
46 doc.funcs = make(map[string]*ast.FuncDecl)
49 func (doc *docReader) addDoc(comments *ast.CommentGroup) {
51 // common case: just one package comment
56 // More than one package comment: Usually there will be only
57 // one file with a package comment, but it's better to collect
58 // all comments than drop them on the floor.
59 // (This code isn't particularly clever - no amortized doubling is
60 // used - but this situation occurs rarely and is not time-critical.)
61 n1 := len(doc.doc.List)
62 n2 := len(comments.List)
63 list := make([]*ast.Comment, n1+1+n2) // + 1 for separator line
64 copy(list, doc.doc.List)
65 list[n1] = &ast.Comment{token.NoPos, "//"} // separator line
66 copy(list[n1+1:], comments.List)
67 doc.doc = &ast.CommentGroup{list}
70 func (doc *docReader) addType(decl *ast.GenDecl) {
71 spec := decl.Specs[0].(*ast.TypeSpec)
72 typ := doc.lookupTypeDoc(spec.Name.Name)
73 // typ should always be != nil since declared types
74 // are always named - be conservative and check
76 // a type should be added at most once, so typ.decl
77 // should be nil - if it isn't, simply overwrite it
82 func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
84 return nil // no type docs for anonymous types
86 if tdoc, found := doc.types[name]; found {
89 // type wasn't found - add one without declaration
90 tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl)}
91 doc.types[name] = tdoc
95 func baseTypeName(typ ast.Expr) string {
96 switch t := typ.(type) {
98 // if the type is not exported, the effect to
99 // a client is as if there were no type name
104 return baseTypeName(t.X)
109 func (doc *docReader) addValue(decl *ast.GenDecl) {
110 // determine if decl should be associated with a type
111 // Heuristic: For each typed entry, determine the type name, if any.
112 // If there is exactly one type name that is sufficiently
113 // frequent, associate the decl with the respective type.
117 for _, s := range decl.Specs {
118 if v, ok := s.(*ast.ValueSpec); ok {
122 // a type is present; determine its name
123 name = baseTypeName(v.Type)
124 case decl.Tok == token.CONST:
125 // no type is present but we have a constant declaration;
126 // use the previous type name (w/o more type information
127 // we cannot handle the case of unnamed variables with
128 // initializer expressions except for some trivial cases)
132 // entry has a named type
133 if domName != "" && domName != name {
134 // more than one type name - do not associate
146 // determine values list
147 const threshold = 0.75
148 values := &doc.values
149 if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) {
150 // typed entries are sufficiently frequent
151 typ := doc.lookupTypeDoc(domName)
153 values = &typ.values // associate with that type
157 *values = append(*values, decl)
160 // Helper function to set the table entry for function f. Makes sure that
161 // at least one f with associated documentation is stored in table, if there
162 // are multiple f's with the same name.
163 func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) {
165 if g, exists := table[name]; exists && g.Doc != nil {
166 // a function with the same name has already been registered;
167 // since it has documentation, assume f is simply another
168 // implementation and ignore it
169 // TODO(gri) consider collecting all functions, or at least
173 // function doesn't exist or has no documentation; use f
177 func (doc *docReader) addFunc(fun *ast.FuncDecl) {
178 name := fun.Name.Name
180 // determine if it should be associated with a type
183 typ := doc.lookupTypeDoc(baseTypeName(fun.Recv.List[0].Type))
185 // exported receiver type
186 setFunc(typ.methods, fun)
188 // otherwise don't show the method
189 // TODO(gri): There may be exported methods of non-exported types
190 // that can be called because of exported values (consts, vars, or
191 // function results) of that type. Could determine if that is the
192 // case and then show those methods in an appropriate section.
196 // perhaps a factory function
197 // determine result type, if any
198 if fun.Type.Results.NumFields() >= 1 {
199 res := fun.Type.Results.List[0]
200 if len(res.Names) <= 1 {
201 // exactly one (named or anonymous) result associated
202 // with the first type in result signature (there may
203 // be more than one result)
204 tname := baseTypeName(res.Type)
205 typ := doc.lookupTypeDoc(tname)
207 // named and exported result type
209 // Work-around for failure of heuristic: In package os
210 // too many functions are considered factory functions
211 // for the Error type. Eliminate manually for now as
212 // this appears to be the only important case in the
213 // current library where the heuristic fails.
214 if doc.pkgName == "os" && tname == "Error" &&
215 name != "NewError" && name != "NewSyscallError" {
216 // not a factory function for os.Error
217 setFunc(doc.funcs, fun) // treat as ordinary function
221 setFunc(typ.factories, fun)
228 setFunc(doc.funcs, fun)
231 func (doc *docReader) addDecl(decl ast.Decl) {
232 switch d := decl.(type) {
234 if len(d.Specs) > 0 {
236 case token.CONST, token.VAR:
237 // constants and variables are always handled as a group
240 // types are handled individually
241 for _, spec := range d.Specs {
242 // make a (fake) GenDecl node for this TypeSpec
243 // (we need to do this here - as opposed to just
244 // for printing - so we don't lose the GenDecl
247 // TODO(gri): Consider just collecting the TypeSpec
248 // node (and copy in the GenDecl.doc if there is no
249 // doc in the TypeSpec - this is currently done in
250 // makeTypeDocs below). Simpler data structures, but
251 // would lose GenDecl documentation if the TypeSpec
252 // has documentation as well.
253 doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos})
254 // A new GenDecl node is created, no need to nil out d.Doc.
263 func copyCommentList(list []*ast.Comment) []*ast.Comment {
264 return append([]*ast.Comment(nil), list...)
268 bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*") // BUG(uid):
269 bug_content = regexp.MustCompile("[^ \n\r\t]+") // at least one non-whitespace char
272 // addFile adds the AST for a source file to the docReader.
273 // Adding the same AST multiple times is a no-op.
275 func (doc *docReader) addFile(src *ast.File) {
276 // add package documentation
279 src.Doc = nil // doc consumed - remove from ast.File node
282 // add all declarations
283 for _, decl := range src.Decls {
287 // collect BUG(...) comments
288 for _, c := range src.Comments {
289 text := c.List[0].Text
290 if m := bug_markers.FindStringIndex(text); m != nil {
291 // found a BUG comment; maybe empty
292 if btxt := text[m[1]:]; bug_content.MatchString(btxt) {
293 // non-empty BUG comment; collect comment without BUG prefix
294 list := copyCommentList(c.List)
295 list[0].Text = text[m[1]:]
296 doc.bugs = append(doc.bugs, &ast.CommentGroup{list})
300 src.Comments = nil // consumed unassociated comments - remove from ast.File node
303 func NewFileDoc(file *ast.File) *PackageDoc {
305 r.init(file.Name.Name)
307 return r.newDoc("", nil)
310 func NewPackageDoc(pkg *ast.Package, importpath string) *PackageDoc {
313 filenames := make([]string, len(pkg.Files))
315 for filename, f := range pkg.Files {
317 filenames[i] = filename
320 return r.newDoc(importpath, filenames)
323 // ----------------------------------------------------------------------------
324 // Conversion to external representation
326 // ValueDoc is the documentation for a group of declared
327 // values, either vars or consts.
329 type ValueDoc struct {
335 type sortValueDoc []*ValueDoc
337 func (p sortValueDoc) Len() int { return len(p) }
338 func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
340 func declName(d *ast.GenDecl) string {
341 if len(d.Specs) != 1 {
345 switch v := d.Specs[0].(type) {
347 return v.Names[0].Name
355 func (p sortValueDoc) Less(i, j int) bool {
357 // pull blocks (name = "") up to top
359 if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj {
362 return p[i].order < p[j].order
365 func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc {
366 d := make([]*ValueDoc, len(list)) // big enough in any case
368 for i, decl := range list {
370 d[n] = &ValueDoc{CommentText(decl.Doc), decl, i}
372 decl.Doc = nil // doc consumed - removed from AST
376 sort.Sort(sortValueDoc(d))
380 // FuncDoc is the documentation for a func declaration,
381 // either a top-level function or a method function.
383 type FuncDoc struct {
385 Recv ast.Expr // TODO(rsc): Would like string here
390 type sortFuncDoc []*FuncDoc
392 func (p sortFuncDoc) Len() int { return len(p) }
393 func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
394 func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name }
396 func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc {
397 d := make([]*FuncDoc, len(m))
399 for _, f := range m {
401 doc.Doc = CommentText(f.Doc)
402 f.Doc = nil // doc consumed - remove from ast.FuncDecl node
404 doc.Recv = f.Recv.List[0].Type
406 doc.Name = f.Name.Name
411 sort.Sort(sortFuncDoc(d))
415 // TypeDoc is the documentation for a declared type.
416 // Consts and Vars are sorted lists of constants and variables of (mostly) that type.
417 // Factories is a sorted list of factory functions that return that type.
418 // Methods is a sorted list of method functions on that type.
419 type TypeDoc struct {
430 type sortTypeDoc []*TypeDoc
432 func (p sortTypeDoc) Len() int { return len(p) }
433 func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
434 func (p sortTypeDoc) Less(i, j int) bool {
436 // pull blocks (name = "") up to top
438 if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj {
441 return p[i].order < p[j].order
444 // NOTE(rsc): This would appear not to be correct for type ( )
445 // blocks, but the doc extractor above has split them into
446 // individual declarations.
447 func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
448 d := make([]*TypeDoc, len(m))
450 for _, old := range m {
451 // all typeDocs should have a declaration associated with
452 // them after processing an entire package - be conservative
454 if decl := old.decl; decl != nil {
455 typespec := decl.Specs[0].(*ast.TypeSpec)
458 typespec.Doc = nil // doc consumed - remove from ast.TypeSpec node
460 // no doc associated with the spec, use the declaration doc, if any
463 decl.Doc = nil // doc consumed - remove from ast.Decl node
464 t.Doc = CommentText(doc)
466 t.Consts = makeValueDocs(old.values, token.CONST)
467 t.Vars = makeValueDocs(old.values, token.VAR)
468 t.Factories = makeFuncDocs(old.factories)
469 t.Methods = makeFuncDocs(old.methods)
475 // no corresponding type declaration found - move any associated
476 // values, factory functions, and methods back to the top-level
477 // so that they are not lost (this should only happen if a package
478 // file containing the explicit type declaration is missing or if
479 // an unqualified type name was used after a "." import)
481 doc.values = append(doc.values, old.values...)
482 // 2) move factory functions
483 for name, f := range old.factories {
487 for name, f := range old.methods {
488 // don't overwrite functions with the same name
489 if _, found := doc.funcs[name]; !found {
495 d = d[0:i] // some types may have been ignored
496 sort.Sort(sortTypeDoc(d))
500 func makeBugDocs(list []*ast.CommentGroup) []string {
501 d := make([]string, len(list))
502 for i, g := range list {
503 d[i] = CommentText(g)
508 // PackageDoc is the documentation for an entire package.
510 type PackageDoc struct {
522 // newDoc returns the accumulated documentation for the package.
524 func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc {
526 p.PackageName = doc.pkgName
527 p.ImportPath = importpath
528 sort.Strings(filenames)
529 p.Filenames = filenames
530 p.Doc = CommentText(doc.doc)
531 // makeTypeDocs may extend the list of doc.values and
532 // doc.funcs and thus must be called before any other
533 // function consuming those lists
534 p.Types = doc.makeTypeDocs(doc.types)
535 p.Consts = makeValueDocs(doc.values, token.CONST)
536 p.Vars = makeValueDocs(doc.values, token.VAR)
537 p.Funcs = makeFuncDocs(doc.funcs)
538 p.Bugs = makeBugDocs(doc.bugs)
542 // ----------------------------------------------------------------------------
545 type Filter func(string) bool
547 func matchFields(fields *ast.FieldList, f Filter) bool {
549 for _, field := range fields.List {
550 for _, name := range field.Names {
560 func matchDecl(d *ast.GenDecl, f Filter) bool {
561 for _, d := range d.Specs {
562 switch v := d.(type) {
564 for _, name := range v.Names {
573 switch t := v.Type.(type) {
574 case *ast.StructType:
575 if matchFields(t.Fields, f) {
578 case *ast.InterfaceType:
579 if matchFields(t.Methods, f) {
588 func filterValueDocs(a []*ValueDoc, f Filter) []*ValueDoc {
590 for _, vd := range a {
591 if matchDecl(vd.Decl, f) {
599 func filterFuncDocs(a []*FuncDoc, f Filter) []*FuncDoc {
601 for _, fd := range a {
610 func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc {
612 for _, td := range a {
613 n := 0 // number of matches
614 if matchDecl(td.Decl, f) {
617 // type name doesn't match, but we may have matching consts, vars, factories or methods
618 td.Consts = filterValueDocs(td.Consts, f)
619 td.Vars = filterValueDocs(td.Vars, f)
620 td.Factories = filterFuncDocs(td.Factories, f)
621 td.Methods = filterFuncDocs(td.Methods, f)
622 n += len(td.Consts) + len(td.Vars) + len(td.Factories) + len(td.Methods)
632 // Filter eliminates documentation for names that don't pass through the filter f.
633 // TODO: Recognize "Type.Method" as a name.
635 func (p *PackageDoc) Filter(f Filter) {
636 p.Consts = filterValueDocs(p.Consts, f)
637 p.Vars = filterValueDocs(p.Vars, f)
638 p.Types = filterTypeDocs(p.Types, f)
639 p.Funcs = filterFuncDocs(p.Funcs, f)
640 p.Doc = "" // don't show top-level package doc