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:

  1. Parsing Package Files: The program reads and parses all Go files within a given directory, creating an AST for each file.

  2. Identifying Struct Definitions: It searches through the AST to find all declarations of struct types.

  3. Preserving Comments: The program retains comments that are associated with the struct definitions and their fields.

  4. 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.

  5. 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.

Go
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)
}