Package gentools contains Golang tools used for scanning and parsing golang code.
It is based on the golang ast and provides simple but very powerful functionality in terms of code scanning.
The API is expected to be simpler and less verbose than go/types package, that looks more like reflect.
This package allows reading and scanning the content of go files based on the non-runtime AST definitions.
The types.PackageMap contains context of mapped packages with their structs and dependencies.
The map could get loaded by using LoadPackages function or types.PackageMap method with the same name.
Both of these loads provided input packages, whereas the method gets only required that doesn't already exist in itself.
Gentools preserves the existing concrete types.Struct, types.Interface,
types.Alias, and types.Function values for generic declarations. Their
additive GenericInfo() accessor exposes declared and receiver type parameters;
each parameter includes its owner, ordinal, and constraint. A use such as
Box[string] is represented by *types.Instantiation, whose Origin and
ordered Arguments distinguish it from Box[int] and the uninstantiated
declaration. Generic aliases are supported by the selected Go 1.27.1
toolchain and require GODEBUG=gotypesalias=1 while loading packages.
The project toolkit is pinned to Go 1.27.1 through .go-version.
Make targets use go from PATH and fail unless it reports the pinned version.
Configure Go through your IDE or a version manager such as mise or asdf:
make testThe canonical lint gate uses golangci-lint v2.13.2. Install that exact
release from the official binary installer into a directory on PATH, then
run:
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b "$(go env GOPATH)/bin" v2.13.2
golangci-lint version
golangci-lint config verify --config .golangci.yml
make golangci-lintThe local command and hosted lint job both apply .golangci.yml to the root
module (./...). Findings produce a non-zero result. See the CI and lint
quickstart for failure-path
and matrix validation evidence.
Visit the Gentools guides for a polished, browser-friendly entry point to the runnable example and testing guidance.
Run the complete quality workflow with the selected toolchain:
make test-qualityThe consumer scenarios distinguish candidate-source validation from the tag-triggered published-module smoke test. See the integration guide and unit-test gap assessment.
For a runnable code-generation example, create a deterministic struct summary:
go run ./examples/codegen/struct-summary \
-input ./examples/codegen/struct-summary/testdata \
-output /tmp/summary.goTo generate typed getters for only the fields you select, run:
go run ./examples/codegen/struct-accessors \
-input ./examples/codegen/struct-accessors/testdata \
-type Account \
-fields ID,Email \
-output /tmp/account_accessors.goFor an annotated API contract example, run:
go run ./examples/codegen/openapi-contract \
-input ./examples/codegen/openapi-contract/testdata \
-output /tmp/gentools-openapi.jsonOperations use comments such as @openapi method=GET route=/pets/{id} summary="Get a pet" response=200:Pet. The example supports documented
primitive, pointer, slice, and nested struct fields, reuses shared model
definitions, and fails with a source declaration diagnostic for malformed,
duplicate, unresolved, or unsupported input. See the OpenAPI contract
quickstart and the
code-generation guide.
GitHub Pages publication is configured by .github/workflows/pages.yml; a
repository administrator must select GitHub Actions as the Pages source as
described in docs/publishing.md.
This feature drops support for Go releases before 1.27.1. Upgrade the Go
toolchain selected by your version manager or IDE to the version in
.go-version, then run the validation commands in the feature
quickstart.
A release containing this support-boundary change is breaking. Before publishing it, release it under the next major-version policy and include this migration guidance in the release notes, as required by the project constitution.
Example:
package main
import (
"fmt"
"os"
"github.com/kucjac/gentools/types"
"github.com/kucjac/gentools/parser"
)
func main() {
pkgs, err := parser.LoadPackages(parser.LoadConfig{
// Paths should contain file system paths to related golang directories.
// Paths: []string{"/home/user/golang/src/github.com/kucjac/gentools", "./../mypackage"},
// PkgNames should contain full package names to get. It could be set along with the 'Paths' field.
PkgNames: []string{"github.com/kucjac/gentools"},
BuildFlags: nil,
Verbose: false,
})
if err != nil {
fmt.Printf("Err: Loading packages failed: %s\n", err)
os.Exit(1)
}
}Based on loaded packages a developer can operate on the types loaded along with these packages.
// Let's get the types.Type interface and check it's content.
t, ok := pkgs.TypeOf("types.Type", nil)
if !ok {
fmt.Println("Err: getting types.Type failed")
os.Exit(1)
}
// We can get the name of given type, with or without it's identifier.
fmt.Println(t.Name(true, "")) // types.Type
fmt.Println(t.Name(false, "")) // Type
// We can set up current working package context while getting the name.
// This way the result should not contain the identifier.
// The package context could be an identifier or even better full package name.
fmt.Println(t.Name(true, "types")) // TypeWhile the packages got loaded not only selected packages were provided but also all dependency imports.
mutexType, ok := pkgs.TypeOf("*sync.Mutex", nil)
if !ok {
fmt.Println("Err: getting *sync.Mutex failed")
os.Exit(1)
}
fmt.Println(mutexType.Name(true, "")) // *sync.Mutex
// We can dereference the pointer, slice, array, channel or wrapper by using 'Elem' method.
mutexType = mutexType.Elem()
fmt.Println(mutexType.Name(true, "")) // sync.MutexThe types allows to easily create and operate on given types with very simple API.
structType, ok := pkgs.TypeOf("types.Struct")
if !ok {
fmt.Println("Err: getting types.Struct failed")
os.Exit(1)
}
// Let's create a chan of slices of pointers to the given Struct.
newType := types.ChanOf(types.RecvOnly, types.SliceOf(types.PointerTo(structType)))
fmt.Println(sliceType) // chan <-[]*types.StructThe package reads all methods and structure fields for loaded types along with the metadata like receiver type (methods) and name, or parameter names.
sliceType, ok := pkgs.TypeOf("types.Array")
if !ok {
fmt.Println("Err: getting types.Struct failed")
os.Exit(1)
}
st := sliceType.(*types.Struct)
for i, sField := range st.Fields {
fmt.Printf("Field: %d - %s\t%s\n", i, sField.Name, sField.Type)
}
for i, method := range st.Methods {
fmt.Printf("Method: %d - %s\n", i, method)
}Each package could also be extracted out of all packages. It is also possible to extract any declaration out of the specific package in a way like:
const typesPkgName = "github.com/kucjac/gentools/types"
typesPkg, ok := pkgs.PackageByPath(typesPkgName)
if !ok {
fmt.Printf("Err: types package: %s not parsed\n", typesPkgName)
os.Exit(1)
}
fmt.Println(typesPkg.GetPkgPath()) // github.com/kucjac/gentools/types
fmt.Println(typesPkg.Identifier) // types
parsedPkg, ok := typesPkg.GetType("Package") // This gets parsed types.Package type.
if !ok {
fmt.Println("Err: package types can't get the type 'Package'")
os.Exit(1)
}Each type could be compared to the other in two ways:
Two types could be compared on their equality by calling the Equal method of one type and providing another as an argument.
If two types are exactly the same then the value would result as true.
i.e.:
// Check if the type String is equal to the type Bool
fmt.Println(types.String.Equal(types.Bool)) // false
// But exactly the same types would give a positive result i.e:
fmt.Println(types.String.Equal(types.String)) // true
// This also applies to any other type - slice, struct, alias, interface, map, pointer etc i.e.:
fmt.Println(types.SliceOf(types.String).Equal(types.SliceOf(types.String))) // trueIt is important to see that the aliases would not be equal to the type they point to i.e.:
type AliasOfInt int
// Load this package and compare types ...
fmt.Println(aliasOfIntType.Equal(types.Int)) // false
// But if we try to derefence this alias the value would be positive:
fmt.Println(aliasOfIntType.Elem().Equal(types.Int)) // trueIn some situations if we don't want to check if the types are equal directly, we can compare the kind of the type.
As an example, we might want to check if the type that we're looking for is a pointer, and if we need to dereference it.
All dereferences are done by using Elem() method, which panics if there is nothing to dereference - you cannot dereference basic types, structs etc.
These are supported type kinds:
- KindBool
- KindInt
- KindInt8
- KindInt16
- KindInt32
- KindInt64
- KindUint
- KindUint8
- KindUint16
- KindUint32
- KindUint64
- KindUintptr
- KindFloat32
- KindFloat64
- KindComplex64
- KindComplex128
- KindString
- KindArray
- KindChan
- KindFunc
- KindInterface
- KindMap
- KindPtr
- KindSlice
- KindStruct
- KindUnsafePointer
In order to compare some types kinds simply use the Kind method of the type and compare it like:
fmt.Println(type1.Kind() == type2.Kind()) // KindBool == KindBool = true This might be super helpful in terms of comparing the kind of aliased types. I.e. if we have alias type mentioned above:
// AliasOfInt is a type wrapper over the `int` standard type.
type AliasOfInt int
// As previously known this would fail
fmt.Println(aliasOfIntType.Equal(types.Int)) // false
// But we can compare it's kind which would result
fmt.Println(aliasOfIntType.Kind() == types.KindInt) // trueThis package supports also interfaces comparison. The parser deeply checks all methods and provides a way to check if an type (struct, interface) implements also another interface.
i.e.:
osFile, ok := pkgs.TypeOf("*os.File")
if !ok {
fmt.Println("*os.File not found")
os.Exit(1)
}
ioReader, ok := pkgs.TypeOf("io.Reader")
if !ok {
fmt.Println("io.Reader not found")
os.Exit(1)
}
// The function types.Implements allows, checking if a type implements provided interface.
fmt.Println(types.Implements(osFile, io.Reader.(*types.Interface))) // true
ioReadCloser, ok := pkgs.TypeOf("io.ReadCloser")
if !ok {
fmt.Println("io.ReadCloser not found")
os.Exit(1)
}
// It could also be an interface - io.ReadCloser implements io.Reader interface.
fmt.Println(types.Implements(ioReadCloser, io.Reader.(*types.Interface))) // true