EntityInfo struct

Information about each entity in the package

(functions, types, interfaces)

Fields:

  • Name (string)
  • Description (string)
  • Example (string)
  • Notes (string)
  • DeprecationNote (string)
  • Parameters ([]string)
  • Returns ([]string)
  • Body (string)
  • Type (string)
  • Fields ([]FieldInfo)
  • Methods ([]EntityInfo)
  • Implements ([]ImplementationInfo)
  • Package (string)
  • PackageURL (string)
  • PackagePath (string)
  • References ([]ReferenceInfo)
  • DescriptionRaw (string)
  • DeprecationNoteRaw (string)

ReferenceInfo struct

Information about references used by an entity

Fields:

  • Name (string)
  • Package (string)
  • PackageURL (string)
  • PackagePath (string)

FieldInfo struct

Information about each field in a struct

Fields:

  • Name (string)
  • Type (string)
  • Tag (string)

ImplementationInfo struct

Information about an implemented interface

Fields:

  • InterfaceName (string)
  • Package (string)

ImportInfo struct

Information about an imported package

Fields:

  • URL (string)
  • Path (string)
  • Alias (string)
  • Doc (string)
  • Comment (string)

EntityExtractor interface

Define an interface for extracting information from AST declarations

Methods:

Extract


Parameters:
  • decl ast.Decl
  • fs *token.FileSet
  • interfaces map[string]EntityInfo
  • pkgName string
  • packagePath string
  • url string

Returns:
  • EntityInfo

FunctionExtractor struct

Extract function details from a function declaration.

Implements:

  • EntityExtractor from parser

Methods:

Extract


Parameters:
  • decl ast.Decl
  • fs *token.FileSet
  • interfaces map[string]EntityInfo
  • pkgName string
  • packagePath string
  • url string

Returns:
  • EntityInfo

References:


Show/Hide Method Body
{
	funcDecl := decl.(*ast.FuncDecl)
	descriptionData := extractDescriptionData(funcDecl.Doc.Text())

	return EntityInfo{
		Name:            funcDecl.Name.Name,
		Type:            "function",
		Body:            extractBody(fs, funcDecl),
		Description:     descriptionData.Description,
		Example:         descriptionData.Example,
		Notes:           descriptionData.Notes,
		DeprecationNote: descriptionData.DeprecationNote,
		Parameters:      extractParameters(funcDecl.Type.Params),
		Returns:         extractParameters(funcDecl.Type.Results),
		Package:         pkgName,
		PackageURL:      url,
		PackagePath:     packagePath,

		// Raw fields
		DescriptionRaw:     descriptionData.DescriptionRaw,
		DeprecationNoteRaw: descriptionData.DeprecationNoteRaw,
	}
}

MethodExtractor struct

Extract method details from a method declaration.

Implements:

  • EntityExtractor from parser

Methods:

Extract


Parameters:
  • decl ast.Decl
  • fs *token.FileSet
  • interfaces map[string]EntityInfo
  • pkgName string
  • packagePath string
  • url string

Returns:
  • EntityInfo

References:


Show/Hide Method Body
{
	funcDecl := decl.(*ast.FuncDecl)
	descriptionData := extractDescriptionData(funcDecl.Doc.Text())

	return EntityInfo{
		Name:            funcDecl.Name.Name,
		Type:            "method",
		Body:            extractBody(fs, funcDecl),
		Description:     descriptionData.Description,
		Example:         descriptionData.Example,
		Notes:           descriptionData.Notes,
		DeprecationNote: descriptionData.DeprecationNote,
		Parameters:      extractParameters(funcDecl.Type.Params),
		Returns:         extractParameters(funcDecl.Type.Results),
		Package:         pkgName,
		PackageURL:      url,
		PackagePath:     packagePath,

		// Raw fields
		DescriptionRaw:     descriptionData.DescriptionRaw,
		DeprecationNoteRaw: descriptionData.DeprecationNoteRaw,
	}
}

StructExtractor struct

Extract struct details from a struct declaration.

Implements:

  • EntityExtractor from parser

Methods:

Extract


