5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 01:43:15 +08:00

[v3] Improved parser for bound structs

This commit is contained in:
Lea Anthony 2023-02-17 20:57:31 +11:00
parent bd184cab85
commit e1279a054f
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
7 changed files with 1247 additions and 510 deletions

View File

@ -5,7 +5,6 @@ import (
"log" "log"
"github.com/wailsapp/wails/v3/examples/binding/services" "github.com/wailsapp/wails/v3/examples/binding/services"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/application"
) )

View File

@ -1,95 +1,96 @@
package parser package parser
import ( //
"bytes" //import (
"fmt" // "bytes"
"go/ast" // "fmt"
"go/types" // "go/ast"
"sort" // "go/types"
"strings" // "sort"
"unicode" // "strings"
) // "unicode"
//)
func GenerateModels(context *Context) ([]byte, error) { //
var buf bytes.Buffer //func GenerateModels(context *Context) ([]byte, error) {
var pkgs []Package // var buf bytes.Buffer
specs := context.GetBoundStructs() // var pkgs []Package
for pkg, pkgSpecs := range specs { // specs := context.GetBoundStructs()
pkgs = append(pkgs, Package{Name: pkg, Specs: pkgSpecs}) // for pkg, pkgSpecs := range specs {
} // pkgs = append(pkgs, Package{Name: pkg, Specs: pkgSpecs})
knownStructs := newAllModels(specs) // }
sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Name < pkgs[j].Name }) // knownStructs := newAllModels(specs)
for _, pkg := range pkgs { // sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Name < pkgs[j].Name })
if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil { // for _, pkg := range pkgs {
return nil, err // if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil {
} // return nil, err
sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name }) // }
for _, spec := range pkg.Specs { // sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name })
if structType, ok := spec.Type.(*ast.StructType); ok { // for _, spec := range pkg.Specs {
if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil { // if structType, ok := spec.Type.(*ast.StructType); ok {
return nil, err // if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil {
} // return nil, err
// }
for _, field := range structType.Fields.List { //
// for _, field := range structType.Fields.List {
// Ignore field names that have a lower case first letter //
if !unicode.IsUpper(rune(field.Names[0].Name[0])) { // // Ignore field names that have a lower case first letter
continue // if !unicode.IsUpper(rune(field.Names[0].Name[0])) {
} // continue
// }
// Get the Go type of the field //
goType := types.ExprString(field.Type) // // Get the Go type of the field
// Check if the type is an array // goType := types.ExprString(field.Type)
if arrayType, ok := field.Type.(*ast.ArrayType); ok { // // Check if the type is an array
// Get the element type of the array // if arrayType, ok := field.Type.(*ast.ArrayType); ok {
elementType := types.ExprString(arrayType.Elt) // // Get the element type of the array
// Look up the corresponding TypeScript type // elementType := types.ExprString(arrayType.Elt)
tsType, ok := goToTS[elementType] // // Look up the corresponding TypeScript type
if !ok { // tsType, ok := goToTS[elementType]
// strip off the * prefix if it is there // if !ok {
if strings.HasPrefix(elementType, "*") { // // strip off the * prefix if it is there
elementType = elementType[1:] // if strings.HasPrefix(elementType, "*") {
} // elementType = elementType[1:]
if knownStructs.exists(elementType) { // }
tsType = elementType // if knownStructs.exists(elementType) {
} else { // tsType = elementType
tsType = "any" // } else {
} // tsType = "any"
} // }
// Output the field as an array of the corresponding TypeScript type // }
if _, err := fmt.Fprintf(&buf, " %s: %s[];\n", field.Names[0].Name, tsType); err != nil { // // Output the field as an array of the corresponding TypeScript type
return nil, err // if _, err := fmt.Fprintf(&buf, " %s: %s[];\n", field.Names[0].Name, tsType); err != nil {
} // return nil, err
} else { // }
// strip off the * prefix if it is there // } else {
if strings.HasPrefix(goType, "*") { // // strip off the * prefix if it is there
goType = goType[1:] // if strings.HasPrefix(goType, "*") {
} // goType = goType[1:]
// Look up the corresponding TypeScript type // }
tsType, ok := goToTS[goType] // // Look up the corresponding TypeScript type
if !ok { // tsType, ok := goToTS[goType]
if knownStructs.exists(goType) { // if !ok {
tsType = goType // if knownStructs.exists(goType) {
} else { // tsType = goType
tsType = "any" // } else {
} // tsType = "any"
} // }
// Output the field as the corresponding TypeScript type // }
if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil { // // Output the field as the corresponding TypeScript type
return nil, err // if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil {
} // return nil, err
} // }
} // }
// }
if _, err := fmt.Fprintf(&buf, " }\n"); err != nil { //
return nil, err // if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
} // return nil, err
} // }
} // }
// }
if _, err := fmt.Fprintf(&buf, "}\n\n"); err != nil { //
return nil, err // if _, err := fmt.Fprintf(&buf, "}\n\n"); err != nil {
} // return nil, err
} // }
return buf.Bytes(), nil // }
} // return buf.Bytes(), nil
//}

View File

