Skip to content

Commit

Permalink
Gencommon updates for a gwrap impl (in stately) (#23)
Browse files Browse the repository at this point in the history
### Background

→ go wrap failed me :(

### Changes

- my own version of go wrap :(

### Testing

- in stately
  • Loading branch information
drshriveer authored Oct 17, 2023
1 parent 54c5e51 commit 20787b6
Show file tree
Hide file tree
Showing 15 changed files with 486 additions and 72 deletions.
74 changes: 65 additions & 9 deletions gencommon/comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,73 @@ package gencommon

import (
"go/ast"
"strings"

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

// CommentGroupRaw returns the comment group as a single string
// but without manipulation.
func CommentGroupRaw(cg *ast.CommentGroup) string {
result := ""
for i, comment := range cg.List {
result += comment.Text
if i < len(cg.List)-1 {
result += "\n"
// Comments is a line-by-line representation of comments.
type Comments []string

// CommentsFromMethod extracts comments of a method with type and method name.
func CommentsFromMethod(pkg *packages.Package, typeName string, methodName string) Comments {
for _, stax := range pkg.Syntax {
obj := stax.Scope.Lookup(typeName)
if obj != nil {
v, ok := obj.Decl.(*ast.InterfaceType)
if !ok {
if ts, tsok := obj.Decl.(*ast.TypeSpec); tsok {
v, ok = ts.Type.(*ast.InterfaceType)
}
}
// if this is an interface type extracting comments is easier.
if ok {
for _, mInfo := range v.Methods.List {
if getName(mInfo.Names...) == methodName {
return FromCommentGroup(mInfo.Doc)
}
}
}
}

// if this is a struct type we first have to find functions with receivers of the correct type.
for _, decl := range stax.Decls {
if fDecl, ok := decl.(*ast.FuncDecl); ok && methodIdent(fDecl, typeName) {
if getName(fDecl.Name) == methodName {
return FromCommentGroup(fDecl.Doc)
}
}
}
}

return nil
}

// CommentsFromObj attempts to find comments associated with the typename provided in
// the package provided.
func CommentsFromObj(pkg *packages.Package, typeName string) Comments {
for _, stax := range pkg.Syntax {
obj := stax.Scope.Lookup(typeName)
if obj == nil {
continue
}

if decl, ok := obj.Decl.(*ast.TypeSpec); ok {
return FromCommentGroup(decl.Doc)
}
}
return result
return nil
}

// FromCommentGroup converts a CommentGroup into Comments!
func FromCommentGroup(group *ast.CommentGroup) Comments {
if group == nil {
return nil
}
return mapper(group.List, func(in *ast.Comment) string { return in.Text })
}

// String returns a single string of the comment block.
func (c Comments) String() string {
return strings.Join(c, "\n")
}
79 changes: 50 additions & 29 deletions gencommon/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ type ImportDesc struct {
inUse bool
}

// ImportString returns a formatted import string with alias (if required).
func (id *ImportDesc) ImportString() string {
if strings.HasSuffix(id.PkgPath, id.Alias) {
return "\"" + id.PkgPath + "\""
}
return id.Alias + " \"" + id.PkgPath + "\""
}

// ImportHandler a collection of import descriptions indexed by package path.
type ImportHandler struct {
PInfo *packages.Package
Expand Down Expand Up @@ -52,42 +60,55 @@ func calcImports(pkg *packages.Package, fAST *ast.File) *ImportHandler {
}

// ExtractTypeRef returns the way the type should be referenced in code.
func (id ImportHandler) ExtractTypeRef(t types.Type) string {
func (ih *ImportHandler) ExtractTypeRef(typ types.Type) string {
// "named" means it is a type which may require importing.
named, ok := t.(*types.Named)
if !ok {
// "*types.Basic"s e.g. string come out as "untyped string"; we need to drop
// that part... Not sure why this is how the type information is conveyed :-/.
return strings.TrimPrefix(t.String(), "untyped ")
}
switch t := typ.(type) {
case *types.Pointer:
return "*" + ih.ExtractTypeRef(t.Elem())
case *types.Slice:
return "[]" + ih.ExtractTypeRef(t.Elem())
case *types.Array:
return fmt.Sprintf("[%d]", t.Len()) + ih.ExtractTypeRef(t.Elem())
case *types.Signature:
// recurse to register relevant method imports-> then we only need the signature.
m := MethodFromSignature(ih, t)
return m.Signature()

pkg := named.Obj().Pkg()
typeName := named.Obj().Name()
if pkg.Path() == id.PInfo.PkgPath {
return typeName
}
case *types.Named:
pkg := t.Obj().Pkg()
typeName := t.Obj().Name()
if pkg == nil || pkg.Path() == ih.PInfo.PkgPath {
return typeName
}

// first check if we have a mapping for the package:
i, ok := id.imports[pkg.Path()]
if ok {
i.inUse = true
} else {
i = &ImportDesc{
Alias: pkg.Name(),
PkgPath: pkg.Path(),
inUse: true,
// first check if we have a mapping for the package:
i, ok := ih.imports[pkg.Path()]
if ok {
i.inUse = true
} else {
i = &ImportDesc{
Alias: pkg.Name(),
PkgPath: pkg.Path(),
inUse: true,
}
ih.imports[i.PkgPath] = i
}
id.imports[i.PkgPath] = i
}

return fmt.Sprintf("%s.%s", i.Alias, typeName)
return fmt.Sprintf("%s.%s", i.Alias, typeName)

default:
// *types.Interface is usually handled here too.
// "*types.Basic"s e.g. string come out as "untyped string"; we need to drop
// that part... Not sure why this is how the type information is conveyed :-/.
return strings.TrimPrefix(t.String(), "untyped ")
}
}

// GetActive returns ordered, active imports.
// Used by templates.
func (id ImportHandler) GetActive() []ImportDesc {
result := make([]ImportDesc, 0, len(id.imports))
for _, i := range id.imports {
func (ih *ImportHandler) GetActive() []ImportDesc {
result := make([]ImportDesc, 0, len(ih.imports))
for _, i := range ih.imports {
if i.inUse {
result = append(result, *i)
}
Expand All @@ -99,6 +120,6 @@ func (id ImportHandler) GetActive() []ImportDesc {
}

// HasActiveImports returns true if there are any active imports.
func (id ImportHandler) HasActiveImports() bool {
return len(id.GetActive()) > 0
func (ih *ImportHandler) HasActiveImports() bool {
return len(ih.GetActive()) > 0
}
112 changes: 94 additions & 18 deletions gencommon/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,119 @@ package gencommon

import (
"fmt"
"go/ast"
"go/types"

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

// ErrorInterface defines the error interface as a type for comparison.
var ErrorInterface = types.NewInterfaceType([]*types.Func{
types.NewFunc(
0,
nil,
"Error",
types.NewSignatureType(nil, nil, nil, nil,
types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.String])),
false)),
}, nil)

// Interface is a parsed interface.
type Interface struct {
// IsInterface returns false if the actual underlying object is a struct rather than an interface.
IsInterface bool

// Comments related to the interface.
Comments Comments

// Name of the type (or interface).
Name string

// List of methods!
Methods Methods
}

// FindInterface locates a given *ast.Interface in a package.
func FindInterface(pkgs []*packages.Package, pkgName, target string) (*ast.InterfaceType, error) {
func FindInterface(
ih *ImportHandler,
pkgs []*packages.Package,
pkgName, target string,
includePrivate bool,
) (*Interface, error) {
for _, pkg := range pkgs {
if pkg.PkgPath == pkgName {
return findIfaceByNameInPackage(pkg, target)
return findIFaceByNameInPackage(ih, pkg, target, includePrivate)
}
// I don't really see why this should be necessary...
for pkgPath, pkg := range pkg.Imports {
if pkgPath == pkgName {
return findIfaceByNameInPackage(pkg, target)
return findIFaceByNameInPackage(ih, pkg, target, includePrivate)
}
}

}
return nil, fmt.Errorf("target %s in package %s not found", target, pkgName)
}

func findIfaceByNameInPackage(pkg *packages.Package, target string) (*ast.InterfaceType, error) {
for _, file := range pkg.Syntax {
tt := file.Scope.Lookup(target)
if tt == nil {
continue
}
ts, ok := tt.Decl.(*ast.TypeSpec)
if !ok {
continue
func findIFaceByNameInPackage(ih *ImportHandler, pkg *packages.Package, target string, includePrivate bool) (
*Interface,
error,
) {
typ := pkg.Types.Scope().Lookup(target)
if typ == nil {
return nil, fmt.Errorf("target %s not found", target)
}
typLayer1, ok := typ.(*types.TypeName)
if !ok {
return nil, fmt.Errorf("target %s found but not a handled type (found %T)", target, typ)
}
typLayer2, ok := typLayer1.Type().(*types.Named)
if !ok {
return nil, fmt.Errorf("target %s found but not a handled nested type (found %T)", target, typLayer1)
}

return namedTypeToInterface(ih, pkg, typLayer2, includePrivate)
}

func namedTypeToInterface(ih *ImportHandler, pkg *packages.Package, t *types.Named, includePrivate bool) (
*Interface,
error,
) {
type hasMethods interface {
NumMethods() int
Method(i int) *types.Func
}

var methodz hasMethods = t
if methodz.NumMethods() == 0 {
if iface, ok := t.Underlying().(*types.Interface); ok {
methodz = iface
}
iface, ok := ts.Type.(*ast.InterfaceType)
if !ok {
continue
}

result := &Interface{
Name: t.Obj().Name(),
IsInterface: false,
Comments: CommentsFromObj(pkg, t.Obj().Name()),
Methods: make(Methods, 0, t.NumMethods()),
}

for i := 0; i < methodz.NumMethods(); i++ {
mInfo := methodz.Method(i)
if includePrivate || mInfo.Exported() {
method := MethodFromSignature(ih, mInfo.Type().(*types.Signature))
method.Name = mInfo.Name()
method.IsExported = mInfo.Exported()
method.Comments = CommentsFromMethod(pkg, t.Obj().Name(), mInfo.Name())
result.Methods = append(result.Methods, method)
}
return iface, nil
}
return result, nil
}

return nil, fmt.Errorf("target %s not found", target)
func mapper[Tin any, Tout any](input []Tin, mapFn func(in Tin) Tout) []Tout {
result := make([]Tout, len(input))
for i, val := range input {
result[i] = mapFn(val)
}
return result
}
Loading

0 comments on commit 20787b6

Please sign in to comment.