Source file
src/go/ast/commentmap.go
1
2
3
4
5 package ast
6
7 import (
8 "bytes"
9 "cmp"
10 "fmt"
11 "go/token"
12 "slices"
13 "strings"
14 )
15
16
17 func sortComments(list []*CommentGroup) {
18 slices.SortFunc(list, func(a, b *CommentGroup) int {
19 return cmp.Compare(a.Pos(), b.Pos())
20 })
21 }
22
23
24
25
26 type CommentMap map[Node][]*CommentGroup
27
28 func (cmap CommentMap) addComment(n Node, c *CommentGroup) {
29 list := cmap[n]
30 if len(list) == 0 {
31 list = []*CommentGroup{c}
32 } else {
33 list = append(list, c)
34 }
35 cmap[n] = list
36 }
37
38
39 func nodeList(n Node) []Node {
40 var list []Node
41 Inspect(n, func(n Node) bool {
42
43 switch n.(type) {
44 case nil, *CommentGroup, *Comment:
45 return false
46 }
47 list = append(list, n)
48 return true
49 })
50
51
52
53
54
55
56
57
58
59
60
61
62
63 return list
64 }
65
66
67 type commentListReader struct {
68 fset *token.FileSet
69 list []*CommentGroup
70 index int
71 comment *CommentGroup
72 pos, end token.Position
73 }
74
75 func (r *commentListReader) eol() bool {
76 return r.index >= len(r.list)
77 }
78
79 func (r *commentListReader) next() {
80 if !r.eol() {
81 r.comment = r.list[r.index]
82 r.pos = r.fset.Position(r.comment.Pos())
83 r.end = r.fset.Position(r.comment.End())
84 r.index++
85 }
86 }
87
88
89
90 type nodeStack []Node
91
92
93
94 func (s *nodeStack) push(n Node) {
95 s.pop(n.Pos())
96 *s = append((*s), n)
97 }
98
99
100
101
102 func (s *nodeStack) pop(pos token.Pos) (top Node) {
103 i := len(*s)
104 for i > 0 && (*s)[i-1].End() <= pos {
105 top = (*s)[i-1]
106 i--
107 }
108 *s = (*s)[0:i]
109 return top
110 }
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 func NewCommentMap(fset *token.FileSet, node Node, comments []*CommentGroup) CommentMap {
128 if len(comments) == 0 {
129 return nil
130 }
131
132 cmap := make(CommentMap)
133
134
135 tmp := make([]*CommentGroup, len(comments))
136 copy(tmp, comments)
137 sortComments(tmp)
138 r := commentListReader{fset: fset, list: tmp}
139 r.next()
140
141
142 nodes := nodeList(node)
143 nodes = append(nodes, nil)
144
145
146 var (
147 p Node
148 pend token.Position
149 pg Node
150 pgend token.Position
151 stack nodeStack
152 )
153
154 for _, q := range nodes {
155 var qpos token.Position
156 if q != nil {
157 qpos = fset.Position(q.Pos())
158 } else {
159
160
161 const infinity = 1 << 30
162 qpos.Offset = infinity
163 qpos.Line = infinity
164 }
165
166
167 for r.end.Offset <= qpos.Offset {
168
169 if top := stack.pop(r.comment.Pos()); top != nil {
170 pg = top
171 pgend = fset.Position(pg.End())
172 }
173
174
175
176
177
178 var assoc Node
179 switch {
180 case pg != nil &&
181 (pgend.Line == r.pos.Line ||
182 pgend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line):
183
184
185
186
187
188 assoc = pg
189 case p != nil &&
190 (pend.Line == r.pos.Line ||
191 pend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line ||
192 q == nil):
193
194
195 assoc = p
196 default:
197
198 if q == nil {
199
200
201 panic("internal error: no comments should be associated with sentinel")
202 }
203 assoc = q
204 }
205 cmap.addComment(assoc, r.comment)
206 if r.eol() {
207 return cmap
208 }
209 r.next()
210 }
211
212
213 p = q
214 pend = fset.Position(p.End())
215
216
217 switch q.(type) {
218 case *File, *Field, Decl, Spec, Stmt:
219 stack.push(q)
220 }
221 }
222
223 return cmap
224 }
225
226
227
228
229 func (cmap CommentMap) Update(old, new Node) Node {
230 if list := cmap[old]; len(list) > 0 {
231 delete(cmap, old)
232 cmap[new] = append(cmap[new], list...)
233 }
234 return new
235 }
236
237
238
239
240 func (cmap CommentMap) Filter(node Node) CommentMap {
241 umap := make(CommentMap)
242 Inspect(node, func(n Node) bool {
243 if g := cmap[n]; len(g) > 0 {
244 umap[n] = g
245 }
246 return true
247 })
248 return umap
249 }
250
251
252
253 func (cmap CommentMap) Comments() []*CommentGroup {
254 list := make([]*CommentGroup, 0, len(cmap))
255 for _, e := range cmap {
256 list = append(list, e...)
257 }
258 sortComments(list)
259 return list
260 }
261
262 func summary(list []*CommentGroup) string {
263 const maxLen = 40
264 var buf bytes.Buffer
265
266
267 loop:
268 for _, group := range list {
269
270
271
272 for _, comment := range group.List {
273 if buf.Len() >= maxLen {
274 break loop
275 }
276 buf.WriteString(comment.Text)
277 }
278 }
279
280
281 if buf.Len() > maxLen {
282 buf.Truncate(maxLen - 3)
283 buf.WriteString("...")
284 }
285
286
287 bytes := buf.Bytes()
288 for i, b := range bytes {
289 switch b {
290 case '\t', '\n', '\r':
291 bytes[i] = ' '
292 }
293 }
294
295 return string(bytes)
296 }
297
298 func (cmap CommentMap) String() string {
299
300 var nodes []Node
301 for node := range cmap {
302 nodes = append(nodes, node)
303 }
304 slices.SortFunc(nodes, func(a, b Node) int {
305 r := cmp.Compare(a.Pos(), b.Pos())
306 if r != 0 {
307 return r
308 }
309 return cmp.Compare(a.End(), b.End())
310 })
311
312 var buf strings.Builder
313 fmt.Fprintln(&buf, "CommentMap {")
314 for _, node := range nodes {
315 comment := cmap[node]
316
317 var s string
318 if ident, ok := node.(*Ident); ok {
319 s = ident.Name
320 } else {
321 s = fmt.Sprintf("%T", node)
322 }
323 fmt.Fprintf(&buf, "\t%p %20s: %s\n", node, s, summary(comment))
324 }
325 fmt.Fprintln(&buf, "}")
326 return buf.String()
327 }
328
View as plain text