Parameters:
  • decl ast.Decl
  • fs *token.FileSet
  • interfaces map[string]EntityInfo
  • pkgName string
  • packagePath string
  • url string

Returns:
  • EntityInfo

References:


Show/Hide Method Body
{
	spec := decl.(*ast.GenDecl).Specs[0].(*ast.TypeSpec)
	structType := spec.Type.(*ast.StructType)

	descriptionData := extractDescriptionData(decl.(*ast.GenDecl).Doc.Text())

	return EntityInfo{
		Name:            spec.Name.Name,
		Type:            "struct",
		Description:     descriptionData.Description,
		Notes:           descriptionData.Notes,
		DeprecationNote: descriptionData.DeprecationNote,
		Fields:          extractFields(structType),
		Package:         pkgName,
		PackageURL:      url,
		PackagePath:     packagePath,

		// Raw fields
		DescriptionRaw:     descriptionData.DescriptionRaw,
		DeprecationNoteRaw: descriptionData.DeprecationNoteRaw,
	}
}

InterfaceExtractor struct

Extract interface details from an interface declaration.

Implements:

  • EntityExtractor from parser

Methods:

Extract


Parameters:
  • decl ast.Decl
  • fs *token.FileSet
  • interfaces map[string]EntityInfo
  • pkgName string
  • packagePath string
  • url string

Returns:
  • EntityInfo

References:


Show/Hide Method Body
{
	spec := decl.(*ast.GenDecl).Specs[0].(*ast.TypeSpec)
	interfaceType := spec.Type.(*ast.InterfaceType)

	descriptionData := extractDescriptionData(decl.(*ast.GenDecl).Doc.Text())

	return EntityInfo{
		Name:            spec.Name.Name,
		Description:     descriptionData.Description,
		Notes:           descriptionData.Notes,
		DeprecationNote: descriptionData.DeprecationNote,
		Type:            "interface",
		Methods:         extractMethods(interfaceType),
		Package:         pkgName,
		PackageURL:      url,
		PackagePath:     packagePath,

		// Raw fields
		DescriptionRaw:     descriptionData.DescriptionRaw,
		DeprecationNoteRaw: descriptionData.DeprecationNoteRaw,
	}
}

TypeExtractor struct

Extract type details from a type declaration.

Implements:

  • EntityExtractor from parser

Methods:

Extract


Parameters:
  • decl ast.Decl
  • fs *token.FileSet
  • interfaces map[string]EntityInfo
  • pkgName string
  • packagePath string
  • url string

Returns:
  • EntityInfo

References:


Show/Hide Method Body
{
	spec := decl.(*ast.GenDecl).Specs[0].(*ast.TypeSpec)
	typeExpr := formatExpr(spec.Type)

	descriptionData := extractDescriptionData(decl.(*ast.GenDecl).Doc.Text())

	return EntityInfo{
		Name:            spec.Name.Name,
		Description:     descriptionData.Description,
		Notes:           descriptionData.Notes,
		DeprecationNote: descriptionData.DeprecationNote,
		Type:            "type",
		Body:            typeExpr,
		Package:         pkgName,
		PackageURL:      url,
		PackagePath:     packagePath,

		// Raw fields
		DescriptionRaw:     descriptionData.DescriptionRaw,
		DeprecationNoteRaw: descriptionData.DeprecationNoteRaw,
	}
}

GetPackages function

Load and return a list of Go package directories from the current project.

Exclude the root directory and only include directories containing Go files.

Example:

packages, err := parser.GetPackages()
if err != nil {
	log.Fatalf("Error fetching packages: %v", err)
}
for _, pkg := range packages {
	fmt.Printf("Package: %s\n", pkg)
}

Returns:

  • []string
  • error
Show/Hide Function Body
{
	cfg := &packages.Config{
		Mode: packages.NeedFiles, // We only need the file paths
	}

	rootDir, err := filepath.Abs(".")
	if err != nil {
		return nil, err
	}

	pkgs, err := packages.Load(cfg, "./...")
	if err != nil {
		return nil, err
	}

	var packageDirs []string
	for _, pkg := range pkgs {
		if len(pkg.GoFiles) > 0 {
			// To get the package directory, we take the directory of the first Go file
			dir := filepath.Dir(pkg.GoFiles[0])

			// if the directory is the root of the project, we skip it
			if dir == rootDir {
				continue
			}
			packageDirs = append(packageDirs, dir)
		}
	}

	return packageDirs, nil
}

