Files
ragmcp/tools/parse_go_ast.go
T
2025-11-04 00:23:51 +00:00

195 lines
4.7 KiB
Go

// # Build from project root:
// go build -o tools/parse_go_ast tools/parse_go_ast.go
package main
import (
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"os"
"strings"
)
type GoDecl struct {
Name string `json:"name"`
Type string `json:"type"` // "func", "method", "type", "struct", "interface", "var", "package"
Receiver string `json:"receiver,omitempty"` // e.g., "*User"
FullName string `json:"full_name,omitempty"`
Fields []string `json:"fields,omitempty"` // for structs
Methods []string `json:"methods,omitempty"` // for interfaces
DocComment string `json:"doc_comment,omitempty"`
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
}
func astTypeToString(expr ast.Expr) string {
switch t := expr.(type) {
case *ast.Ident:
return t.Name
case *ast.StarExpr:
return "*" + astTypeToString(t.X)
case *ast.SelectorExpr:
return astTypeToString(t.X) + "." + t.Sel.Name
case *ast.ArrayType:
return "[]" + astTypeToString(t.Elt)
case *ast.MapType:
return "map[" + astTypeToString(t.Key) + "]" + astTypeToString(t.Value)
default:
return "unknown"
}
}
func extractDocComment(comments []*ast.CommentGroup, pos token.Pos, fset *token.FileSet) string {
if len(comments) == 0 {
return ""
}
line := fset.Position(pos).Line
for i := len(comments) - 1; i >= 0; i-- {
cg := comments[i]
cgLine := fset.Position(cg.End()).Line
if cgLine < line && line-cgLine <= 5 {
return strings.TrimSpace(cg.Text())
}
}
return ""
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: parse_go_ast <file.go>")
os.Exit(1)
}
filename := os.Args[1]
src, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Printf("ERR: %v\n", err)
os.Exit(2)
}
fset := token.NewFileSet()
fileNode, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
if err != nil {
fmt.Printf("ERR: %v\n", err)
os.Exit(3)
}
decls := []GoDecl{}
// Package comment (first comment group before package decl)
pkgComment := ""
for _, cg := range fileNode.Comments {
if cg.Pos() < fileNode.Package {
pkgComment = strings.TrimSpace(cg.Text())
} else {
break
}
}
if pkgComment != "" {
decls = append(decls, GoDecl{
Name: "package",
Type: "package",
DocComment: pkgComment,
StartLine: 1,
EndLine: 1,
})
}
// Process declarations
for _, d := range fileNode.Decls {
switch d := d.(type) {
case *ast.FuncDecl:
name := d.Name.Name
recvType := ""
if d.Recv != nil && len(d.Recv.List) > 0 {
recvType = astTypeToString(d.Recv.List[0].Type)
}
typ := "func"
fullName := name
if recvType != "" {
typ = "method"
fullName = fmt.Sprintf("(%s).%s", recvType, name)
}
doc := extractDocComment(fileNode.Comments, d.Pos(), fset)
decls = append(decls, GoDecl{
Name: name,
Type: typ,
Receiver: recvType,
FullName: fullName,
DocComment: doc,
StartLine: fset.Position(d.Pos()).Line,
EndLine: fset.Position(d.End()).Line,
})
case *ast.GenDecl:
for _, spec := range d.Specs {
switch s := spec.(type) {
case *ast.TypeSpec:
typName := s.Name.Name
declType := "type"
var fields []string
var methods []string
switch t := s.Type.(type) {
case *ast.StructType:
declType = "struct"
for _, f := range t.Fields.List {
for _, n := range f.Names {
field := n.Name
if f.Tag != nil {
field += " " + f.Tag.Value
}
fields = append(fields, field)
}
if f.Names == nil {
// Embedded field
fields = append(fields, astTypeToString(f.Type))
}
}
case *ast.InterfaceType:
declType = "interface"
for _, m := range t.Methods.List {
if len(m.Names) > 0 {
methods = append(methods, m.Names[0].Name)
}
}
}
doc := extractDocComment(fileNode.Comments, s.Pos(), fset)
decls = append(decls, GoDecl{
Name: typName,
Type: declType,
Fields: fields,
Methods: methods,
DocComment: doc,
StartLine: fset.Position(d.Pos()).Line,
EndLine: fset.Position(d.End()).Line,
})
case *ast.ValueSpec:
for _, name := range s.Names {
doc := extractDocComment(fileNode.Comments, name.Pos(), fset)
// Use the individual identifier's position, not the declaration's position
startLine := fset.Position(name.Pos()).Line
endLine := fset.Position(name.End()).Line
decls = append(decls, GoDecl{
Name: name.Name,
Type: "var",
DocComment: doc,
StartLine: startLine,
EndLine: endLine,
})
}
}
}
}
}
out, _ := json.Marshal(decls)
fmt.Println(string(out))
}