1
2
3
4
5
6 package doc
7
8 import (
9 "go/ast"
10 "go/token"
11 "regexp"
12 "sort"
13 )
14
15
16
17 type typeDoc struct {
18
19
20 decl *ast.GenDecl
21
22 values []*ast.GenDecl
23 factories map[string]*ast.FuncDecl
24 methods map[string]*ast.FuncDecl
25 }
26
27
28
29
30
31
32
33
34 type docReader struct {
35 doc *ast.CommentGroup
36 pkgName string
37 values []*ast.GenDecl
38 types map[string]*typeDoc
39 funcs map[string]*ast.FuncDecl
40 bugs []*ast.CommentGroup
41 }
42
43 func (doc *docReader) init(pkgName string) {
44 doc.pkgName = pkgName
45 doc.types = make(map[string]*typeDoc)
46 doc.funcs = make(map[string]*ast.FuncDecl)
47 }
48
49 func (doc *docReader) addDoc(comments *ast.CommentGroup) {
50 if doc.doc == nil {
51
52 doc.doc = comments
53 return
54 }
55
56
57
58
59
60
61 n1 := len(doc.doc.List)
62 n2 := len(comments.List)
63 list := make([]*ast.Comment, n1+1+n2)
64 copy(list, doc.doc.List)
65 list[n1] = &ast.Comment{token.NoPos, "//"}
66 copy(list[n1+1:], comments.List)
67 doc.doc = &ast.CommentGroup{list}
68 }
69
70 func (doc *docReader) addType(decl *ast.GenDecl) {
71 spec := decl.Specs[0].(*ast.TypeSpec)
72 typ := doc.lookupTypeDoc(spec.Name.Name)
73
74
75 if typ != nil {
76
77
78 typ.decl = decl
79 }
80 }
81
82 func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
83 if name == "" {
84 return nil
85 }
86 if tdoc, found := doc.types[name]; found {
87 return tdoc
88 }
89
90 tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl)}
91 doc.types[name] = tdoc
92 return tdoc
93 }
94
95 func baseTypeName(typ ast.Expr) string {
96 switch t := typ.(type) {
97 case *ast.Ident:
98
99
100 if t.IsExported() {
101 return t.Name
102 }
103 case *ast.StarExpr:
104 return baseTypeName(t.X)
105 }
106 return ""
107 }
108
109 func (doc *docReader) addValue(decl *ast.GenDecl) {
110
111
112
113
114 domName := ""
115 domFreq := 0
116 prev := ""
117 for _, s := range decl.Specs {
118 if v, ok := s.(*ast.ValueSpec); ok {
119 name := ""
120 switch {
121 case v.Type != nil:
122
123 name = baseTypeName(v.Type)
124 case decl.Tok == token.CONST:
125
126
127
128
129 name = prev
130 }
131 if name != "" {
132
133 if domName != "" && domName != name {
134
135
136 domName = ""
137 break
138 }
139 domName = name
140 domFreq++
141 }
142 prev = name
143 }
144 }
145
146
147 const threshold = 0.75
148 values := &doc.values
149 if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) {
150
151 typ := doc.lookupTypeDoc(domName)
152 if typ != nil {
153 values = &typ.values
154 }
155 }
156
157 *values = append(*values, decl)
158 }
159
160
161
162
163 func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) {
164 name := f.Name.Name
165 if g, exists := table[name]; exists && g.Doc != nil {
166
167
168
169
170
171 return
172 }
173
174 table[name] = f
175 }
176
177 func (doc *docReader) addFunc(fun *ast.FuncDecl) {
178 name := fun.Name.Name
179
180
181 if fun.Recv != nil {
182
183 typ := doc.lookupTypeDoc(baseTypeName(fun.Recv.List[0].Type))
184 if typ != nil {
185
186 setFunc(typ.methods, fun)
187 }
188
189
190
191
192
193 return
194 }
195
196
197
198 if fun.Type.Results.NumFields() >= 1 {
199 res := fun.Type.Results.List[0]
200 if len(res.Names) <= 1 {
201
202
203
204 tname := baseTypeName(res.Type)
205 typ := doc.lookupTypeDoc(tname)
206 if typ != nil {
207
208
209
210
211
212
213
214 if doc.pkgName == "os" && tname == "Error" &&
215 name != "NewError" && name != "NewSyscallError" {
216
217 setFunc(doc.funcs, fun)
218 return
219 }
220
221 setFunc(typ.factories, fun)
222 return
223 }
224 }
225 }
226
227
228 setFunc(doc.funcs, fun)
229 }
230
231 func (doc *docReader) addDecl(decl ast.Decl) {
232 switch d := decl.(type) {
233 case *ast.GenDecl:
234 if len(d.Specs) > 0 {
235 switch d.Tok {
236 case token.CONST, token.VAR:
237
238 doc.addValue(d)
239 case token.TYPE:
240
241 for _, spec := range d.Specs {
242
243
244
245
246
247
248
249
250
251
252
253 doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos})
254
255 }
256 }
257 }
258 case *ast.FuncDecl:
259 doc.addFunc(d)
260 }
261 }
262
263 func copyCommentList(list []*ast.Comment) []*ast.Comment {
264 return append([]*ast.Comment(nil), list...)
265 }
266
267 var (
268 bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*")
269 bug_content = regexp.MustCompile("[^ \n\r\t]+")
270 )
271
272
273
274
275 func (doc *docReader) addFile(src *ast.File) {
276
277 if src.Doc != nil {
278 doc.addDoc(src.Doc)
279 src.Doc = nil
280 }
281
282
283 for _, decl := range src.Decls {
284 doc.addDecl(decl)
285 }
286
287
288 for _, c := range src.Comments {
289 text := c.List[0].Text
290 if m := bug_markers.FindStringIndex(text); m != nil {
291
292 if btxt := text[m[1]:]; bug_content.MatchString(btxt) {
293
294 list := copyCommentList(c.List)
295 list[0].Text = text[m[1]:]
296 doc.bugs = append(doc.bugs, &ast.CommentGroup{list})
297 }
298 }
299 }
300 src.Comments = nil
301 }
302
303 func NewFileDoc(file *ast.File) *PackageDoc {
304 var r docReader
305 r.init(file.Name.Name)
306 r.addFile(file)
307 return r.newDoc("", nil)
308 }
309
310 func NewPackageDoc(pkg *ast.Package, importpath string) *PackageDoc {
311 var r docReader
312 r.init(pkg.Name)
313 filenames := make([]string, len(pkg.Files))
314 i := 0
315 for filename, f := range pkg.Files {
316 r.addFile(f)
317 filenames[i] = filename
318 i++
319 }
320 return r.newDoc(importpath, filenames)
321 }
322
323
324
325
326
327
328
329 type ValueDoc struct {
330 Doc string
331 Decl *ast.GenDecl
332 order int
333 }
334
335 type sortValueDoc []*ValueDoc
336
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] }
339
340 func declName(d *ast.GenDecl) string {
341 if len(d.Specs) != 1 {
342 return ""
343 }
344
345 switch v := d.Specs[0].(type) {
346 case *ast.ValueSpec:
347 return v.Names[0].Name
348 case *ast.TypeSpec:
349 return v.Name.Name
350 }
351
352 return ""
353 }
354
355 func (p sortValueDoc) Less(i, j int) bool {
356
357
358
359 if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj {
360 return ni < nj
361 }
362 return p[i].order < p[j].order
363 }
364
365 func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc {
366 d := make([]*ValueDoc, len(list))
367 n := 0
368 for i, decl := range list {
369 if decl.Tok == tok {
370 d[n] = &ValueDoc{CommentText(decl.Doc), decl, i}
371 n++
372 decl.Doc = nil
373 }
374 }
375 d = d[0:n]
376 sort.Sort(sortValueDoc(d))
377 return d
378 }
379
380
381
382
383 type FuncDoc struct {
384 Doc string
385 Recv ast.Expr
386 Name string
387 Decl *ast.FuncDecl
388 }
389
390 type sortFuncDoc []*FuncDoc
391
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 }
395
396 func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc {
397 d := make([]*FuncDoc, len(m))
398 i := 0
399 for _, f := range m {
400 doc := new(FuncDoc)
401 doc.Doc = CommentText(f.Doc)
402 f.Doc = nil
403 if f.Recv != nil {
404 doc.Recv = f.Recv.List[0].Type
405 }
406 doc.Name = f.Name.Name
407 doc.Decl = f
408 d[i] = doc
409 i++
410 }
411 sort.Sort(sortFuncDoc(d))
412 return d
413 }
414
415
416
417
418
419 type TypeDoc struct {
420 Doc string
421 Type *ast.TypeSpec
422 Consts []*ValueDoc
423 Vars []*ValueDoc
424 Factories []*FuncDoc
425 Methods []*FuncDoc
426 Decl *ast.GenDecl
427 order int
428 }
429
430 type sortTypeDoc []*TypeDoc
431
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 {
435
436
437
438 if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj {
439 return ni < nj
440 }
441 return p[i].order < p[j].order
442 }
443
444
445
446
447 func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
448 d := make([]*TypeDoc, len(m))
449 i := 0
450 for _, old := range m {
451
452
453
454 if decl := old.decl; decl != nil {
455 typespec := decl.Specs[0].(*ast.TypeSpec)
456 t := new(TypeDoc)
457 doc := typespec.Doc
458 typespec.Doc = nil
459 if doc == nil {
460
461 doc = decl.Doc
462 }
463 decl.Doc = nil
464 t.Doc = CommentText(doc)
465 t.Type = typespec
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)
470 t.Decl = old.decl
471 t.order = i
472 d[i] = t
473 i++
474 } else {
475
476
477
478
479
480
481 doc.values = append(doc.values, old.values...)
482
483 for name, f := range old.factories {
484 doc.funcs[name] = f
485 }
486
487 for name, f := range old.methods {
488
489 if _, found := doc.funcs[name]; !found {
490 doc.funcs[name] = f
491 }
492 }
493 }
494 }
495 d = d[0:i]
496 sort.Sort(sortTypeDoc(d))
497 return d
498 }
499
500 func makeBugDocs(list []*ast.CommentGroup) []string {
501 d := make([]string, len(list))
502 for i, g := range list {
503 d[i] = CommentText(g)
504 }
505 return d
506 }
507
508
509
510 type PackageDoc struct {
511 PackageName string
512 ImportPath string
513 Filenames []string
514 Doc string
515 Consts []*ValueDoc
516 Types []*TypeDoc
517 Vars []*ValueDoc
518 Funcs []*FuncDoc
519 Bugs []string
520 }
521
522
523
524 func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc {
525 p := new(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
532
533
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)
539 return p
540 }
541
542
543
544
545 type Filter func(string) bool
546
547 func matchFields(fields *ast.FieldList, f Filter) bool {
548 if fields != nil {
549 for _, field := range fields.List {
550 for _, name := range field.Names {
551 if f(name.Name) {
552 return true
553 }
554 }
555 }
556 }
557 return false
558 }
559
560 func matchDecl(d *ast.GenDecl, f Filter) bool {
561 for _, d := range d.Specs {
562 switch v := d.(type) {
563 case *ast.ValueSpec:
564 for _, name := range v.Names {
565 if f(name.Name) {
566 return true
567 }
568 }
569 case *ast.TypeSpec:
570 if f(v.Name.Name) {
571 return true
572 }
573 switch t := v.Type.(type) {
574 case *ast.StructType:
575 if matchFields(t.Fields, f) {
576 return true
577 }
578 case *ast.InterfaceType:
579 if matchFields(t.Methods, f) {
580 return true
581 }
582 }
583 }
584 }
585 return false
586 }
587
588 func filterValueDocs(a []*ValueDoc, f Filter) []*ValueDoc {
589 w := 0
590 for _, vd := range a {
591 if matchDecl(vd.Decl, f) {
592 a[w] = vd
593 w++
594 }
595 }
596 return a[0:w]
597 }
598
599 func filterFuncDocs(a []*FuncDoc, f Filter) []*FuncDoc {
600 w := 0
601 for _, fd := range a {
602 if f(fd.Name) {
603 a[w] = fd
604 w++
605 }
606 }
607 return a[0:w]
608 }
609
610 func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc {
611 w := 0
612 for _, td := range a {
613 n := 0
614 if matchDecl(td.Decl, f) {
615 n = 1
616 } else {
617
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)
623 }
624 if n > 0 {
625 a[w] = td
626 w++
627 }
628 }
629 return a[0:w]
630 }
631
632
633
634
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 = ""
641 }