The program is designed to traverse and analyze Go language source code within a specified package directory. It utilizes the go/parser
and go/token
packages to parse the abstract syntax tree (AST) of each Go file. The main functionality includes:
Parsing Package Files: The program reads and parses all Go files within a given directory, creating an AST for each file.
Identifying Struct Definitions: It searches through the AST to find all declarations of struct types.
Preserving Comments: The program retains comments that are associated with the struct definitions and their fields.
Formatting and Printing Structs: The struct types, along with their fields and associated comments, are formatted to resemble the original Go code. The program uses the
go/format
package to ensure that the output closely matches the syntax and style of handwritten Go code.Output: The program outputs the struct definitions to the standard output (console), allowing users to see the struct types and associated comments in a readable format.
The program is invoked by running the main.go
file with the -path
flag set to the path of the Go package directory to be analyzed. The output can be used for documentation purposes, code reviews, or as a reference when working with existing codebases.
package main import ( "flag" "fmt" "go/ast" "go/format" "go/parser" "go/token" "io/fs" "os" "path/filepath" "strings" ) func formatNode(fset *token.FileSet, node ast.Node) (string, error) { var buf strings.Builder if err := format.Node(&buf, fset, node); err != nil { return "", err } return buf.String(), nil } func mustFormatNode(fset *token.FileSet, node ast.Node) string { str, err := formatNode(fset, node) if err != nil { panic(err) } return str } func printComments(fset *token.FileSet, file *ast.File, pos token.Pos, indent int) { for _, group := range file.Comments { for _, comment := range group.List { if fset.Position(comment.Pos()).Line == fset.Position(pos).Line-1 { fmt.Printf("%s%s\n", strings.Repeat(" ", indent), comment.Text) } } } } func printStructs(fset *token.FileSet, file *ast.File) { for _, decl := range file.Decls { if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE { for _, spec := range genDecl.Specs { tspec := spec.(*ast.TypeSpec) if structType, ok := tspec.Type.(*ast.StructType); ok { structName := tspec.Name.Name // Print comments associated with the struct printComments(fset, file, tspec.Pos(), 0) fmt.Printf("type %s struct {\n", structName) for _, field := range structType.Fields.List { if field.Names != nil { for _, name := range field.Names { // Print comments associated with the field printComments(fset, file, name.Pos(), 4) fmt.Printf(" %s %s", name.Name, mustFormatNode(fset, field.Type)) if field.Tag != nil { fmt.Printf(" %s", field.Tag.Value) } fmt.Println("") } } else { // anonymous field // Print comments associated with the field printComments(fset, file, field.Type.Pos(), 4) fmt.Printf(" %s", mustFormatNode(fset, field.Type)) if field.Tag != nil { fmt.Printf(" %s", field.Tag.Value) } fmt.Println("") } } fmt.Println("}") } } } } } func visitFiles(fset *token.FileSet, files map[string]*ast.File) { for filename, file := range files { fmt.Printf("/* file: %s */\n", filename) printStructs(fset, file) } } func main() { var path string flag.StringVar(&path, "path", "./", "Directory to parse") flag.Parse() // Create the AST file set. fset := token.NewFileSet() // Parse all files in the directory. pkgs, err := parser.ParseDir(fset, path, func(info fs.FileInfo) bool { //log.Infof("# file: %s", info.Name()) return !info.IsDir() && filepath.Ext(info.Name()) == ".go" }, parser.ParseComments) if err != nil { fmt.Println("Error parsing directory:", err) os.Exit(1) } // Map to hold package files. files := make(map[string]*ast.File) for _, pkg := range pkgs { for name, file := range pkg.Files { files[filepath.Join(path, name)] = file } } // Visit all files and collect struct definitions. visitFiles(fset, files) }