ParseEntitiesInPackage function

Parse Go source files in the specified package directory, extracting function, method, type,

struct, and interface information. Also gather import details and associate methods with structs.

Resolve interface implementations and find references for each entity.

Example:

entities, err := parser.ParseEntitiesInPackage("/home/me/myproject/pkg/mypackage")
if err != nil {
	log.Fatalf("Error parsing entities: %v", err)
}
for _, entity := range entities {
	fmt.Printf("Name: %s\n", entity.Name)
	fmt.Printf("Type: %s\n", entity.Type)
	fmt.Printf("Description: %s\n", entity.Description)
	fmt.Printf("Package: %s\n", entity.Package)
}

Notes:

The package must be a full path to the package directory

Parameters:

  • projectPath string
  • pkgPath string
  • relativePath string

Returns:

  • []EntityInfo
  • []ImportInfo
  • error
Show/Hide Function Body
{
	var entities []EntityInfo
	var imports []ImportInfo
	var interfaces = make(map[string]EntityInfo)
	var methodsByType = make(map[string][]EntityInfo)
	var entityIndex = make(map[string]EntityInfo)

	fs := token.NewFileSet()
	pkgs, err := parser.ParseDir(fs, pkgPath, nil, parser.ParseComments)
	if err != nil {
		return nil, nil, err
	}

	var pkgName string
	var pkg *ast.Package
	for k, v := range pkgs {
		pkgName = k
		pkg = v
		break
	}

	extractors := map[string]EntityExtractor{
		"function":  FunctionExtractor{},
		"method":    MethodExtractor{},
		"struct":    StructExtractor{},
		"interface": InterfaceExtractor{},
		"type":      TypeExtractor{},
	}

	// Replace slashes with hyphens to ensure unique filenames
	url := strings.ReplaceAll(relativePath, string(os.PathSeparator), "-")

	for _, file := range pkg.Files {

		// here we parse all imports
		for _, imp := range file.Imports {
			importPath := strings.Trim(imp.Path.Value, `"`)

			var importName string
			if imp.Name != nil {
				if imp.Name.Name == "_" {
					importName = "Anonymous Import"
				} else {
					importName = imp.Name.Name
				}
			} else {
				importName = ""
			}

			importURL := strings.ReplaceAll(importPath, "/", "-")
			doc := ""
			comment := ""
			if imp.Doc != nil {
				doc = imp.Doc.Text()
			}
			if imp.Comment != nil {
				comment = imp.Comment.Text()
			}

			imports = append(imports, ImportInfo{
				Path:    importPath,
				URL:     importURL,
				Alias:   importName,
				Doc:     doc,
				Comment: comment,
			})
		}

		// here we parse all entities types
		for _, decl := range file.Decls {
			switch decl := decl.(type) {
			case *ast.FuncDecl:
				if decl.Recv != nil {
					receiverType := formatExpr(decl.Recv.List[0].Type)
					method := extractors["method"].Extract(decl, fs, interfaces, pkgName, relativePath, url)
					methodsByType[receiverType] = append(methodsByType[receiverType], method)
				} else {
					entity := extractors["function"].Extract(decl, fs, interfaces, pkgName, relativePath, url)
					entities = append(entities, entity)
					entityIndex[pkgName+"."+entity.Name] = entity
				}
			case *ast.GenDecl:
				for _, spec := range decl.Specs {
					switch spec := spec.(type) {
					case *ast.TypeSpec:
						var entityType string
						switch spec.Type.(type) {
						case *ast.StructType:
							entityType = "struct"
						case *ast.InterfaceType:
							entityType = "interface"
							if _, exists := interfaces[spec.Name.Name]; !exists {
								ifaceInfo := extractors[entityType].Extract(decl, fs, interfaces, pkgName, relativePath, url)
								ifaceInfo.Package = pkgName
								interfaces[spec.Name.Name] = ifaceInfo
								entities = append(entities, ifaceInfo)
								entityIndex[pkgName+"."+ifaceInfo.Name] = ifaceInfo
							}
						default:
							entityType = "type"
						}

						if entityType != "interface" {
							entity := extractors[entityType].Extract(decl, fs, interfaces, pkgName, relativePath, url)
							entities = append(entities, entity)
							entityIndex[pkgName+"."+entity.Name] = entity
						}
					}
				}
			}
		}
	}

	// Here we associate methods with structs, resolve interfaces
	// implementations and find references for each entity
	for i, entity := range entities {
		references := findReferences(entity, entityIndex)
		entity.References = references

		// if the entity is a struct, we associate methods with it
		if entity.Type == "struct" {
			receiverName := entity.Name
			if methods, ok := methodsByType[receiverName]; ok {
				entity.Methods = append(entity.Methods, methods...)
			} else if methods, ok := methodsByType["*"+receiverName]; ok {
				entity.Methods = append(entity.Methods, methods...)
			}

			entity.Implements = findImplementedInterfaces(entity, interfaces)

			// and here we find references for each method if any
			for j, method := range entity.Methods {
				methodReferences := findReferences(method, entityIndex)
				entity.Methods[j].References = methodReferences
			}
		}

		entities[i] = entity
	}

	return entities, imports, nil
}

