-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added support for composite generics
- Loading branch information
1 parent
3c94f9e
commit 548317b
Showing
10 changed files
with
1,209 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package types | ||
|
||
import ( | ||
"go/ast" | ||
"go/token" | ||
) | ||
|
||
// InterfaceSpecification represents abstraction over interface type. It contains all the metadata | ||
// required to render a mock for given interface. One could deduce whether interface is generic | ||
// by looking for type params | ||
type InterfaceSpecification struct { | ||
InterfaceName string | ||
InterfaceParams []InterfaceSpecificationParam | ||
} | ||
|
||
// InterfaceSpecificationParam represents a group of type param variables and their type | ||
// I.e. [T,K any] would result in names "T","K" and type "any" | ||
type InterfaceSpecificationParam struct { | ||
ParamNames []string | ||
ParamType string | ||
} | ||
|
||
func FindAllInterfaces(p *ast.Package, pattern string) []InterfaceSpecification { | ||
// Find all declared types in a single package | ||
types := []*ast.TypeSpec{} | ||
for _, file := range p.Files { | ||
types = append(types, findAllTypeSpecsInFile(file)...) | ||
} | ||
|
||
// Filter interfaces from all the declarations | ||
interfaces := []*ast.TypeSpec{} | ||
for _, typeSpec := range types { | ||
if isInterface(typeSpec) { | ||
interfaces = append(interfaces, typeSpec) | ||
} | ||
} | ||
|
||
// Filter interfaces with the given pattern | ||
filteredInterfaces := []*ast.TypeSpec{} | ||
for _, iface := range interfaces { | ||
if match(iface.Name.Name, pattern) { | ||
filteredInterfaces = append(filteredInterfaces, iface) | ||
} | ||
} | ||
|
||
// Transform AST nodes into specifications | ||
interfaceSpecifications := make([]InterfaceSpecification, 0, len(filteredInterfaces)) | ||
for _, iface := range filteredInterfaces { | ||
interfaceSpecifications = append(interfaceSpecifications, InterfaceSpecification{ | ||
InterfaceName: iface.Name.Name, | ||
InterfaceParams: getTypeParams(iface), | ||
}) | ||
} | ||
|
||
return interfaceSpecifications | ||
} | ||
|
||
func isInterface(typeSpec *ast.TypeSpec) bool { | ||
// Check if this type declaration is specifically an interface declaration | ||
_, ok := typeSpec.Type.(*ast.InterfaceType) | ||
return ok | ||
} | ||
|
||
// findAllInterfaceNodesInFile ranges over file's AST nodes and extracts all interfaces inside | ||
// returned *ast.TypeSpecs can be safely interpreted as interface declaration nodes | ||
func findAllTypeSpecsInFile(f *ast.File) []*ast.TypeSpec { | ||
typeSpecs := []*ast.TypeSpec{} | ||
|
||
// Range over all declarations in a single file | ||
for _, declaration := range f.Decls { | ||
// Check if declaration is an import, constant, type or variable declaration. | ||
// If it is, check specifically if it's a TYPE as all interfaces are types | ||
if genericDeclaration, ok := declaration.(*ast.GenDecl); ok && genericDeclaration.Tok == token.TYPE { | ||
// Range over all specifications and find ones that are Type declarations | ||
// This is mostly a precaution | ||
for _, spec := range genericDeclaration.Specs { | ||
// Check directly for a type spec declaration | ||
if typeSpec, ok := spec.(*ast.TypeSpec); ok { | ||
typeSpecs = append(typeSpecs, typeSpec) | ||
} | ||
} | ||
} | ||
} | ||
|
||
return typeSpecs | ||
} | ||
|
||
// match returns true if pattern is a wildcard or directly matches the given name | ||
func match(name, pattern string) bool { | ||
return pattern == "*" || name == pattern | ||
} | ||
|
||
func getTypeParams(typeSpec *ast.TypeSpec) []InterfaceSpecificationParam { | ||
params := []InterfaceSpecificationParam{} | ||
|
||
// Check whether node has any type params at all | ||
if typeSpec == nil || typeSpec.TypeParams == nil { | ||
return nil | ||
} | ||
|
||
// If node has any type params - store them in slice and return as a spec | ||
for _, param := range typeSpec.TypeParams.List { | ||
names := []string{} | ||
for _, name := range param.Names { | ||
names = append(names, name.Name) | ||
} | ||
|
||
paramType := "" | ||
|
||
ast.Print(token.NewFileSet(), param.Type) | ||
|
||
switch node := param.Type.(type) { | ||
// Direct declarations in form of | ||
// [T int] or [T any] | ||
case *ast.Ident: | ||
paramType = node.Name | ||
// Reference to a type, i.e. | ||
// proto.Message | ||
|
||
// we can reference those without worrying about external imports | ||
// due to Go tooling being able to deduce missing imports from | ||
// the surrounding context (files and existing references to types). | ||
// i.e. user already referenced the type in the nearby file. | ||
case *ast.SelectorExpr: | ||
paramType = node.X.(*ast.Ident).Name + "." + node.Sel.Name | ||
// Inline reference, i.e. | ||
// int | float64 | ||
case *ast.BinaryExpr: | ||
paramType = node.X.(*ast.Ident).Name + " | " + node.Y.(*ast.Ident).Name | ||
} | ||
|
||
params = append(params, InterfaceSpecificationParam{ | ||
ParamNames: names, | ||
ParamType: paramType, | ||
}) | ||
} | ||
|
||
return params | ||
} |
Oops, something went wrong.