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.

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

Categories: Snippets

Yu

Ideals are like the stars: we never reach them, but like the mariners of the sea, we chart our course by them.

Leave a Reply

Your email address will not be published. Required fields are marked *