findImplementedInterfaces function

Check which interfaces are implemented by a struct

Parameters:

  • entity EntityInfo
  • interfaces map[string]EntityInfo

Returns:

  • []ImplementationInfo

References:

Show/Hide Function Body
{
	var implemented []ImplementationInfo

	for ifaceName, ifaceInfo := range interfaces {
		if implementsInterface(entity, ifaceInfo) {
			implemented = append(implemented, ImplementationInfo{
				InterfaceName: ifaceName,
				Package:       ifaceInfo.Package,
			})
		}
	}

	return implemented
}

implementsInterface function

Check if a struct implements a given interface

Parameters:

  • entity EntityInfo
  • iface EntityInfo

Returns:

  • bool

References:

Show/Hide Function Body
{
	methodSet := make(map[string]EntityInfo)
	for _, method := range entity.Methods {
		methodSet[method.Name] = method
	}

	for _, ifaceMethod := range iface.Methods {
		if method, ok := methodSet[ifaceMethod.Name]; !ok {
			return false
		} else {
			if !methodsMatch(ifaceMethod, method) {
				return false
			}
		}
	}

	return true
}

methodsMatch function

Check if the parameters and return types of two methods match

Parameters:

  • ifaceMethod EntityInfo
  • structMethod EntityInfo

Returns:

  • bool

References:

Show/Hide Function Body
{
	if len(ifaceMethod.Parameters) != len(structMethod.Parameters) ||
		len(ifaceMethod.Returns) != len(structMethod.Returns) {
		return false
	}

	for i, param := range ifaceMethod.Parameters {
		if param != structMethod.Parameters[i] {
			return false
		}
	}

	for i, ret := range ifaceMethod.Returns {
		if ret != structMethod.Returns[i] {
			return false
		}
	}

	return true
}

ExampleDeprecationNote function

ExampleDeprecationNote is an example of a deprecated function

Deprecated:

This function is deprecated only for demonstration purposes

Show/Hide Function Body
{}

extractMethods function

Extract methods from an interface declaration

Parameters:

  • interfaceType *ast.InterfaceType

Returns:

  • []EntityInfo
Show/Hide Function Body
{
	var methods []EntityInfo
	for _, field := range interfaceType.Methods.List {
		if funcType, ok := field.Type.(*ast.FuncType); ok {
			methodInfo := EntityInfo{
				Name:       field.Names[0].Name,
				Parameters: extractParameters(funcType.Params),
				Returns:    extractParameters(funcType.Results),
			}
			methods = append(methods, methodInfo)
		}
	}
	return methods
}

extractFields function

Extract fields from a struct

Parameters:

  • structType *ast.StructType

Returns:

  • []FieldInfo