@ -3,14 +3,14 @@ package parser
import ( import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/build"
"go/parser" "go/parser"
"go/token" "go/token"
"os" "os"
"strings" "path/filepath"
"strconv"
"github.com/samber/lo" "github.com/samber/lo"
"golang.org/x/tools/go/packages"
) )
var Debug = false var Debug = false
@ -21,24 +21,68 @@ func debug(msg string, args ...interface{}) {
} }
} }
type BoundStruct struct {
Name string
Methods map[string]*FuncSignature
Comments []string
}
type parsedPackage struct { type parsedPackage struct {
name string name string
pkg *ast.Package pkg *ast.Package
boundStructs map[string]*ast.TypeSpec boundStructs map[string]*BoundStruct
} }
type Context struct { type Context struct {
packages map[string]*parsedPackage packages map[string]*parsedPackage
dir string
} }
func (c *Context) GetBoundStructs() map[string][]*ast.TypeSpec { func (c *Context) findImportPackage(pkgName string, pkg *ast.Package) (*ast.Package, error) {
structs := make(map[string][]*ast.TypeSpec) for _, file := range pkg.Files {
for _, pkg := range c.packages { for _, imp := range file.Imports {
for _, structType := range pkg.boundStructs { path, err := strconv.Unquote(imp.Path.Value)
structs[pkg.name] = append(structs[pkg.name], structType) if err != nil {
return nil, err
}
if imp.Name != nil && imp.Name.Name == pkgName {
return c.getPackageFromPath(path)
} else {
_, pkgName := filepath.Split(path)
if pkgName == pkgName {
return c.getPackageFromPath(path)
} }
} }
return structs }
}
return nil, fmt.Errorf("package '%s' not found in %s", pkgName, pkg.Name)
}
func (c *Context) getPackageFromPath(path string) (*ast.Package, error) {
dir, err := filepath.Abs(c.dir)
if err != nil {
return nil, err
}
if !filepath.IsAbs(path) {
dir = filepath.Join(dir, path)
} else {
impPkgDir, err := build.Import(path, dir, build.ImportMode(0))
if err != nil {
return nil, err
}
dir = impPkgDir.Dir
}
impPkg, err := parser.ParseDir(token.NewFileSet(), dir, nil, parser.AllErrors)
if err != nil {
return nil, err
}
for impName, impPkg := range impPkg {
if impName == "main" {
continue
}
return impPkg, nil
}
return nil, fmt.Errorf("Package not found in imported package %s", path)
} }
func ParseDirectory(dir string) (*Context, error) { func ParseDirectory(dir string) (*Context, error) {
@ -52,12 +96,13 @@ func ParseDirectory(dir string) (*Context, error) {
dir = cwd dir = cwd
} }
println("Parsing directory " + dir) println("Parsing directory " + dir)
pkgs, err := parser.ParseDir(fset, dir, nil, parser.AllErrors) pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments)
if err != nil { if err != nil {
return nil, err return nil, err
} }
context := &Context{ context := &Context{
dir: dir,
packages: make(map[string]*parsedPackage), packages: make(map[string]*parsedPackage),
} }
@ -66,16 +111,46 @@ func ParseDirectory(dir string) (*Context, error) {
context.packages[pkg.Name] = &parsedPackage{ context.packages[pkg.Name] = &parsedPackage{
name: pkg.Name, name: pkg.Name,
pkg: pkg, pkg: pkg,
boundStructs: make(map[string]*ast.TypeSpec), boundStructs: make(map[string]*BoundStruct),
} }
} }
findApplicationNewCalls(context) findApplicationNewCalls(context)
err = findStructDefinitions(context)
if err != nil {
return nil, err
}
return context, nil return context, nil
} }
func findStructDefinitions(context *Context) error {
// iterate over the packages
for _, pkg := range context.packages {
// iterate the struct names
for structName, _ := range pkg.boundStructs {
structSpec, methods, comments := getStructTypeSpec(pkg.pkg, structName)
if structSpec == nil {
return fmt.Errorf("unable to find struct %s in package %s", structName, pkg.name)
}
pkg.boundStructs[structName] = &BoundStruct{
Name: structName,
Comments: comments,
}
if pkg.boundStructs[structName].Methods == nil {
pkg.boundStructs[structName].Methods = make(map[string]*FuncSignature)
}
for _, method := range methods {
pkg.boundStructs[structName].Methods[method.Name] = FuncTypeToSignature(method.Type)
pkg.boundStructs[structName].Methods[method.Name].Comments = method.Comments
}
}
}
return nil
}
func findApplicationNewCalls(context *Context) { func findApplicationNewCalls(context *Context) {
println("Finding application.New calls")
// Iterate through the packages // Iterate through the packages
currentPackages := lo.Keys(context.packages) currentPackages := lo.Keys(context.packages)
@ -120,10 +195,10 @@ func findApplicationNewCalls(context *Context) {
if !ok { if !ok {
return true return true
} }
if selectorExpr.Sel.Name != "Application" { if selectorExpr.Sel.Name != "Options" {
return true return true
} }
if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "options" { if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "application" {
return true return true
} }
@ -169,15 +244,16 @@ func findApplicationNewCalls(context *Context) {
ident, ok := boundStructLit.Type.(*ast.Ident) ident, ok := boundStructLit.Type.(*ast.Ident)
if ok { if ok {
if ident.Obj == nil { if ident.Obj == nil {
structTypeSpec := findStructInPackage(thisPackage.pkg, ident.Name) thisPackage.boundStructs[ident.Name] = &BoundStruct{
thisPackage.boundStructs[ident.Name] = structTypeSpec Name: ident.Name,
findNestedStructs(structTypeSpec, file, packageName, context) }
continue continue
} }
// Check if the ident is a struct type // Check if the ident is a struct type
if t, ok := ident.Obj.Decl.(*ast.TypeSpec); ok { if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
thisPackage.boundStructs[ident.Name] = t thisPackage.boundStructs[ident.Name] = &BoundStruct{
findNestedStructs(t, file, packageName, context) Name: ident.Name,
}
continue continue
} }
// Check the typespec decl is a struct // Check the typespec decl is a struct
@ -189,343 +265,194 @@ func findApplicationNewCalls(context *Context) {
// Check if the lit is a selector // Check if the lit is a selector
selector, ok := boundStructLit.Type.(*ast.SelectorExpr) selector, ok := boundStructLit.Type.(*ast.SelectorExpr)
if ok { if ok {
getStructsFromSelector(selector, file, context) // Check if the selector is an ident
continue if ident, ok := selector.X.(*ast.Ident); ok {
} // Check if the ident is a package
} if _, ok := context.packages[ident.Name]; !ok {
} externalPackage, err := context.getPackageFromPath(ident.Name)
}
return true
})
}
}
}
func getStructsFromSelector(selector *ast.SelectorExpr, file *ast.File, context *Context) {
debug("getStructsFromSelector called with selector '%s' on file '%s.go'", selector.Sel.Name, file.Name.Name)
// extract package name from selector
packageName := selector.X.(*ast.Ident).Name
if context.packages[packageName] == nil {
context.packages[packageName] = &parsedPackage{
name: packageName,
boundStructs: make(map[string]*ast.TypeSpec),
}
}
// extract struct name from selector
structName := selector.Sel.Name
// Find the package name from the imports
for _, imp := range file.Imports {
var match bool
if imp.Name == nil || imp.Name.Name == packageName {
match = true
}
if match == false {
pathSplit := strings.Split(imp.Path.Value, "/")
endPath := strings.Trim(pathSplit[len(pathSplit)-1], `"`)
match = endPath == packageName
}
if match {
// We have the import
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedModule,
}
pkgs, err := packages.Load(cfg, strings.Trim(imp.Path.Value, `"`))
if err != nil { if err != nil {
panic(err) println("Error getting package from path: " + err.Error())
return true
}
context.packages[ident.Name] = &parsedPackage{
name: ident.Name,
pkg: externalPackage,
boundStructs: make(map[string]*BoundStruct),
}
}
context.packages[ident.Name].boundStructs[selector.Sel.Name] = &BoundStruct{
Name: selector.Sel.Name,
}
}
continue
}
}
}
} }
foundPackage := pkgs[0]
// Iterate the files in the package and find struct types
for _, parsedFile := range foundPackage.Syntax {
ast.Inspect(parsedFile, func(n ast.Node) bool {
if n == nil {
return false
}
switch n.(type) {
case *ast.TypeSpec:
typeSpec := n.(*ast.TypeSpec)
if typeSpec.Name.Name == structName {
if _, ok := context.packages[packageName].boundStructs[structName]; !ok {
debug("Adding struct '%s' in package '%s'", structName, packageName)
context.packages[packageName].boundStructs[typeSpec.Name.Name] = typeSpec
findNestedStructs(typeSpec, parsedFile, packageName, context)
}
return false
}
}
return true return true
}) })
} }
continue
}
}
}
func findNestedStructs(t *ast.TypeSpec, parsedFile *ast.File, pkgName string, context *Context) {
debug("findNestedStructs called with type '%s' on file '%s.go'", t.Name.Name, parsedFile.Name.Name)
structType, ok := t.Type.(*ast.StructType)
if !ok {
return
}
for _, field := range structType.Fields.List {
for _, ident := range field.Names {
switch t := ident.Obj.Decl.(*ast.Field).Type.(type) {
case *ast.Ident:
if t.Obj == nil {
continue
}
if t.Obj.Kind == ast.Typ {
if _, ok := t.Obj.Decl.(*ast.TypeSpec); ok {
if _, ok := context.packages[pkgName].boundStructs[t.Name]; !ok {
debug("Adding nested struct '%s' to package '%s'", t.Name, pkgName)
context.packages[pkgName].boundStructs[t.Name] = t.Obj.Decl.(*ast.TypeSpec)
findNestedStructs(t.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
}
}
}
case *ast.SelectorExpr:
if ident, ok := t.X.(*ast.Ident); ok {
if ident.IsExported() {
getStructsFromSelector(t, parsedFile, context)
}
}
case *ast.StarExpr:
if sel, ok := t.X.(*ast.SelectorExpr); ok {
if _, ok := sel.X.(*ast.Ident); ok {
if ident.IsExported() {
getStructsFromSelector(sel, parsedFile, context)
}
}
}
}
}
}
findStructsInMethods(t.Name.Name, parsedFile, pkgName, context)
}
func findStructsInMethods(name string, parsedFile *ast.File, pkgName string, context *Context) {
debug("findStructsInMethods called with type '%s' on file '%s.go'", name, parsedFile.Name.Name)
// Find the struct declaration for the given name
var structDecl *ast.TypeSpec
for _, decl := range parsedFile.Decls {
if fn, ok := decl.(*ast.FuncDecl); ok {
// check the receiver name is the same as the name given
if fn.Recv == nil {
continue
}
// Check if the receiver is a pointer
if starExpr, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok {
if ident, ok := starExpr.X.(*ast.Ident); ok {
if ident.Name != name {
continue
}
}
} else {
if ident, ok := fn.Recv.List[0].Type.(*ast.Ident); ok {
if ident.Name != name {
continue
}
}
}
findStructsInMethodParams(fn, parsedFile, pkgName, context)
}
}
if structDecl == nil {
return
}
// Iterate the methods in the struct
}
func findStructsInMethodParams(f *ast.FuncDecl, parsedFile *ast.File, pkgName string, context *Context) {
debug("findStructsInMethodParams called with type '%s' on file '%s.go'", f.Name.Name, parsedFile.Name.Name)
if f.Type.Params == nil {
for _, field := range f.Type.Params.List {
parseField(field, parsedFile, pkgName, context)
}
}
if f.Type.Results != nil {
for _, field := range f.Type.Results.List {
parseField(field, parsedFile, pkgName, context)
}
} }
} }
func parseField(field *ast.Field, parsedFile *ast.File, pkgName string, context *Context) { //type Method struct {
if se, ok := field.Type.(*ast.StarExpr); ok { // Name string
// Check if the star expr is a struct // Type *ast.FuncType
if selExp, ok := se.X.(*ast.SelectorExpr); ok {
getStructsFromSelector(selExp, parsedFile, context)
return
}
if ident, ok := se.X.(*ast.Ident); ok {
if ident.Obj == nil {
return
}
if ident.Obj.Kind == ast.Typ {
if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
if _, ok := context.packages[pkgName].boundStructs[ident.Name]; !ok {
debug("Adding field struct '%s' to package '%s'", ident.Name, pkgName)
context.packages[pkgName].boundStructs[ident.Name] = ident.Obj.Decl.(*ast.TypeSpec)
findNestedStructs(ident.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
} else {
debug("Struct %s already bound", ident.Name)
}
}
}
}
}
if selExp, ok := field.Type.(*ast.SelectorExpr); ok {
getStructsFromSelector(selExp, parsedFile, context)
return
}
if ident, ok := field.Type.(*ast.Ident); ok {
if ident.Obj == nil {
return
}
if ident.Obj.Kind == ast.Typ {
if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
if _, ok := context.packages[pkgName].boundStructs[ident.Name]; !ok {
debug("Adding field struct '%s' to package '%s'", ident.Name, pkgName)
context.packages[pkgName].boundStructs[ident.Name] = ident.Obj.Decl.(*ast.TypeSpec)
findNestedStructs(ident.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
} else {
debug("Struct %s already bound", ident.Name)
}
}
}
}
}
func findStructInPackage(pkg *ast.Package, name string) *ast.TypeSpec {
for _, file := range pkg.Files {
for _, decl := range file.Decls {
if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.TYPE {
for _, spec := range gen.Specs {
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
if typeSpec.Name.Name == name {
if _, ok := typeSpec.Type.(*ast.StructType); ok {
return typeSpec
}
}
}
}
}
}
}
return nil
}
type Package struct {
Name string
Specs []*ast.TypeSpec
}
var goToTS = map[string]string{
"int": "number",
"int8": "number",
"int16": "number",
"int32": "number",
"int64": "number",
"uint": "number",
"uint8": "number",
"uint16": "number",
"uint32": "number",
"uint64": "number",
"float32": "number",
"float64": "number",
"string": "string",
"bool": "boolean",
}
//func GenerateModels(specs map[string][]*ast.TypeSpec) ([]byte, error) {
// var buf bytes.Buffer
// var packages []Package
// for pkg, pkgSpecs := range specs {
// packages = append(packages, Package{Name: pkg, Specs: pkgSpecs})
// }
// sort.Slice(packages, func(i, j int) bool { return packages[i].Name < packages[j].Name })
// for _, pkg := range packages {
// if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil {
// return nil, err
// }
// sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name })
// for _, spec := range pkg.Specs {
// if structType, ok := spec.Type.(*ast.StructType); ok {
// if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil {
// return nil, err
// }
//
// for _, field := range structType.Fields.List {
//
// // Get the Go type of the field
// goType := types.ExprString(field.Type)
// // Look up the corresponding TypeScript type
// tsType, ok := goToTS[goType]
// if !ok {
// tsType = goType
// }
//
// if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil {
// return nil, err
// }
// }
//
// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
// return nil, err
// }
// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
// return nil, err
// }
// }
// }
//
// if _, err := fmt.Fprintf(&buf, "}\n"); err != nil {
// return nil, err
// }
// }
// return buf.Bytes(), nil
//} //}
type allModels struct { //func getStructTypeSpec(pkg *ast.Package, structName string) (*ast.TypeSpec, []Method) {
known map[string]map[string]struct{} // var typeSpec *ast.TypeSpec
// var methods []Method
//
// // Iterate over all files in the package
// for _, file := range pkg.Files {
// // Iterate over all declarations in the file
// for _, decl := range file.Decls {
// // Check if the declaration is a type declaration
// if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE {
// // Iterate over all type specifications in the type declaration
// for _, spec := range genDecl.Specs {
// // Check if the type specification is a struct type specification
// if tSpec, ok := spec.(*ast.TypeSpec); ok && tSpec.Name.Name == structName {
// // Check if the type specification is a struct type
// if _, ok := tSpec.Type.(*ast.StructType); ok {
// typeSpec = tSpec
// }
// }
// }
// } else if funcDecl, ok := decl.(*ast.FuncDecl); ok && funcDecl.Recv != nil {
// // Check if the function has a receiver argument of the struct type
// recvType, ok := funcDecl.Recv.List[0].Type.(*ast.StarExpr)
// if ok {
// if ident, ok := recvType.X.(*ast.Ident); ok && ident.Name == structName {
// // Add the method to the list of methods
// method := Method{
// Name: funcDecl.Name.Name,
// Type: funcDecl.Type,
// }
// methods = append(methods, method)
// }
// }
// }
// }
// }
//
// return typeSpec, methods
//}
type Arg struct {
Name string
Type string
} }
func newAllModels(models map[string][]*ast.TypeSpec) *allModels { type FuncSignature struct {
result := &allModels{known: make(map[string]map[string]struct{})} Comments []string
// iterate over all models Inputs []Arg
for pkg, pkgSpecs := range models { Outputs []Arg
for _, spec := range pkgSpecs {
result.known[pkg] = make(map[string]struct{})
result.known[pkg][spec.Name.Name] = struct{}{}
}
}
return result
} }
func (k *allModels) exists(name string) bool { func FuncTypeToSignature(ft *ast.FuncType) *FuncSignature {
// Split the name into package and type sig := &FuncSignature{}
parts := strings.Split(name, ".")
typ := parts[0] // process input arguments
pkg := "main" if ft.Params != nil {
if len(parts) == 2 { for _, field := range ft.Params.List {
pkg = parts[0] arg := Arg{}
typ = parts[1] for _, name := range field.Names {
arg.Name = name.Name
}
arg.Type = tokenToString(field.Type)
sig.Inputs = append(sig.Inputs, arg)
}
} }
knownPkg, ok := k.known[pkg] // process output arguments
if !ok { if ft.Results != nil {
return false for _, field := range ft.Results.List {
arg := Arg{}
arg.Type = tokenToString(field.Type)
sig.Outputs = append(sig.Outputs, arg)
} }
_, ok = knownPkg[typ] }
return ok
return sig
}
func tokenToString(t ast.Expr) string {
switch t := t.(type) {
case *ast.Ident:
return t.Name
case *ast.StarExpr:
return "*" + tokenToString(t.X)
case *ast.SelectorExpr:
return tokenToString(t.X) + "." + t.Sel.Name
case *ast.ArrayType:
return "[]" + tokenToString(t.Elt)
case *ast.StructType:
return "struct"
default:
return ""
}
}
type Method struct {
Name string
Type *ast.FuncType
Comments []string // Add a field to capture comments for the method
}
func getStructTypeSpec(pkg *ast.Package, structName string) (*ast.TypeSpec, []Method, []string) {
var typeSpec *ast.TypeSpec
var methods []Method
var structComments []string
// Iterate over all files in the package
for _, file := range pkg.Files {
// Iterate over all declarations in the file
for _, decl := range file.Decls {
// Check if the declaration is a type declaration
if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE {
// Iterate over all type specifications in the type declaration
for _, spec := range genDecl.Specs {
// Check if the type specification is a struct type specification
if tSpec, ok := spec.(*ast.TypeSpec); ok && tSpec.Name.Name == structName {
// Check if the type specification is a struct type
if _, ok := tSpec.Type.(*ast.StructType); ok {
// Get comments associated with the struct
if genDecl.Doc != nil {
for _, comment := range genDecl.Doc.List {
structComments = append(structComments, comment.Text)
}
}
typeSpec = tSpec
}
}
}
} else if funcDecl, ok := decl.(*ast.FuncDecl); ok && funcDecl.Recv != nil {
// Check if the function has a receiver argument of the struct type
recvType, ok := funcDecl.Recv.List[0].Type.(*ast.StarExpr)
if ok {
if ident, ok := recvType.X.(*ast.Ident); ok && ident.Name == structName {
// Get comments associated with the method
if funcDecl.Doc != nil {
var comments []string
for _, comment := range funcDecl.Doc.List {
comments = append(comments, comment.Text)
}
// Add the method to the list of methods
method := Method{
Name: funcDecl.Name.Name,
Type: funcDecl.Type,
Comments: comments,
}
methods = append(methods, method)
}
}
}
}
}
}
return typeSpec, methods, structComments
} }

