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/formatpackage 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)
}