astdump.go raw
1 package main
2
3 import (
4 "fmt"
5 "go/ast"
6 "go/token"
7 "strings"
8 )
9
10 // dumpAST produces a compact text representation of a declaration's AST.
11 // Format: indented tree with node types and key identifiers.
12 func dumpAST(fset *token.FileSet, decl ast.Decl) string {
13 var b strings.Builder
14 dumpNode(&b, fset, decl, 0)
15 return b.String()
16 }
17
18 func dumpNode(b *strings.Builder, fset *token.FileSet, n ast.Node, depth int) {
19 if n == nil {
20 return
21 }
22 indent := strings.Repeat(" ", depth)
23
24 switch x := n.(type) {
25 case *ast.FuncDecl:
26 recv := ""
27 if x.Recv != nil && len(x.Recv.List) > 0 {
28 recv = " recv=" + exprStr(x.Recv.List[0].Type)
29 }
30 fmt.Fprintf(b, "%sFuncDecl %s%s\n", indent, x.Name.Name, recv)
31 if x.Type != nil {
32 dumpFuncType(b, fset, x.Type, depth+1)
33 }
34 if x.Body != nil {
35 dumpBlock(b, fset, x.Body, depth+1)
36 }
37
38 case *ast.GenDecl:
39 fmt.Fprintf(b, "%sGenDecl %s\n", indent, x.Tok)
40 for _, spec := range x.Specs {
41 dumpSpec(b, fset, spec, depth+1)
42 }
43
44 default:
45 fmt.Fprintf(b, "%s%T\n", indent, n)
46 }
47 }
48
49 func dumpFuncType(b *strings.Builder, fset *token.FileSet, ft *ast.FuncType, depth int) {
50 indent := strings.Repeat(" ", depth)
51 if ft.TypeParams != nil && len(ft.TypeParams.List) > 0 {
52 fmt.Fprintf(b, "%sTypeParams\n", indent)
53 for _, f := range ft.TypeParams.List {
54 dumpField(b, f, depth+1)
55 }
56 }
57 if ft.Params != nil && len(ft.Params.List) > 0 {
58 fmt.Fprintf(b, "%sParams\n", indent)
59 for _, f := range ft.Params.List {
60 dumpField(b, f, depth+1)
61 }
62 }
63 if ft.Results != nil && len(ft.Results.List) > 0 {
64 fmt.Fprintf(b, "%sResults\n", indent)
65 for _, f := range ft.Results.List {
66 dumpField(b, f, depth+1)
67 }
68 }
69 }
70
71 func dumpField(b *strings.Builder, f *ast.Field, depth int) {
72 indent := strings.Repeat(" ", depth)
73 names := ""
74 for i, n := range f.Names {
75 if i > 0 {
76 names += ","
77 }
78 names += n.Name
79 }
80 if names != "" {
81 fmt.Fprintf(b, "%s%s %s\n", indent, names, exprStr(f.Type))
82 } else {
83 fmt.Fprintf(b, "%s%s\n", indent, exprStr(f.Type))
84 }
85 }
86
87 func dumpSpec(b *strings.Builder, fset *token.FileSet, spec ast.Spec, depth int) {
88 indent := strings.Repeat(" ", depth)
89 switch s := spec.(type) {
90 case *ast.ImportSpec:
91 path := ""
92 if s.Path != nil {
93 path = s.Path.Value
94 }
95 name := ""
96 if s.Name != nil {
97 name = s.Name.Name + " "
98 }
99 fmt.Fprintf(b, "%sImport %s%s\n", indent, name, path)
100 case *ast.TypeSpec:
101 fmt.Fprintf(b, "%sType %s %s\n", indent, s.Name.Name, exprStr(s.Type))
102 if st, ok := s.Type.(*ast.StructType); ok && st.Fields != nil {
103 for _, f := range st.Fields.List {
104 dumpField(b, f, depth+1)
105 }
106 }
107 if it, ok := s.Type.(*ast.InterfaceType); ok && it.Methods != nil {
108 for _, f := range it.Methods.List {
109 dumpField(b, f, depth+1)
110 }
111 }
112 case *ast.ValueSpec:
113 names := ""
114 for i, n := range s.Names {
115 if i > 0 {
116 names += ","
117 }
118 names += n.Name
119 }
120 typ := ""
121 if s.Type != nil {
122 typ = " " + exprStr(s.Type)
123 }
124 fmt.Fprintf(b, "%sValue %s%s\n", indent, names, typ)
125 }
126 }
127
128 func dumpBlock(b *strings.Builder, fset *token.FileSet, block *ast.BlockStmt, depth int) {
129 indent := strings.Repeat(" ", depth)
130 fmt.Fprintf(b, "%sBlock\n", indent)
131 for _, stmt := range block.List {
132 dumpStmt(b, fset, stmt, depth+1)
133 }
134 }
135
136 func dumpStmt(b *strings.Builder, fset *token.FileSet, stmt ast.Stmt, depth int) {
137 indent := strings.Repeat(" ", depth)
138 switch s := stmt.(type) {
139 case *ast.ReturnStmt:
140 refs := collectRefs(s.Results...)
141 if len(refs) > 0 {
142 fmt.Fprintf(b, "%sReturn [%s]\n", indent, strings.Join(refs, ","))
143 } else {
144 fmt.Fprintf(b, "%sReturn\n", indent)
145 }
146 case *ast.AssignStmt:
147 lhs := exprListStr(s.Lhs)
148 allExprs := append(s.Lhs, s.Rhs...)
149 refs := collectRefs(allExprs...)
150 if len(refs) > 0 {
151 fmt.Fprintf(b, "%sAssign %s %s [%s]\n", indent, lhs, s.Tok, strings.Join(refs, ","))
152 } else {
153 fmt.Fprintf(b, "%sAssign %s %s\n", indent, lhs, s.Tok)
154 }
155 case *ast.ExprStmt:
156 refs := collectRefs(s.X)
157 if len(refs) > 0 {
158 fmt.Fprintf(b, "%sExpr %s [%s]\n", indent, exprStr(s.X), strings.Join(refs, ","))
159 } else {
160 fmt.Fprintf(b, "%sExpr %s\n", indent, exprStr(s.X))
161 }
162 case *ast.IfStmt:
163 var allExprs []ast.Expr
164 if s.Init != nil {
165 if as, ok := s.Init.(*ast.AssignStmt); ok {
166 allExprs = append(allExprs, as.Lhs...)
167 allExprs = append(allExprs, as.Rhs...)
168 }
169 }
170 allExprs = append(allExprs, s.Cond)
171 refs := collectRefs(allExprs...)
172 if len(refs) > 0 {
173 fmt.Fprintf(b, "%sIf [%s]\n", indent, strings.Join(refs, ","))
174 } else {
175 fmt.Fprintf(b, "%sIf\n", indent)
176 }
177 if s.Body != nil {
178 dumpBlock(b, fset, s.Body, depth+1)
179 }
180 if s.Else != nil {
181 fmt.Fprintf(b, "%sElse\n", indent)
182 dumpStmt(b, fset, s.Else, depth+1)
183 }
184 case *ast.ForStmt:
185 var allExprs []ast.Expr
186 if s.Init != nil {
187 if as, ok := s.Init.(*ast.AssignStmt); ok {
188 allExprs = append(allExprs, as.Lhs...)
189 allExprs = append(allExprs, as.Rhs...)
190 }
191 }
192 allExprs = append(allExprs, s.Cond)
193 if s.Post != nil {
194 if as, ok := s.Post.(*ast.AssignStmt); ok {
195 allExprs = append(allExprs, as.Lhs...)
196 allExprs = append(allExprs, as.Rhs...)
197 }
198 if inc, ok := s.Post.(*ast.IncDecStmt); ok {
199 allExprs = append(allExprs, inc.X)
200 }
201 }
202 refs := collectRefs(allExprs...)
203 if len(refs) > 0 {
204 fmt.Fprintf(b, "%sFor [%s]\n", indent, strings.Join(refs, ","))
205 } else {
206 fmt.Fprintf(b, "%sFor\n", indent)
207 }
208 if s.Body != nil {
209 dumpBlock(b, fset, s.Body, depth+1)
210 }
211 case *ast.RangeStmt:
212 allExprs := []ast.Expr{s.X}
213 if s.Key != nil {
214 allExprs = append(allExprs, s.Key)
215 }
216 if s.Value != nil {
217 allExprs = append(allExprs, s.Value)
218 }
219 refs := collectRefs(allExprs...)
220 rangeStr := exprStr(s.X)
221 if len(refs) > 0 {
222 fmt.Fprintf(b, "%sRange %s [%s]\n", indent, rangeStr, strings.Join(refs, ","))
223 } else {
224 fmt.Fprintf(b, "%sRange %s\n", indent, rangeStr)
225 }
226 if s.Body != nil {
227 dumpBlock(b, fset, s.Body, depth+1)
228 }
229 case *ast.SwitchStmt:
230 var allExprs []ast.Expr
231 if s.Init != nil {
232 if as, ok := s.Init.(*ast.AssignStmt); ok {
233 allExprs = append(allExprs, as.Lhs...)
234 allExprs = append(allExprs, as.Rhs...)
235 }
236 }
237 allExprs = append(allExprs, s.Tag)
238 refs := collectRefs(allExprs...)
239 if len(refs) > 0 {
240 fmt.Fprintf(b, "%sSwitch [%s]\n", indent, strings.Join(refs, ","))
241 } else {
242 fmt.Fprintf(b, "%sSwitch\n", indent)
243 }
244 if s.Body != nil {
245 dumpBlock(b, fset, s.Body, depth+1)
246 }
247 case *ast.TypeSwitchStmt:
248 fmt.Fprintf(b, "%sTypeSwitch\n", indent)
249 if s.Body != nil {
250 dumpBlock(b, fset, s.Body, depth+1)
251 }
252 case *ast.SelectStmt:
253 fmt.Fprintf(b, "%sSelect\n", indent)
254 if s.Body != nil {
255 dumpBlock(b, fset, s.Body, depth+1)
256 }
257 case *ast.DeclStmt:
258 dumpNode(b, fset, s.Decl, depth)
259 case *ast.BlockStmt:
260 dumpBlock(b, fset, s, depth)
261 case *ast.CaseClause:
262 if s.List == nil {
263 fmt.Fprintf(b, "%sDefault\n", indent)
264 } else {
265 refs := collectRefs(s.List...)
266 if len(refs) > 0 {
267 fmt.Fprintf(b, "%sCase [%s]\n", indent, strings.Join(refs, ","))
268 } else {
269 fmt.Fprintf(b, "%sCase\n", indent)
270 }
271 }
272 for _, st := range s.Body {
273 dumpStmt(b, fset, st, depth+1)
274 }
275 case *ast.CommClause:
276 if s.Comm == nil {
277 fmt.Fprintf(b, "%sDefault\n", indent)
278 } else {
279 fmt.Fprintf(b, "%sComm\n", indent)
280 }
281 for _, st := range s.Body {
282 dumpStmt(b, fset, st, depth+1)
283 }
284 case *ast.BranchStmt:
285 fmt.Fprintf(b, "%s%s\n", indent, s.Tok)
286 case *ast.DeferStmt:
287 refs := collectRefs(s.Call)
288 if len(refs) > 0 {
289 fmt.Fprintf(b, "%sDefer %s [%s]\n", indent, exprStr(s.Call.Fun), strings.Join(refs, ","))
290 } else {
291 fmt.Fprintf(b, "%sDefer %s\n", indent, exprStr(s.Call.Fun))
292 }
293 case *ast.GoStmt:
294 refs := collectRefs(s.Call)
295 if len(refs) > 0 {
296 fmt.Fprintf(b, "%sGo %s [%s]\n", indent, exprStr(s.Call.Fun), strings.Join(refs, ","))
297 } else {
298 fmt.Fprintf(b, "%sGo %s\n", indent, exprStr(s.Call.Fun))
299 }
300 case *ast.SendStmt:
301 refs := collectRefs(s.Chan, s.Value)
302 if len(refs) > 0 {
303 fmt.Fprintf(b, "%sSend %s [%s]\n", indent, exprStr(s.Chan), strings.Join(refs, ","))
304 } else {
305 fmt.Fprintf(b, "%sSend %s\n", indent, exprStr(s.Chan))
306 }
307 case *ast.IncDecStmt:
308 refs := collectRefs(s.X)
309 if len(refs) > 0 {
310 fmt.Fprintf(b, "%s%s %s [%s]\n", indent, exprStr(s.X), s.Tok, strings.Join(refs, ","))
311 } else {
312 fmt.Fprintf(b, "%s%s %s\n", indent, exprStr(s.X), s.Tok)
313 }
314 case *ast.LabeledStmt:
315 fmt.Fprintf(b, "%sLabel %s\n", indent, s.Label.Name)
316 dumpStmt(b, fset, s.Stmt, depth+1)
317 default:
318 fmt.Fprintf(b, "%s%T\n", indent, stmt)
319 }
320 }
321
322 func exprStr(e ast.Expr) string {
323 if e == nil {
324 return ""
325 }
326 switch x := e.(type) {
327 case *ast.Ident:
328 return x.Name
329 case *ast.SelectorExpr:
330 return exprStr(x.X) + "." + x.Sel.Name
331 case *ast.StarExpr:
332 return "*" + exprStr(x.X)
333 case *ast.ArrayType:
334 if x.Len == nil {
335 return "[]" + exprStr(x.Elt)
336 }
337 return "[" + exprStr(x.Len) + "]" + exprStr(x.Elt)
338 case *ast.MapType:
339 return "map[" + exprStr(x.Key) + "]" + exprStr(x.Value)
340 case *ast.ChanType:
341 return "chan " + exprStr(x.Value)
342 case *ast.FuncType:
343 return "func(...)"
344 case *ast.InterfaceType:
345 return "interface{}"
346 case *ast.StructType:
347 return "struct{...}"
348 case *ast.Ellipsis:
349 return "..." + exprStr(x.Elt)
350 case *ast.CallExpr:
351 return exprStr(x.Fun) + "(...)"
352 case *ast.IndexExpr:
353 return exprStr(x.X) + "[" + exprStr(x.Index) + "]"
354 case *ast.BasicLit:
355 return x.Value
356 case *ast.UnaryExpr:
357 return x.Op.String() + exprStr(x.X)
358 case *ast.BinaryExpr:
359 return exprStr(x.X) + x.Op.String() + exprStr(x.Y)
360 case *ast.ParenExpr:
361 return "(" + exprStr(x.X) + ")"
362 case *ast.CompositeLit:
363 if x.Type != nil {
364 return exprStr(x.Type) + "{...}"
365 }
366 return "{...}"
367 case *ast.TypeAssertExpr:
368 if x.Type != nil {
369 return exprStr(x.X) + ".(" + exprStr(x.Type) + ")"
370 }
371 return exprStr(x.X) + ".(type)"
372 case *ast.SliceExpr:
373 return exprStr(x.X) + "[:]"
374 case *ast.KeyValueExpr:
375 return exprStr(x.Key) + ":" + exprStr(x.Value)
376 case *ast.FuncLit:
377 return "func(){...}"
378 case *ast.IndexListExpr:
379 parts := make([]string, len(x.Indices))
380 for i, idx := range x.Indices {
381 parts[i] = exprStr(idx)
382 }
383 return exprStr(x.X) + "[" + strings.Join(parts, ",") + "]"
384 }
385 return fmt.Sprintf("%T", e)
386 }
387
388 var builtinTypes = map[string]bool{
389 "bool": true, "byte": true, "int": true, "int8": true, "int16": true,
390 "int32": true, "int64": true, "uint": true, "uint8": true, "uint16": true,
391 "uint32": true, "uint64": true, "float32": true, "float64": true,
392 "string": true, "rune": true, "error": true, "any": true,
393 "true": true, "false": true, "nil": true,
394 "len": true, "cap": true, "append": true, "copy": true, "delete": true,
395 "close": true, "panic": true, "recover": true, "print": true, "println": true,
396 "make": true, "new": true,
397 }
398
399 func collectRefs(exprs ...ast.Expr) []string {
400 seen := map[string]bool{}
401 var refs []string
402 for _, e := range exprs {
403 walkExpr(e, func(id string) {
404 if !seen[id] && !builtinTypes[id] {
405 seen[id] = true
406 refs = append(refs, id)
407 }
408 })
409 }
410 return refs
411 }
412
413 func walkExpr(e ast.Expr, visit func(string)) {
414 if e == nil {
415 return
416 }
417 switch x := e.(type) {
418 case *ast.Ident:
419 visit(x.Name)
420 case *ast.SelectorExpr:
421 walkExpr(x.X, visit)
422 visit(x.Sel.Name)
423 case *ast.CallExpr:
424 walkExpr(x.Fun, visit)
425 for _, a := range x.Args {
426 walkExpr(a, visit)
427 }
428 case *ast.BinaryExpr:
429 walkExpr(x.X, visit)
430 walkExpr(x.Y, visit)
431 case *ast.UnaryExpr:
432 walkExpr(x.X, visit)
433 case *ast.StarExpr:
434 walkExpr(x.X, visit)
435 case *ast.ParenExpr:
436 walkExpr(x.X, visit)
437 case *ast.IndexExpr:
438 walkExpr(x.X, visit)
439 walkExpr(x.Index, visit)
440 case *ast.SliceExpr:
441 walkExpr(x.X, visit)
442 walkExpr(x.Low, visit)
443 walkExpr(x.High, visit)
444 walkExpr(x.Max, visit)
445 case *ast.CompositeLit:
446 walkExpr(x.Type, visit)
447 for _, elt := range x.Elts {
448 walkExpr(elt, visit)
449 }
450 case *ast.KeyValueExpr:
451 walkExpr(x.Key, visit)
452 walkExpr(x.Value, visit)
453 case *ast.TypeAssertExpr:
454 walkExpr(x.X, visit)
455 walkExpr(x.Type, visit)
456 case *ast.FuncLit:
457 if x.Body != nil {
458 for _, stmt := range x.Body.List {
459 walkStmtRefs(stmt, visit)
460 }
461 }
462 case *ast.ArrayType:
463 walkExpr(x.Elt, visit)
464 walkExpr(x.Len, visit)
465 case *ast.MapType:
466 walkExpr(x.Key, visit)
467 walkExpr(x.Value, visit)
468 case *ast.IndexListExpr:
469 walkExpr(x.X, visit)
470 for _, idx := range x.Indices {
471 walkExpr(idx, visit)
472 }
473 }
474 }
475
476 func walkStmtRefs(stmt ast.Stmt, visit func(string)) {
477 if stmt == nil {
478 return
479 }
480 switch s := stmt.(type) {
481 case *ast.AssignStmt:
482 for _, e := range s.Lhs {
483 walkExpr(e, visit)
484 }
485 for _, e := range s.Rhs {
486 walkExpr(e, visit)
487 }
488 case *ast.ReturnStmt:
489 for _, e := range s.Results {
490 walkExpr(e, visit)
491 }
492 case *ast.ExprStmt:
493 walkExpr(s.X, visit)
494 case *ast.IfStmt:
495 if s.Init != nil {
496 walkStmtRefs(s.Init, visit)
497 }
498 walkExpr(s.Cond, visit)
499 if s.Body != nil {
500 for _, st := range s.Body.List {
501 walkStmtRefs(st, visit)
502 }
503 }
504 if s.Else != nil {
505 walkStmtRefs(s.Else, visit)
506 }
507 case *ast.ForStmt:
508 if s.Init != nil {
509 walkStmtRefs(s.Init, visit)
510 }
511 walkExpr(s.Cond, visit)
512 if s.Post != nil {
513 walkStmtRefs(s.Post, visit)
514 }
515 if s.Body != nil {
516 for _, st := range s.Body.List {
517 walkStmtRefs(st, visit)
518 }
519 }
520 case *ast.RangeStmt:
521 walkExpr(s.Key, visit)
522 walkExpr(s.Value, visit)
523 walkExpr(s.X, visit)
524 if s.Body != nil {
525 for _, st := range s.Body.List {
526 walkStmtRefs(st, visit)
527 }
528 }
529 case *ast.BlockStmt:
530 for _, st := range s.List {
531 walkStmtRefs(st, visit)
532 }
533 case *ast.SwitchStmt:
534 if s.Init != nil {
535 walkStmtRefs(s.Init, visit)
536 }
537 walkExpr(s.Tag, visit)
538 if s.Body != nil {
539 for _, st := range s.Body.List {
540 walkStmtRefs(st, visit)
541 }
542 }
543 case *ast.TypeSwitchStmt:
544 if s.Init != nil {
545 walkStmtRefs(s.Init, visit)
546 }
547 walkStmtRefs(s.Assign, visit)
548 if s.Body != nil {
549 for _, st := range s.Body.List {
550 walkStmtRefs(st, visit)
551 }
552 }
553 case *ast.CaseClause:
554 for _, e := range s.List {
555 walkExpr(e, visit)
556 }
557 for _, st := range s.Body {
558 walkStmtRefs(st, visit)
559 }
560 case *ast.CommClause:
561 walkStmtRefs(s.Comm, visit)
562 for _, st := range s.Body {
563 walkStmtRefs(st, visit)
564 }
565 case *ast.SelectStmt:
566 if s.Body != nil {
567 for _, st := range s.Body.List {
568 walkStmtRefs(st, visit)
569 }
570 }
571 case *ast.DeferStmt:
572 walkExpr(s.Call.Fun, visit)
573 for _, a := range s.Call.Args {
574 walkExpr(a, visit)
575 }
576 case *ast.GoStmt:
577 walkExpr(s.Call.Fun, visit)
578 for _, a := range s.Call.Args {
579 walkExpr(a, visit)
580 }
581 case *ast.SendStmt:
582 walkExpr(s.Chan, visit)
583 walkExpr(s.Value, visit)
584 case *ast.IncDecStmt:
585 walkExpr(s.X, visit)
586 case *ast.DeclStmt:
587 if gd, ok := s.Decl.(*ast.GenDecl); ok {
588 for _, sp := range gd.Specs {
589 if vs, ok := sp.(*ast.ValueSpec); ok {
590 for _, n := range vs.Names {
591 visit(n.Name)
592 }
593 for _, v := range vs.Values {
594 walkExpr(v, visit)
595 }
596 }
597 }
598 }
599 case *ast.LabeledStmt:
600 walkStmtRefs(s.Stmt, visit)
601 }
602 }
603
604 func exprListStr(exprs []ast.Expr) string {
605 parts := make([]string, len(exprs))
606 for i, e := range exprs {
607 parts[i] = exprStr(e)
608 }
609 return strings.Join(parts, ",")
610 }
611