View File

@ -0,0 +1,758 @@
package parser
//
//import (
// "fmt"
// "go/ast"
// "go/build"
// "go/parser"
// "go/token"
// "os"
// "path/filepath"
// "strconv"
// "strings"
//
// "github.com/samber/lo"
//)
//
//var Debug = false
//
//func debug(msg string, args ...interface{}) {
// if Debug {
// println(fmt.Sprintf(msg, args...))
// }
//}
//
//type BoundStruct struct {
// Name string
// MethodDecls []Method
//}
//
//type parsedPackage struct {
// name string
// pkg *ast.Package
// boundStructs map[string]*BoundStruct
// boundStructMethods map[string][]Method
//}
//
//type Context struct {
// packages map[string]*parsedPackage
// dir string
//}
//
//func (c *Context) findImportPackage(pkgName string, pkg *ast.Package) (*ast.Package, error) {
// for _, file := range pkg.Files {
// for _, imp := range file.Imports {
// path, err := strconv.Unquote(imp.Path.Value)
// if err != nil {
// return nil, err
// }
// if imp.Name != nil && imp.Name.Name == pkgName {
// return c.getPackageFromPath(path)
// } else {
// _, pkgName := filepath.Split(path)
// if pkgName == pkgName {
// return c.getPackageFromPath(path)
// }
// }
// }
// }
// return nil, fmt.Errorf("Package %s not found in %s", pkgName, pkg.Name)
//}
//
//func (c *Context) getPackageFromPath(path string) (*ast.Package, error) {
// dir, err := filepath.Abs(c.dir)
// if err != nil {
// return nil, err
// }
// if !filepath.IsAbs(path) {
// dir = filepath.Join(dir, path)
// } else {
// impPkgDir, err := build.Import(path, dir, build.ImportMode(0))
// if err != nil {
// return nil, err
// }
// dir = impPkgDir.Dir
// }
// impPkg, err := parser.ParseDir(token.NewFileSet(), dir, nil, parser.AllErrors)
// if err != nil {
// return nil, err
// }
// for impName, impPkg := range impPkg {
// if impName == "main" {
// continue
// }
// return impPkg, nil
// }
// return nil, fmt.Errorf("Package not found in imported package %s", path)
//}
//
//func ParseDirectory(dir string) (*Context, error) {
// // Parse the directory
// fset := token.NewFileSet()
// if dir == "." || dir == "" {
// cwd, err := os.Getwd()
// if err != nil {
// return nil, err
// }
// dir = cwd
// }
// println("Parsing directory " + dir)
// pkgs, err := parser.ParseDir(fset, dir, nil, parser.AllErrors)
// if err != nil {
// return nil, err
// }
//
// context := &Context{
// dir: dir,
// packages: make(map[string]*parsedPackage),
// }
//
// // Iterate through the packages
// for _, pkg := range pkgs {
// context.packages[pkg.Name] = &parsedPackage{
// name: pkg.Name,
// pkg: pkg,
// boundStructs: make(map[string]*BoundStruct),
// boundStructMethods: make(map[string][]Method),
// }
// }
//
// findApplicationNewCalls(context)
// err = findStructDefinitions(context)
// if err != nil {
// return nil, err
// }
//
// return context, nil
//}
//
//func findStructDefinitions(context *Context) error {
// // iterate over the packages
// for _, pkg := range context.packages {
// // iterate the struct names
// for structName, _ := range pkg.boundStructs {
// structSpec, methods := getStructTypeSpec(pkg.pkg, structName)
// if structSpec == nil {
// return fmt.Errorf("unable to find struct %s in package %s", structName, pkg.name)
// }
// pkg.boundStructs[structName] = &BoundStruct{
// Name: structName,
// MethodDecls: methods,
// }
// }
// }
// return nil
//}
//
//func findApplicationNewCalls(context *Context) {
// println("Finding application.New calls")
// // Iterate through the packages
// currentPackages := lo.Keys(context.packages)
//
// for _, packageName := range currentPackages {
// thisPackage := context.packages[packageName]
// debug("Parsing package: %s", packageName)
// // Iterate through the package's files
// for _, file := range thisPackage.pkg.Files {
// // Use an ast.Inspector to find the calls to application.New
// ast.Inspect(file, func(n ast.Node) bool {
// // Check if the node is a call expression
// callExpr, ok := n.(*ast.CallExpr)
// if !ok {
// return true
// }
//
// // Check if the function being called is "application.New"
// selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
// if !ok {
// return true
// }
// if selExpr.Sel.Name != "New" {
// return true
// }
// if id, ok := selExpr.X.(*ast.Ident); !ok || id.Name != "application" {
// return true
// }
//
// // Check there is only 1 argument
// if len(callExpr.Args) != 1 {
// return true
// }
//
// // Check argument 1 is a struct literal
// structLit, ok := callExpr.Args[0].(*ast.CompositeLit)
// if !ok {
// return true
// }
//
// // Check struct literal is of type "application.Options"
// selectorExpr, ok := structLit.Type.(*ast.SelectorExpr)
// if !ok {
// return true
// }
// if selectorExpr.Sel.Name != "Options" {
// return true
// }
// if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "application" {
// return true
// }
//
// for _, elt := range structLit.Elts {
// // Find the "Bind" field
// kvExpr, ok := elt.(*ast.KeyValueExpr)
// if !ok {
// continue
// }
// if id, ok := kvExpr.Key.(*ast.Ident); !ok || id.Name != "Bind" {
// continue
// }
// // Check the value is a slice of interfaces
// sliceExpr, ok := kvExpr.Value.(*ast.CompositeLit)
// if !ok {
// continue
// }
// var arrayType *ast.ArrayType
// if arrayType, ok = sliceExpr.Type.(*ast.ArrayType); !ok {
// continue
// }
//
// // Check array type is of type "interface{}"
// if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok {
// continue
// }
// // Iterate through the slice elements
// for _, elt := range sliceExpr.Elts {
// // Check the element is a unary expression
// unaryExpr, ok := elt.(*ast.UnaryExpr)
// if ok {
// // Check the unary expression is a composite lit
// boundStructLit, ok := unaryExpr.X.(*ast.CompositeLit)
// if !ok {
// continue
// }
// // Check if the composite lit is a struct
// if _, ok := boundStructLit.Type.(*ast.StructType); ok {
// // Parse struct
// continue
// }
// // Check if the lit is an ident
// ident, ok := boundStructLit.Type.(*ast.Ident)
// if ok {
// if ident.Obj == nil {
// thisPackage.boundStructs[ident.Name] = &BoundStruct{
// Name: ident.Name,
// }
// continue
// }
// // Check if the ident is a struct type
// if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
// thisPackage.boundStructs[ident.Name] = &BoundStruct{
// Name: ident.Name,
// }
// continue
// }
// // Check the typespec decl is a struct
// if _, ok := ident.Obj.Decl.(*ast.StructType); ok {
// continue
// }
//
// }
// // Check if the lit is a selector
// selector, ok := boundStructLit.Type.(*ast.SelectorExpr)
// if ok {
// // Check if the selector is an ident
// if ident, ok := selector.X.(*ast.Ident); ok {
// // Check if the ident is a package
// if _, ok := context.packages[ident.Name]; !ok {
// externalPackage, err := context.getPackageFromPath(ident.Name)
// if err != nil {
// println("Error getting package from path: " + err.Error())
// return true
// }
// context.packages[ident.Name] = &parsedPackage{
// name: ident.Name,
// pkg: externalPackage,
// boundStructs: make(map[string]*BoundStruct),
// }
// }
// context.packages[ident.Name].boundStructs[selector.Sel.Name] = &BoundStruct{
// Name: selector.Sel.Name,
// }
// }
// continue
// }
// }
// }
// }
//
// return true
// })
// }
// }
//}
//
//type Method struct {
// Name string
// Type *ast.FuncType
//}
//
//
////func findApplicationNewCalls(context *Context) {
//// println("Finding application.New calls")
//// // Iterate through the packages
//// currentPackages := lo.Keys(context.packages)
////
//// for _, packageName := range currentPackages {
//// thisPackage := context.packages[packageName]
//// debug("Parsing package: %s", packageName)
//// // Iterate through the package's files
//// for _, file := range thisPackage.pkg.Files {
//// // Use an ast.Inspector to find the calls to application.New
//// ast.Inspect(file, func(n ast.Node) bool {
//// // Check if the node is a call expression
//// callExpr, ok := n.(*ast.CallExpr)
//// if !ok {
//// return true
//// }
////
//// // Check if the function being called is "application.New"
//// selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
//// if !ok {
//// return true
//// }
//// if selExpr.Sel.Name != "New" {
//// return true
//// }
//// if id, ok := selExpr.X.(*ast.Ident); !ok || id.Name != "application" {
//// return true
//// }
////
//// // Check there is only 1 argument
//// if len(callExpr.Args) != 1 {
//// return true
//// }
////
//// // Check argument 1 is a struct literal
//// structLit, ok := callExpr.Args[0].(*ast.CompositeLit)
//// if !ok {
//// return true
//// }
////
//// // Check struct literal is of type "application.Options"
//// selectorExpr, ok := structLit.Type.(*ast.SelectorExpr)
//// if !ok {
//// return true
//// }
//// if selectorExpr.Sel.Name != "Application" {
//// return true
//// }
//// if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "options" {
//// return true
//// }
////
//// for _, elt := range structLit.Elts {
//// // Find the "Bind" field
//// kvExpr, ok := elt.(*ast.KeyValueExpr)
//// if !ok {
//// continue
//// }
//// if id, ok := kvExpr.Key.(*ast.Ident); !ok || id.Name != "Bind" {
//// continue
//// }
//// // Check the value is a slice of interfaces
//// sliceExpr, ok := kvExpr.Value.(*ast.CompositeLit)
//// if !ok {
//// continue
//// }
//// var arrayType *ast.ArrayType
//// if arrayType, ok = sliceExpr.Type.(*ast.ArrayType); !ok {
//// continue
//// }
////
//// // Check array type is of type "interface{}"
//// if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok {
//// continue
//// }
//// // Iterate through the slice elements
//// for _, elt := range sliceExpr.Elts {
//// // Check the element is a unary expression
//// unaryExpr, ok := elt.(*ast.UnaryExpr)
//// if ok {
//// // Check the unary expression is a composite lit
//// boundStructLit, ok := unaryExpr.X.(*ast.CompositeLit)
//// if !ok {
//// continue
//// }
//// // Check if the composite lit is a struct
//// if _, ok := boundStructLit.Type.(*ast.StructType); ok {
//// // Parse struct
//// continue
//// }
//// // Check if the lit is an ident
//// ident, ok := boundStructLit.Type.(*ast.Ident)
//// if ok {
//// if ident.Obj == nil {
//// structTypeSpec := findStructInPackage(thisPackage.pkg, ident.Name)
//// thisPackage.boundStructs[ident.Name] = structTypeSpec
//// findNestedStructs(structTypeSpec, file, packageName, context)
//// continue
//// }
//// // Check if the ident is a struct type
//// if t, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
//// thisPackage.boundStructs[ident.Name] = t
//// findNestedStructs(t, file, packageName, context)
//// continue
//// }
//// // Check the typespec decl is a struct
//// if _, ok := ident.Obj.Decl.(*ast.StructType); ok {
//// continue
//// }
////
//// }
//// // Check if the lit is a selector
//// selector, ok := boundStructLit.Type.(*ast.SelectorExpr)
//// if ok {
//// getStructsFromSelector(selector, file, context)
//// continue
//// }
//// }
//// }
//// }
////
//// return true
//// })
//// }
//// }
////}
//
////func getStructsFromSelector(selector *ast.SelectorExpr, file *ast.File, context *Context) {
//// debug("getStructsFromSelector called with selector '%s' on file '%s.go'", selector.Sel.Name, file.Name.Name)
////
//// // extract package name from selector
//// packageName := selector.X.(*ast.Ident).Name
////
//// if context.packages[packageName] == nil {
//// context.packages[packageName] = &parsedPackage{
//// name: packageName,
//// boundStructs: make(map[string]*BoundStruct),
//// }
//// }
////
//// // extract struct name from selector
//// structName := selector.Sel.Name
////
//// // Find the package name from the imports
//// for _, imp := range file.Imports {
//// var match bool
//// if imp.Name == nil || imp.Name.Name == packageName {
//// match = true
//// }
//// if match == false {
//// pathSplit := strings.Split(imp.Path.Value, "/")
//// endPath := strings.Trim(pathSplit[len(pathSplit)-1], `"`)
//// match = endPath == packageName
//// }
////
//// if match {
//// // We have the import
//// cfg := &packages.Config{
//// Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedModule,
//// }
//// pkgs, err := packages.Load(cfg, strings.Trim(imp.Path.Value, `"`))
//// if err != nil {
//// panic(err)
//// }
//// foundPackage := pkgs[0]
////
//// // Iterate the files in the package and find struct types
//// for _, parsedFile := range foundPackage.Syntax {
//// ast.Inspect(parsedFile, func(n ast.Node) bool {
//// if n == nil {
//// return false
//// }
//// switch n.(type) {
//// case *ast.TypeSpec:
//// typeSpec := n.(*ast.TypeSpec)
//// if typeSpec.Name.Name == structName {
//// if _, ok := context.packages[packageName].boundStructs[structName]; !ok {
//// debug("Adding struct '%s' in package '%s'", structName, packageName)
//// context.packages[packageName].boundStructs[typeSpec.Name.Name] = &BoundStruct{
//// Name: typeSpec.Name.Name,
//// }
//// findNestedStructs(typeSpec, parsedFile, packageName, context)
//// }
//// return false
//// }
//// }
//// return true
//// })
//// }
////
//// continue
//// }
//// }
////
////}
////
////func findNestedStructs(t *ast.TypeSpec, parsedFile *ast.File, pkgName string, context *Context) {
//// debug("findNestedStructs called with type '%s' on file '%s.go'", t.Name.Name, parsedFile.Name.Name)
//// structType, ok := t.Type.(*ast.StructType)
//// if !ok {
//// return
//// }
//// for _, field := range structType.Fields.List {
//// for _, ident := range field.Names {
//// switch t := ident.Obj.Decl.(*ast.Field).Type.(type) {
//// case *ast.Ident:
//// if t.Obj == nil {
//// continue
//// }
//// if t.Obj.Kind == ast.Typ {
//// if _, ok := t.Obj.Decl.(*ast.TypeSpec); ok {
//// if _, ok := context.packages[pkgName].boundStructs[t.Name]; !ok {
//// debug("Adding nested struct '%s' to package '%s'", t.Name, pkgName)
//// context.packages[pkgName].boundStructs[t.Name] = t.Obj.Decl.(*ast.TypeSpec)
//// findNestedStructs(t.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
//// }
//// }
//// }
//// case *ast.SelectorExpr:
//// if ident, ok := t.X.(*ast.Ident); ok {
//// if ident.IsExported() {
//// getStructsFromSelector(t, parsedFile, context)
//// }
//// }
//// case *ast.StarExpr:
//// if sel, ok := t.X.(*ast.SelectorExpr); ok {
//// if _, ok := sel.X.(*ast.Ident); ok {
//// if ident.IsExported() {
//// getStructsFromSelector(sel, parsedFile, context)
//// }
//// }
//// }
//// }
//// }
//// }
//// findStructsInMethods(t.Name.Name, parsedFile, pkgName, context)
////
////}
////
////func findStructsInMethods(name string, parsedFile *ast.File, pkgName string, context *Context) {
//// debug("findStructsInMethods called with type '%s' on file '%s.go'", name, parsedFile.Name.Name)
//// // Find the struct declaration for the given name
//// var structDecl *ast.TypeSpec
//// for _, decl := range parsedFile.Decls {
//// if fn, ok := decl.(*ast.FuncDecl); ok {
//// // check the receiver name is the same as the name given
//// if fn.Recv == nil {
//// continue
//// }
//// // Check if the receiver is a pointer
//// if starExpr, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok {
//// if ident, ok := starExpr.X.(*ast.Ident); ok {
//// if ident.Name != name {
//// continue
//// }
//// }
//// } else {
//// if ident, ok := fn.Recv.List[0].Type.(*ast.Ident); ok {
//// if ident.Name != name {
//// continue
//// }
//// }
//// }
//// findStructsInMethodParams(fn, parsedFile, pkgName, context)
//// }
//// }
//// if structDecl == nil {
//// return
//// }
//// // Iterate the methods in the struct
////
////}
////
////func findStructsInMethodParams(f *ast.FuncDecl, parsedFile *ast.File, pkgName string, context *Context) {
//// debug("findStructsInMethodParams called with type '%s' on file '%s.go'", f.Name.Name, parsedFile.Name.Name)
//// if f.Type.Params == nil {
//// for _, field := range f.Type.Params.List {
//// parseField(field, parsedFile, pkgName, context)
//// }
//// }
//// if f.Type.Results != nil {
//// for _, field := range f.Type.Results.List {
//// parseField(field, parsedFile, pkgName, context)
//// }
//// }
////}
//
////func parseField(field *ast.Field, parsedFile *ast.File, pkgName string, context *Context) {
//// if se, ok := field.Type.(*ast.StarExpr); ok {
//// // Check if the star expr is a struct
//// if selExp, ok := se.X.(*ast.SelectorExpr); ok {
//// getStructsFromSelector(selExp, parsedFile, context)
//// return
//// }
//// if ident, ok := se.X.(*ast.Ident); ok {
//// if ident.Obj == nil {
//// return
//// }
//// if ident.Obj.Kind == ast.Typ {
//// if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
//// if _, ok := context.packages[pkgName].boundStructs[ident.Name]; !ok {
//// debug("Adding field struct '%s' to package '%s'", ident.Name, pkgName)
//// context.packages[pkgName].boundStructs[ident.Name] = ident.Obj.Decl.(*ast.TypeSpec)
//// findNestedStructs(ident.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
//// } else {
//// debug("Struct %s already bound", ident.Name)
//// }
//// }
//// }
//// }
//// }
//// if selExp, ok := field.Type.(*ast.SelectorExpr); ok {
//// getStructsFromSelector(selExp, parsedFile, context)
//// return
//// }
//// if ident, ok := field.Type.(*ast.Ident); ok {
//// if ident.Obj == nil {
//// return
//// }
//// if ident.Obj.Kind == ast.Typ {
//// if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
//// if _, ok := context.packages[pkgName].boundStructs[ident.Name]; !ok {
//// debug("Adding field struct '%s' to package '%s'", ident.Name, pkgName)
//// context.packages[pkgName].boundStructs[ident.Name] = ident.Obj.Decl.(*ast.TypeSpec)
//// findNestedStructs(ident.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
//// } else {
//// debug("Struct %s already bound", ident.Name)
//// }
//// }
//// }
//// }
////}
////
////func findStructInPackage(pkg *ast.Package, name string) *ast.TypeSpec {
//// for _, file := range pkg.Files {
//// for _, decl := range file.Decls {
//// if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.TYPE {
//// for _, spec := range gen.Specs {
//// if typeSpec, ok := spec.(*ast.TypeSpec); ok {
//// if typeSpec.Name.Name == name {
//// if _, ok := typeSpec.Type.(*ast.StructType); ok {
//// return typeSpec
//// }
//// }
//// }
//// }
//// }
//// }
//// }
//// return nil
////}
////
////type Package struct {
//// Name string
//// Specs []*ast.TypeSpec
////}
////
////var goToTS = map[string]string{
//// "int": "number",
//// "int8": "number",
//// "int16": "number",
//// "int32": "number",
//// "int64": "number",
//// "uint": "number",
//// "uint8": "number",
//// "uint16": "number",
//// "uint32": "number",
//// "uint64": "number",
//// "float32": "number",
//// "float64": "number",
//// "string": "string",
//// "bool": "boolean",
////}
//
////func GenerateModels(specs map[string][]*ast.TypeSpec) ([]byte, error) {
//// var buf bytes.Buffer
//// var packages []Package
//// for pkg, pkgSpecs := range specs {
//// packages = append(packages, Package{Name: pkg, Specs: pkgSpecs})
//// }
//// sort.Slice(packages, func(i, j int) bool { return packages[i].Name < packages[j].Name })
//// for _, pkg := range packages {
//// if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil {
//// return nil, err
//// }
//// sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name })
//// for _, spec := range pkg.Specs {
//// if structType, ok := spec.Type.(*ast.StructType); ok {
//// if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil {
//// return nil, err
//// }
////
//// for _, field := range structType.Fields.List {
////
//// // Get the Go type of the field
//// goType := types.ExprString(field.Type)
//// // Look up the corresponding TypeScript type
//// tsType, ok := goToTS[goType]
//// if !ok {
//// tsType = goType
//// }
////
//// if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil {
//// return nil, err
//// }
//// }
////
//// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
//// return nil, err
//// }
//// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
//// return nil, err
//// }
//// }
//// }
////
//// if _, err := fmt.Fprintf(&buf, "}\n"); err != nil {
//// return nil, err
//// }
//// }
//// return buf.Bytes(), nil
////}
//
//type allModels struct {
// known map[string]map[string]struct{}
//}
//
//func newAllModels(models map[string][]*ast.TypeSpec) *allModels {
// result := &allModels{known: make(map[string]map[string]struct{})}
// // iterate over all models
// for pkg, pkgSpecs := range models {
// for _, spec := range pkgSpecs {
// result.known[pkg] = make(map[string]struct{})
// result.known[pkg][spec.Name.Name] = struct{}{}
// }
// }
// return result
//}
//
//func (k *allModels) exists(name string) bool {
// // Split the name into package and type
// parts := strings.Split(name, ".")
// typ := parts[0]
// pkg := "main"
// if len(parts) == 2 {
// pkg = parts[0]
// typ = parts[1]
// }
//
// knownPkg, ok := k.known[pkg]
// if !ok {
// return false
// }
// _, ok = knownPkg[typ]
// return ok
//}

