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