Show/Hide Function Body
{
	var fields []FieldInfo
	for _, field := range structType.Fields.List {
		typeStr := formatExpr(field.Type)
		for _, name := range field.Names {
			fieldInfo := FieldInfo{
				Name: name.Name,
				Type: typeStr,
				Tag:  extractTag(field),
			}
			fields = append(fields, fieldInfo)
		}
	}
	return fields
}

extractTag function

Extract a structs tags

Parameters:

  • field *ast.Field

Returns:

  • string
Show/Hide Function Body
{
	if field.Tag != nil {
		return strings.Trim(field.Tag.Value, "`")
	}
	return ""
}

DescriptionData struct

Holds different parts of a function's documentation comment

Fields:

  • Description (string)
  • Example (string)
  • Notes (string)
  • DeprecationNote (string)
  • Returns (string)
  • DescriptionRaw (string)
  • DeprecationNoteRaw (string)

extractDescriptionData function

Extract and format description data and example code from a

documentation comment string

Parameters:

  • doc string

Returns:

  • DescriptionData

References:

Show/Hide Function Body
{
	lines := strings.Split(doc, "\n")

	var descLines []string
	var exampleLines []string
	var notesLines []string
	var deprecationNoteLines []string
	var returnsLines []string

	var description string
	var example string
	var notes string
	var deprecationNote string
	var returns string

	isExample := false
	isNotes := false
	isDeprecationNote := false
	isReturns := false

	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "Example:") {
			isExample = true
			isNotes = false
			isDeprecationNote = false
			isReturns = false
			continue
		}
		if strings.HasPrefix(line, "Notes:") {
			isNotes = true
			isExample = false
			isDeprecationNote = false
			isReturns = false
			continue
		}
		if strings.HasPrefix(line, "Deprecated:") {
			isDeprecationNote = true
			isExample = false
			isNotes = false
			isReturns = false
			continue
		}
		if strings.HasPrefix(line, "Returns:") {
			isReturns = true
			isExample = false
			isNotes = false
			isDeprecationNote = false
			continue
		}

		if isExample {
			exampleLines = append(exampleLines, line)
		} else if isNotes {
			notesLines = append(notesLines, line)
		} else if isDeprecationNote {
			deprecationNoteLines = append(deprecationNoteLines, line)
		} else if isReturns {
			returnsLines = append(returnsLines, line)
		} else {
			descLines = append(descLines, line)
		}
	}

	// Description
	descriptionRaw := strings.Join(descLines, "\n")
	description = strings.Join(descLines, "</p>\n<p>")
	description = "<p>" + description + "</p>"
	description = strings.ReplaceAll(description, "\t", " ")
	if description == "<p></p>" {
		description = ""
	}

	// Example
	example = strings.Join(exampleLines, "\n")
	example = strings.TrimLeft(example, " \t")
	example = strings.TrimLeft(example, "\n")
	example = formatExample(example)

	// Notes
	notes = strings.Join(notesLines, "</p>\n<p>")
	notes = "<p>" + notes + "</p>"
	notes = strings.ReplaceAll(notes, "\t", " ")
	if notes == "<p></p>" {
		notes = ""
	}

	// Deprecation Note
	deprecationNoteRaw := strings.Join(deprecationNoteLines, "\n")
	deprecationNote = strings.Join(deprecationNoteLines, "</p>\n<p>")
	deprecationNote = "<p>" + deprecationNote + "</p>"
	deprecationNote = strings.ReplaceAll(deprecationNote, "\t", " ")
	if deprecationNote == "<p></p>" {
		deprecationNote = ""
	}

	// Returns
	returns = strings.Join(returnsLines, "</p>\n<p>")
	returns = "<p>" + returns + "</p>"
	returns = strings.ReplaceAll(returns, "\t", " ")
	if returns == "<p></p>" {
		returns = ""
	}

	return DescriptionData{
		Description:     description,
		Example:         example,
		Notes:           notes,
		DeprecationNote: deprecationNote,
		Returns:         returns,

		// Raw fields
		DescriptionRaw:     descriptionRaw,
		DeprecationNoteRaw: deprecationNoteRaw,
	}
}

