EntityInfo struct

EntityInfo contains relevant 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

ReferenceInfo contains information about references used by an entity

Fields:

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

FieldInfo struct

FieldInfo contains relevant information about each field in a struct

Fields:

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

ImplementationInfo struct

ImplementationInfo contains information about an implemented interface

Fields:

  • InterfaceName (string)
  • Package (string)

ImportInfo struct

ImportInfo contains information about an imported package

Fields:

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

EntityExtractor interface

EntityExtractor defines 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

FunctionExtractor extracts information from function declarations

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

MethodExtractor extracts information from method declarations

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

StructExtractor extracts information from struct declarations

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

InterfaceExtractor extracts information from interface declarations

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

TypeExtractor extracts information from type declarations

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

GetPackages returns a list of all package directories in the project

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

ParseEntitiesInPackage parses the entities in a given package and returns

a slice of EntityInfo

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

findImplementedInterfaces checks 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

implementsInterface checks 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

methodsMatch checks 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

extractMethods extracts 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

extractFields extracts 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

extractTag extracts struct tags

Parameters:

  • field *ast.Field

Returns:

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

DescriptionData struct

DescriptionData contains different parts of a function's documentation comment

Fields:

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

extractDescriptionData function

extractDescriptionData extracts the description and example code from a

function's documentation comment

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 description string
	var example string
	var notes string
	var deprecationNote string

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

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

		if isExample {
			exampleLines = append(exampleLines, line)
		} else if isNotes {
			notesLines = append(notesLines, line)
		} else if isDeprecationNote {
			deprecationNoteLines = append(deprecationNoteLines, 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 = ""
	}

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

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

formatExample function

formatExample formats the example code 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

extractParameters extracts 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

extractBody 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

formatExpr formats an expression 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

findReferences finds references to other entities in an 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 {
		paramType := strings.Split(param, " ")[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 {
		if refEntity, found := entityIndex[entity.Package+"."+ret]; found {
			references = append(references, ReferenceInfo{
				Name:        ret,
				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"