Source file
src/go/doc/reader.go
1
2
3
4
5 package doc
6
7 import (
8 "cmp"
9 "fmt"
10 "go/ast"
11 "go/token"
12 "internal/lazyregexp"
13 "path"
14 "slices"
15 "strconv"
16 "strings"
17 "unicode"
18 "unicode/utf8"
19 )
20
21
22
23
24
25
26
27
28 type methodSet map[string]*Func
29
30
31
32 func recvString(recv ast.Expr) string {
33 switch t := recv.(type) {
34 case *ast.Ident:
35 return t.Name
36 case *ast.StarExpr:
37 return "*" + recvString(t.X)
38 case *ast.IndexExpr:
39
40 return fmt.Sprintf("%s[%s]", recvString(t.X), recvParam(t.Index))
41 case *ast.IndexListExpr:
42
43 if len(t.Indices) > 0 {
44 var b strings.Builder
45 b.WriteString(recvString(t.X))
46 b.WriteByte('[')
47 b.WriteString(recvParam(t.Indices[0]))
48 for _, e := range t.Indices[1:] {
49 b.WriteString(", ")
50 b.WriteString(recvParam(e))
51 }
52 b.WriteByte(']')
53 return b.String()
54 }
55 }
56 return "BADRECV"
57 }
58
59 func recvParam(p ast.Expr) string {
60 if id, ok := p.(*ast.Ident); ok {
61 return id.Name
62 }
63 return "BADPARAM"
64 }
65
66
67
68
69
70 func (mset methodSet) set(f *ast.FuncDecl, preserveAST bool) {
71 name := f.Name.Name
72 if g := mset[name]; g != nil && g.Doc != "" {
73
74
75
76
77
78 return
79 }
80
81 recv := ""
82 if f.Recv != nil {
83 var typ ast.Expr
84
85 if list := f.Recv.List; len(list) == 1 {
86 typ = list[0].Type
87 }
88 recv = recvString(typ)
89 }
90 mset[name] = &Func{
91 Doc: f.Doc.Text(),
92 Name: name,
93 Decl: f,
94 Recv: recv,
95 Orig: recv,
96 }
97 if !preserveAST {
98 f.Doc = nil
99 }
100 }
101
102
103
104
105 func (mset methodSet) add(m *Func) {
106 old := mset[m.Name]
107 if old == nil || m.Level < old.Level {
108 mset[m.Name] = m
109 return
110 }
111 if m.Level == old.Level {
112
113 mset[m.Name] = &Func{
114 Name: m.Name,
115 Level: m.Level,
116 }
117 }
118 }
119
120
121
122
123
124
125 func baseTypeName(x ast.Expr) (name string, imported bool) {
126 switch t := x.(type) {
127 case *ast.Ident:
128 return t.Name, false
129 case *ast.IndexExpr:
130 return baseTypeName(t.X)
131 case *ast.IndexListExpr:
132 return baseTypeName(t.X)
133 case *ast.SelectorExpr:
134 if _, ok := t.X.(*ast.Ident); ok {
135
136
137 return t.Sel.Name, true
138 }
139 case *ast.ParenExpr:
140 return baseTypeName(t.X)
141 case *ast.StarExpr:
142 return baseTypeName(t.X)
143 }
144 return "", false
145 }
146
147
148 type embeddedSet map[*namedType]bool
149
150
151
152
153 type namedType struct {
154 doc string
155 name string
156 decl *ast.GenDecl
157
158 isEmbedded bool
159 isStruct bool
160 embedded embeddedSet
161
162
163 values []*Value
164 funcs methodSet
165 methods methodSet
166 }
167
168
169
170
171
172
173
174
175
176
177 type reader struct {
178 mode Mode
179
180
181 doc string
182 filenames []string
183 notes map[string][]*Note
184
185
186 imports map[string]int
187 hasDotImp bool
188 importByName map[string]string
189
190
191 values []*Value
192 order int
193 types map[string]*namedType
194 funcs methodSet
195
196
197 shadowedPredecl map[string]bool
198 fixmap map[string][]*ast.InterfaceType
199 }
200
201 func (r *reader) isVisible(name string) bool {
202 return r.mode&AllDecls != 0 || token.IsExported(name)
203 }
204
205
206
207
208
209 func (r *reader) lookupType(name string) *namedType {
210 if name == "" || name == "_" {
211 return nil
212 }
213 if typ, found := r.types[name]; found {
214 return typ
215 }
216
217 typ := &namedType{
218 name: name,
219 embedded: make(embeddedSet),
220 funcs: make(methodSet),
221 methods: make(methodSet),
222 }
223 r.types[name] = typ
224 return typ
225 }
226
227
228
229
230
231 func (r *reader) recordAnonymousField(parent *namedType, fieldType ast.Expr) (fname string) {
232 fname, imp := baseTypeName(fieldType)
233 if parent == nil || imp {
234 return
235 }
236 if ftype := r.lookupType(fname); ftype != nil {
237 ftype.isEmbedded = true
238 _, ptr := fieldType.(*ast.StarExpr)
239 parent.embedded[ftype] = ptr
240 }
241 return
242 }
243
244 func (r *reader) readDoc(comment *ast.CommentGroup) {
245
246
247 text := comment.Text()
248 if r.doc == "" {
249 r.doc = text
250 return
251 }
252 r.doc += "\n" + text
253 }
254
255 func (r *reader) remember(predecl string, typ *ast.InterfaceType) {
256 if r.fixmap == nil {
257 r.fixmap = make(map[string][]*ast.InterfaceType)
258 }
259 r.fixmap[predecl] = append(r.fixmap[predecl], typ)
260 }
261
262 func specNames(specs []ast.Spec) []string {
263 names := make([]string, 0, len(specs))
264 for _, s := range specs {
265
266 for _, ident := range s.(*ast.ValueSpec).Names {
267 names = append(names, ident.Name)
268 }
269 }
270 return names
271 }
272
273
274 func (r *reader) readValue(decl *ast.GenDecl) {
275
276
277
278
279 domName := ""
280 domFreq := 0
281 prev := ""
282 n := 0
283 for _, spec := range decl.Specs {
284 s, ok := spec.(*ast.ValueSpec)
285 if !ok {
286 continue
287 }
288 name := ""
289 switch {
290 case s.Type != nil:
291
292 if n, imp := baseTypeName(s.Type); !imp {
293 name = n
294 }
295 case decl.Tok == token.CONST && len(s.Values) == 0:
296
297
298 name = prev
299 }
300 if name != "" {
301
302 if domName != "" && domName != name {
303
304
305 domName = ""
306 break
307 }
308 domName = name
309 domFreq++
310 }
311 prev = name
312 n++
313 }
314
315
316 if n == 0 {
317 return
318 }
319
320
321 values := &r.values
322 const threshold = 0.75
323 if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) {
324
325 if typ := r.lookupType(domName); typ != nil {
326 values = &typ.values
327 }
328 }
329
330 *values = append(*values, &Value{
331 Doc: decl.Doc.Text(),
332 Names: specNames(decl.Specs),
333 Decl: decl,
334 order: r.order,
335 })
336 if r.mode&PreserveAST == 0 {
337 decl.Doc = nil
338 }
339
340
341
342
343 r.order++
344 }
345
346
347 func fields(typ ast.Expr) (list []*ast.Field, isStruct bool) {
348 var fields *ast.FieldList
349 switch t := typ.(type) {
350 case *ast.StructType:
351 fields = t.Fields
352 isStruct = true
353 case *ast.InterfaceType:
354 fields = t.Methods
355 }
356 if fields != nil {
357 list = fields.List
358 }
359 return
360 }
361
362
363 func (r *reader) readType(decl *ast.GenDecl, spec *ast.TypeSpec) {
364 typ := r.lookupType(spec.Name.Name)
365 if typ == nil {
366 return
367 }
368
369
370
371 typ.decl = decl
372
373
374 doc := spec.Doc
375 if doc == nil {
376
377 doc = decl.Doc
378 }
379 if r.mode&PreserveAST == 0 {
380 spec.Doc = nil
381 decl.Doc = nil
382 }
383 typ.doc = doc.Text()
384
385
386
387
388 var list []*ast.Field
389 list, typ.isStruct = fields(spec.Type)
390 for _, field := range list {
391 if len(field.Names) == 0 {
392 r.recordAnonymousField(typ, field.Type)
393 }
394 }
395 }
396
397
398 func (r *reader) isPredeclared(n string) bool {
399 return predeclaredTypes[n] && r.types[n] == nil
400 }
401
402
403 func (r *reader) readFunc(fun *ast.FuncDecl) {
404
405 if r.mode&PreserveAST == 0 {
406 fun.Body = nil
407 }
408
409
410 if fun.Recv != nil {
411
412 if len(fun.Recv.List) == 0 {
413
414
415 return
416 }
417 recvTypeName, imp := baseTypeName(fun.Recv.List[0].Type)
418 if imp {
419
420
421 return
422 }
423 if typ := r.lookupType(recvTypeName); typ != nil {
424 typ.methods.set(fun, r.mode&PreserveAST != 0)
425 }
426
427
428
429
430
431 return
432 }
433
434
435
436 if fun.Type.Results.NumFields() >= 1 {
437 var typ *namedType
438 numResultTypes := 0
439 for _, res := range fun.Type.Results.List {
440 factoryType := res.Type
441 if t, ok := factoryType.(*ast.ArrayType); ok {
442
443
444 factoryType = t.Elt
445 }
446 if n, imp := baseTypeName(factoryType); !imp && r.isVisible(n) && !r.isPredeclared(n) {
447 if lookupTypeParam(n, fun.Type.TypeParams) != nil {
448
449
450 continue
451 }
452 if t := r.lookupType(n); t != nil {
453 typ = t
454 numResultTypes++
455 if numResultTypes > 1 {
456 break
457 }
458 }
459 }
460 }
461
462
463 if numResultTypes == 1 {
464 typ.funcs.set(fun, r.mode&PreserveAST != 0)
465 return
466 }
467 }
468
469
470 r.funcs.set(fun, r.mode&PreserveAST != 0)
471 }
472
473
474
475 func lookupTypeParam(name string, tparams *ast.FieldList) *ast.Ident {
476 if tparams == nil {
477 return nil
478 }
479 for _, field := range tparams.List {
480 for _, id := range field.Names {
481 if id.Name == name {
482 return id
483 }
484 }
485 }
486 return nil
487 }
488
489 var (
490 noteMarker = `([A-Z][A-Z]+)\(([^)]+)\):?`
491 noteMarkerRx = lazyregexp.New(`^[ \t]*` + noteMarker)
492 noteCommentRx = lazyregexp.New(`^/[/*][ \t]*` + noteMarker)
493 )
494
495
496
497 func clean(s string) string {
498 var b []byte
499 p := byte(' ')
500 for i := 0; i < len(s); i++ {
501 q := s[i]
502 if q == '\r' || q == '\t' {
503 q = ' '
504 }
505 if q != ' ' || p != ' ' {
506 b = append(b, q)
507 p = q
508 }
509 }
510
511 if n := len(b); n > 0 && p == ' ' {
512 b = b[0 : n-1]
513 }
514 return string(b)
515 }
516
517
518 func (r *reader) readNote(list []*ast.Comment) {
519 text := (&ast.CommentGroup{List: list}).Text()
520 if m := noteMarkerRx.FindStringSubmatchIndex(text); m != nil {
521
522
523
524
525 body := clean(text[m[1]:])
526 if body != "" {
527 marker := text[m[2]:m[3]]
528 r.notes[marker] = append(r.notes[marker], &Note{
529 Pos: list[0].Pos(),
530 End: list[len(list)-1].End(),
531 UID: text[m[4]:m[5]],
532 Body: body,
533 })
534 }
535 }
536 }
537
538
539
540
541
542
543 func (r *reader) readNotes(comments []*ast.CommentGroup) {
544 for _, group := range comments {
545 i := -1
546 list := group.List
547 for j, c := range list {
548 if noteCommentRx.MatchString(c.Text) {
549 if i >= 0 {
550 r.readNote(list[i:j])
551 }
552 i = j
553 }
554 }
555 if i >= 0 {
556 r.readNote(list[i:])
557 }
558 }
559 }
560
561
562 func (r *reader) readFile(src *ast.File) {
563
564 if src.Doc != nil {
565 r.readDoc(src.Doc)
566 if r.mode&PreserveAST == 0 {
567 src.Doc = nil
568 }
569 }
570
571
572 for _, decl := range src.Decls {
573 switch d := decl.(type) {
574 case *ast.GenDecl:
575 switch d.Tok {
576 case token.IMPORT:
577
578 for _, spec := range d.Specs {
579 if s, ok := spec.(*ast.ImportSpec); ok {
580 if import_, err := strconv.Unquote(s.Path.Value); err == nil {
581 r.imports[import_] = 1
582 var name string
583 if s.Name != nil {
584 name = s.Name.Name
585 if name == "." {
586 r.hasDotImp = true
587 }
588 }
589 if name != "." {
590 if name == "" {
591 name = assumedPackageName(import_)
592 }
593 old, ok := r.importByName[name]
594 if !ok {
595 r.importByName[name] = import_
596 } else if old != import_ && old != "" {
597 r.importByName[name] = ""
598 }
599 }
600 }
601 }
602 }
603 case token.CONST, token.VAR:
604
605 r.readValue(d)
606 case token.TYPE:
607
608 if len(d.Specs) == 1 && !d.Lparen.IsValid() {
609
610
611
612
613
614 if s, ok := d.Specs[0].(*ast.TypeSpec); ok {
615 r.readType(d, s)
616 }
617 break
618 }
619 for _, spec := range d.Specs {
620 if s, ok := spec.(*ast.TypeSpec); ok {
621
622
623
624
625 fake := &ast.GenDecl{
626 Doc: d.Doc,
627
628
629
630
631
632 TokPos: s.Pos(),
633 Tok: token.TYPE,
634 Specs: []ast.Spec{s},
635 }
636 r.readType(fake, s)
637 }
638 }
639 }
640 }
641 }
642
643
644 r.readNotes(src.Comments)
645 if r.mode&PreserveAST == 0 {
646 src.Comments = nil
647 }
648 }
649
650 func (r *reader) readPackage(pkg *ast.Package, mode Mode) {
651
652 r.filenames = make([]string, len(pkg.Files))
653 r.imports = make(map[string]int)
654 r.mode = mode
655 r.types = make(map[string]*namedType)
656 r.funcs = make(methodSet)
657 r.notes = make(map[string][]*Note)
658 r.importByName = make(map[string]string)
659
660
661
662 i := 0
663 for filename := range pkg.Files {
664 r.filenames[i] = filename
665 i++
666 }
667 slices.Sort(r.filenames)
668
669
670 for _, filename := range r.filenames {
671 f := pkg.Files[filename]
672 if mode&AllDecls == 0 {
673 r.fileExports(f)
674 }
675 r.readFile(f)
676 }
677
678 for name, path := range r.importByName {
679 if path == "" {
680 delete(r.importByName, name)
681 }
682 }
683
684
685 for _, f := range pkg.Files {
686 for _, decl := range f.Decls {
687 if d, ok := decl.(*ast.FuncDecl); ok {
688 r.readFunc(d)
689 }
690 }
691 }
692 }
693
694
695
696
697 func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) *Func {
698 if f == nil || f.Decl == nil || f.Decl.Recv == nil || len(f.Decl.Recv.List) != 1 {
699 return f
700 }
701
702
703 newField := *f.Decl.Recv.List[0]
704 origPos := newField.Type.Pos()
705 _, origRecvIsPtr := newField.Type.(*ast.StarExpr)
706 newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName}
707 var typ ast.Expr = newIdent
708 if !embeddedIsPtr && origRecvIsPtr {
709 newIdent.NamePos++
710 typ = &ast.StarExpr{Star: origPos, X: newIdent}
711 }
712 newField.Type = typ
713
714
715 newFieldList := *f.Decl.Recv
716 newFieldList.List = []*ast.Field{&newField}
717
718
719 newFuncDecl := *f.Decl
720 newFuncDecl.Recv = &newFieldList
721
722
723 newF := *f
724 newF.Decl = &newFuncDecl
725 newF.Recv = recvString(typ)
726
727 newF.Level = level
728
729 return &newF
730 }
731
732
733 func (r *reader) collectEmbeddedMethods(mset methodSet, typ *namedType, recvTypeName string, embeddedIsPtr bool, level int, visited embeddedSet) {
734 visited[typ] = true
735 for embedded, isPtr := range typ.embedded {
736
737
738
739
740
741 thisEmbeddedIsPtr := embeddedIsPtr || isPtr
742 for _, m := range embedded.methods {
743
744 if m.Level == 0 {
745 mset.add(customizeRecv(m, recvTypeName, thisEmbeddedIsPtr, level))
746 }
747 }
748 if !visited[embedded] {
749 r.collectEmbeddedMethods(mset, embedded, recvTypeName, thisEmbeddedIsPtr, level+1, visited)
750 }
751 }
752 delete(visited, typ)
753 }
754
755
756 func (r *reader) computeMethodSets() {
757 for _, t := range r.types {
758
759 if t.isStruct {
760
761 r.collectEmbeddedMethods(t.methods, t, t.name, false, 1, make(embeddedSet))
762 } else {
763
764
765 }
766 }
767
768
769
770 for predecl := range r.shadowedPredecl {
771 for _, ityp := range r.fixmap[predecl] {
772 removeAnonymousField(predecl, ityp)
773 }
774 }
775 }
776
777
778
779
780
781 func (r *reader) cleanupTypes() {
782 for _, t := range r.types {
783 visible := r.isVisible(t.name)
784 predeclared := predeclaredTypes[t.name]
785
786 if t.decl == nil && (predeclared || visible && (t.isEmbedded || r.hasDotImp)) {
787
788
789
790
791
792
793 r.values = append(r.values, t.values...)
794
795 for name, f := range t.funcs {
796
797
798 r.funcs[name] = f
799 }
800
801 if !predeclared {
802 for name, m := range t.methods {
803
804 if _, found := r.funcs[name]; !found {
805 r.funcs[name] = m
806 }
807 }
808 }
809 }
810
811 if t.decl == nil || !visible {
812 delete(r.types, t.name)
813 }
814 }
815 }
816
817
818
819
820 func sortedKeys(m map[string]int) []string {
821 list := make([]string, len(m))
822 i := 0
823 for key := range m {
824 list[i] = key
825 i++
826 }
827 slices.Sort(list)
828 return list
829 }
830
831
832 func sortingName(d *ast.GenDecl) string {
833 if len(d.Specs) == 1 {
834 if s, ok := d.Specs[0].(*ast.ValueSpec); ok {
835 return s.Names[0].Name
836 }
837 }
838 return ""
839 }
840
841 func sortedValues(m []*Value, tok token.Token) []*Value {
842 list := make([]*Value, len(m))
843 i := 0
844 for _, val := range m {
845 if val.Decl.Tok == tok {
846 list[i] = val
847 i++
848 }
849 }
850 list = list[0:i]
851
852 slices.SortFunc(list, func(a, b *Value) int {
853 r := strings.Compare(sortingName(a.Decl), sortingName(b.Decl))
854 if r != 0 {
855 return r
856 }
857 return cmp.Compare(a.order, b.order)
858 })
859
860 return list
861 }
862
863 func sortedTypes(m map[string]*namedType, allMethods bool) []*Type {
864 list := make([]*Type, len(m))
865 i := 0
866 for _, t := range m {
867 list[i] = &Type{
868 Doc: t.doc,
869 Name: t.name,
870 Decl: t.decl,
871 Consts: sortedValues(t.values, token.CONST),
872 Vars: sortedValues(t.values, token.VAR),
873 Funcs: sortedFuncs(t.funcs, true),
874 Methods: sortedFuncs(t.methods, allMethods),
875 }
876 i++
877 }
878
879 slices.SortFunc(list, func(a, b *Type) int {
880 return strings.Compare(a.Name, b.Name)
881 })
882
883 return list
884 }
885
886 func removeStar(s string) string {
887 if len(s) > 0 && s[0] == '*' {
888 return s[1:]
889 }
890 return s
891 }
892
893 func sortedFuncs(m methodSet, allMethods bool) []*Func {
894 list := make([]*Func, len(m))
895 i := 0
896 for _, m := range m {
897
898 switch {
899 case m.Decl == nil:
900
901 case allMethods, m.Level == 0, !token.IsExported(removeStar(m.Orig)):
902
903
904 list[i] = m
905 i++
906 }
907 }
908 list = list[0:i]
909 slices.SortFunc(list, func(a, b *Func) int {
910 return strings.Compare(a.Name, b.Name)
911 })
912 return list
913 }
914
915
916
917 func noteBodies(notes []*Note) []string {
918 var list []string
919 for _, n := range notes {
920 list = append(list, n.Body)
921 }
922 return list
923 }
924
925
926
927
928
929 func IsPredeclared(s string) bool {
930 return predeclaredTypes[s] || predeclaredFuncs[s] || predeclaredConstants[s]
931 }
932
933 var predeclaredTypes = map[string]bool{
934 "any": true,
935 "bool": true,
936 "byte": true,
937 "comparable": true,
938 "complex64": true,
939 "complex128": true,
940 "error": true,
941 "float32": true,
942 "float64": true,
943 "int": true,
944 "int8": true,
945 "int16": true,
946 "int32": true,
947 "int64": true,
948 "rune": true,
949 "string": true,
950 "uint": true,
951 "uint8": true,
952 "uint16": true,
953 "uint32": true,
954 "uint64": true,
955 "uintptr": true,
956 }
957
958 var predeclaredFuncs = map[string]bool{
959 "append": true,
960 "cap": true,
961 "clear": true,
962 "close": true,
963 "complex": true,
964 "copy": true,
965 "delete": true,
966 "imag": true,
967 "len": true,
968 "make": true,
969 "max": true,
970 "min": true,
971 "new": true,
972 "panic": true,
973 "print": true,
974 "println": true,
975 "real": true,
976 "recover": true,
977 }
978
979 var predeclaredConstants = map[string]bool{
980 "false": true,
981 "iota": true,
982 "nil": true,
983 "true": true,
984 }
985
986
987
988
989 func assumedPackageName(importPath string) string {
990 notIdentifier := func(ch rune) bool {
991 return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' ||
992 '0' <= ch && ch <= '9' ||
993 ch == '_' ||
994 ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch)))
995 }
996
997 base := path.Base(importPath)
998 if strings.HasPrefix(base, "v") {
999 if _, err := strconv.Atoi(base[1:]); err == nil {
1000 dir := path.Dir(importPath)
1001 if dir != "." {
1002 base = path.Base(dir)
1003 }
1004 }
1005 }
1006 base = strings.TrimPrefix(base, "go-")
1007 if i := strings.IndexFunc(base, notIdentifier); i >= 0 {
1008 base = base[:i]
1009 }
1010 return base
1011 }
1012
View as plain text