View File

@ -34,9 +34,9 @@ func TestParseDirectory(t *testing.T) {
wantErr: false, wantErr: false,
}, },
{ {
name: "should find bound services from other packages", name: "should find multiple bound services over multiple packages",
dir: "../../examples/binding", dir: "testdata/struct_literal_multiple_other",
want: []string{"main.localStruct", "services.GreetService", "models.Person"}, want: []string{"main.GreetService", "services.OtherService"},
wantErr: false, wantErr: false,
}, },
} }
@ -50,7 +50,8 @@ func TestParseDirectory(t *testing.T) {
} }
for name, pkg := range got.packages { for name, pkg := range got.packages {
for structName := range pkg.boundStructs { for structName, structType := range pkg.boundStructs {
require.NotNil(t, structType)
require.True(t, lo.Contains(tt.want, name+"."+structName)) require.True(t, lo.Contains(tt.want, name+"."+structName))
tt.want = lo.Without(tt.want, name+"."+structName) tt.want = lo.Without(tt.want, name+"."+structName)
} }
@ -58,87 +59,88 @@ func TestParseDirectory(t *testing.T) {
require.Empty(t, tt.want) require.Empty(t, tt.want)
}) })
} }
} }
func TestGenerateTypeScript(t *testing.T) { //func TestGenerateTypeScript(t *testing.T) {
tests := []struct { // tests := []struct {
name string // name string
dir string // dir string
want string // want string
wantErr bool // wantErr bool
}{ // }{
{ // {
name: "should find single bound service", // name: "should find single bound service",
dir: "testdata/struct_literal_single", // dir: "testdata/struct_literal_single",
want: `namespace main { // want: `namespace main {
class GreetService { // class GreetService {
SomeVariable: number; // SomeVariable: number;
} // }
} //}
`, //`,
wantErr: false, // wantErr: false,
}, // },
{ // {
name: "should find multiple bound services", // name: "should find multiple bound services",
dir: "testdata/struct_literal_multiple", // dir: "testdata/struct_literal_multiple",
want: `namespace main { // want: `namespace main {
class GreetService { // class GreetService {
SomeVariable: number; // SomeVariable: number;
} // }
class OtherService { // class OtherService {
} // }
} //}
`, //`,
wantErr: false, // wantErr: false,
}, // },
{ // {
name: "should find multiple bound services over multiple files", // name: "should find multiple bound services over multiple files",
dir: "testdata/struct_literal_multiple_files", // dir: "testdata/struct_literal_multiple_files",
want: `namespace main { // want: `namespace main {
class GreetService { // class GreetService {
SomeVariable: number; // SomeVariable: number;
} // }
class OtherService { // class OtherService {
} // }
} //}
`, //`,
wantErr: false, // wantErr: false,
}, // },
{ // {
name: "should find bound services from other packages", // name: "should find bound services from other packages",
dir: "../../examples/binding", // dir: "../../examples/binding",
want: `namespace main { // want: `namespace main {
class localStruct { // class localStruct {
} // }
} //}
namespace models { //namespace models {
class Person { // class Person {
Name: string; // Name: string;
} // }
} //}
namespace services { //namespace services {
class GreetService { // class GreetService {
SomeVariable: number; // SomeVariable: number;
Parent: models.Person; // Parent: models.Person;
} // }
} //}
`, //`,
wantErr: false, // wantErr: false,
}, // },
} // }
for _, tt := range tests { // for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { // t.Run(tt.name, func(t *testing.T) {
Debug = true // Debug = true
context, err := ParseDirectory(tt.dir) // context, err := ParseDirectory(tt.dir)
if (err != nil) != tt.wantErr { // if (err != nil) != tt.wantErr {
t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr) // t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr)
return // return
} // }
//
ts, err := GenerateModels(context) // ts, err := GenerateModels(context)
require.NoError(t, err) // require.NoError(t, err)
require.Equal(t, tt.want, string(ts)) // require.Equal(t, tt.want, string(ts))
//
}) // })
} // }
} //}

View File

@ -0,0 +1,38 @@
package main
import (
_ "embed"
"log"
"github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services"
"github.com/wailsapp/wails/v3/pkg/application"
)
// GreetService is great
type GreetService struct {
SomeVariable int
lowerCase string
}
// Greet does XYZ
func (*GreetService) Greet(name string) string {
return "Hello " + name
}
func main() {
app := application.New(application.Options{
Bind: []interface{}{
&GreetService{},
&services.OtherService{},
},
})
app.NewWebviewWindow()
err := app.Run()
if err != nil {
log.Fatal(err)
}
}

View File

@ -0,0 +1,12 @@
package services
// OtherService is a struct
// that does things
type OtherService struct {
t int
}
// Yay does this and that
func (o *OtherService) Yay() {
}