-
Notifications
You must be signed in to change notification settings - Fork 94
/
Copy pathreflect_comments.go
146 lines (132 loc) · 3.67 KB
/
reflect_comments.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package jsonschema
import (
"fmt"
"io/fs"
gopath "path"
"path/filepath"
"reflect"
"strings"
"go/ast"
"go/doc"
"go/parser"
"go/token"
)
type commentOptions struct {
fullObjectText bool // use the first sentence only?
}
// CommentOption allows for special configuration options when preparing Go
// source files for comment extraction.
type CommentOption func(*commentOptions)
// WithFullComment will configure the comment extraction to process to use an
// object type's full comment text instead of just the synopsis.
func WithFullComment() CommentOption {
return func(o *commentOptions) {
o.fullObjectText = true
}
}
// AddGoComments will update the reflectors comment map with all the comments
// found in the provided source directories including sub-directories, in order to
// generate a dictionary of comments associated with Types and Fields. The results
// will be added to the `Reflect.CommentMap` ready to use with Schema "description"
// fields.
//
// The `go/parser` library is used to extract all the comments and unfortunately doesn't
// have a built-in way to determine the fully qualified name of a package. The `base`
// parameter, the URL used to import that package, is thus required to be able to match
// reflected types.
//
// When parsing type comments, by default we use the `go/doc`'s Synopsis method to extract
// the first phrase only. Field comments, which tend to be much shorter, will include everything.
// This behavior can be changed by using the `WithFullComment` option.
func (r *Reflector) AddGoComments(base, path string, opts ...CommentOption) error {
if r.CommentMap == nil {
r.CommentMap = make(map[string]string)
}
co := new(commentOptions)
for _, opt := range opts {
opt(co)
}
return r.extractGoComments(base, path, r.CommentMap, co)
}
func (r *Reflector) extractGoComments(base, path string, commentMap map[string]string, opts *commentOptions) error {
fset := token.NewFileSet()
dict := make(map[string][]*ast.Package)
err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
d, err := parser.ParseDir(fset, path, nil, parser.ParseComments)
if err != nil {
return err
}
for _, v := range d {
// paths may have multiple packages, like for tests
k := gopath.Join(base, path)
dict[k] = append(dict[k], v)
}
}
return nil
})
if err != nil {
return err
}
for pkg, p := range dict {
for _, f := range p {
gtxt := ""
typ := ""
ast.Inspect(f, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.TypeSpec:
typ = x.Name.String()
if !ast.IsExported(typ) {
typ = ""
} else {
txt := x.Doc.Text()
if txt == "" && gtxt != "" {
txt = gtxt
gtxt = ""
}
if !opts.fullObjectText {
txt = doc.Synopsis(txt)
}
commentMap[fmt.Sprintf("%s.%s", pkg, typ)] = strings.TrimSpace(txt)
}
case *ast.Field:
txt := x.Doc.Text()
if txt == "" {
txt = x.Comment.Text()
}
if typ != "" && txt != "" {
for _, n := range x.Names {
if ast.IsExported(n.String()) {
k := fmt.Sprintf("%s.%s.%s", pkg, typ, n)
commentMap[k] = strings.TrimSpace(txt)
}
}
}
case *ast.GenDecl:
// remember for the next type
gtxt = x.Doc.Text()
}
return true
})
}
}
return nil
}
func (r *Reflector) lookupComment(t reflect.Type, name string) string {
if r.LookupComment != nil {
if comment := r.LookupComment(t, name); comment != "" {
return comment
}
}
if r.CommentMap == nil {
return ""
}
n := fullyQualifiedTypeName(t)
if name != "" {
n = n + "." + name
}
return r.CommentMap[n]
}