formatExample function

Format the example code snippet to be properly indented and aligned

using the go/format package.

Parameters:

  • example string

Returns:

  • string
Show/Hide Function Body
{
	src := []byte(example)
	formattedSrc, err := format.Source(src)
	if err != nil {
		return example
	}

	return string(formattedSrc)
}

extractParameters function

Extract the parameters from a function or method declaration

Parameters:

  • fieldList *ast.FieldList

Returns:

  • []string
Show/Hide Function Body
{
	var params []string
	if fieldList != nil {
		for _, param := range fieldList.List {
			typeStr := formatExpr(param.Type)
			for _, name := range param.Names {
				params = append(params, name.Name+" "+typeStr)
			}
			if len(param.Names) == 0 {
				params = append(params, typeStr)
			}
		}
	}
	return params
}

extractBody function

Extracts the body of a function declaration

Parameters:

  • fs *token.FileSet
  • fn *ast.FuncDecl

Returns:

  • string
Show/Hide Function Body
{
	if fn.Body == nil {
		return ""
	}

	start := fs.Position(fn.Body.Pos()).Offset
	end := fs.Position(fn.Body.End()).Offset
	fileContent, _ := os.ReadFile(fs.File(fn.Body.Pos()).Name())
	body := string(fileContent[start:end])

	// before returning we have to escape possible html snippets in it since
	// those snippets are rendered by highlighting.js which has an issue with
	// unescaped html snippets (yeah even if inside a Go string, what a pleasure)
	return html.EscapeString(body)
}

formatExpr function

Format an expression into a string representation

using the go/format package.

Parameters:

  • expr ast.Expr

Returns:

  • string
Show/Hide Function Body
{
	var out strings.Builder
	if err := format.Node(&out, token.NewFileSet(), expr); err != nil {
		return ""
	}
	return out.String()
}

findReferences function

Identify references to other entities within the given entity.

Parameters:

  • entity EntityInfo
  • entityIndex map[string]EntityInfo

Returns:

  • []ReferenceInfo

References:

Show/Hide Function Body
{
	var references []ReferenceInfo

	// Check for parameters
	for _, param := range entity.Parameters {
		parts := strings.Fields(param)
		paramType := parts[len(parts)-1]
		if refEntity, found := entityIndex[entity.Package+"."+paramType]; found {
			references = append(references, ReferenceInfo{
				Name:        paramType,
				Package:     refEntity.Package,
				PackageURL:  refEntity.PackageURL,
				PackagePath: refEntity.PackagePath,
			})
		}
	}

	// Check for returns
	for _, ret := range entity.Returns {
		parts := strings.Fields(ret)
		retType := parts[len(parts)-1]
		if refEntity, found := entityIndex[entity.Package+"."+retType]; found {
			references = append(references, ReferenceInfo{
				Name:        retType,
				Package:     refEntity.Package,
				PackageURL:  refEntity.PackageURL,
				PackagePath: refEntity.PackagePath,
			})
		}
	}

	// Check for fields
	for _, field := range entity.Fields {
		if refEntity, found := entityIndex[entity.Package+"."+field.Type]; found {
			references = append(references, ReferenceInfo{
				Name:        field.Type,
				Package:     refEntity.Package,
				PackageURL:  refEntity.PackageURL,
				PackagePath: refEntity.PackagePath,
			})
		}
	}

	return references
}

go/ast import

Import example:

import "go/ast"

go/token import

Import example:

import "go/token"

path/filepath import

Import example:

import "path/filepath"

golang.org/x/tools/go/packages import

Import example:

import "golang.org/x/tools/go/packages"

go/ast import

Import example:

import "go/ast"

go/parser import

Import example:

import "go/parser"

go/token import

Import example:

import "go/token"

os import

Import example:

import "os"

strings import

Import example:

import "strings"

go/ast import

Import example:

import "go/ast"

go/format import

Import example:

import "go/format"

go/token import

Import example:

import "go/token"

html import

Import example:

import "html"

os import

Import example:

import "os"

strings import

Import example:

import "strings"