diff --git a/v3/go.mod b/v3/go.mod index 1e3660579..66450173a 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/go-task/task/v3 v3.20.0 + github.com/google/go-cmp v0.5.9 github.com/jackmordaunt/icns/v2 v2.2.1 github.com/json-iterator/go v1.1.12 github.com/leaanthony/clir v1.6.0 @@ -12,17 +13,14 @@ require ( github.com/matryer/is v1.4.0 github.com/pterm/pterm v0.12.51 github.com/samber/lo v1.37.0 - github.com/stretchr/testify v1.8.1 github.com/tc-hib/winres v0.1.6 github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 - golang.org/x/tools v0.1.12 ) require ( atomicgo.dev/cursor v0.1.1 // indirect atomicgo.dev/keyboard v0.2.8 // indirect github.com/containerd/console v1.0.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/gookit/color v1.5.2 // indirect @@ -39,7 +37,6 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/radovskyb/watcher v1.0.7 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/sajari/fuzzy v1.0.0 // indirect @@ -47,7 +44,6 @@ require ( github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.3.0 // indirect diff --git a/v3/go.sum b/v3/go.sum index 5f1d31e05..ad5466828 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -26,6 +26,7 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/go-task/task/v3 v3.20.0 h1:pTavuhP+AiEpKLzh5I6Lja9Ux7ypYO5QMsEPTbhYEDc= github.com/go-task/task/v3 v3.20.0/go.mod h1:y7rWakbLR5gFElGgo6rA2dyr6vU/zNIDVfn3S4Of6OI= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= @@ -113,7 +114,6 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -122,7 +122,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tc-hib/winres v0.1.6 h1:qgsYHze+BxQPEYilxIz/KCQGaClvI2+yLBAZs+3+0B8= github.com/tc-hib/winres v0.1.6/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= @@ -136,8 +135,6 @@ golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= @@ -172,8 +169,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go index 98f2c131f..f4b93ed72 100644 --- a/v3/internal/parser/parser.go +++ b/v3/internal/parser/parser.go @@ -3,89 +3,76 @@ package parser import ( "fmt" "go/ast" - "go/build" "go/parser" "go/token" "os" - "path/filepath" - "strconv" - - "github.com/samber/lo" ) -var Debug = false +var packageCache = make(map[string]*ParsedPackage) -func debug(msg string, args ...interface{}) { - if Debug { - println(fmt.Sprintf(msg, args...)) - } +type packageName = string +type structName = string + +type Parameter struct { + Name string + Type string + IsStruct bool + IsSlice bool + IsPointer bool } -type BoundStruct struct { +type BoundMethod struct { + Name string + DocComment string + Inputs []*Parameter + Outputs []*Parameter +} + +type Model struct { + Name string + Fields []*Field +} + +type Field struct { Name string - Methods map[string]*FuncSignature - Comments []string + Type string + IsStruct bool + IsSlice bool } -type parsedPackage struct { - name string - pkg *ast.Package - boundStructs map[string]*BoundStruct +type ParsedPackage struct { + Pkg *ast.Package } -type Context struct { - packages map[string]*parsedPackage - dir string +type Project struct { + Path string + BoundMethods map[packageName]map[structName][]*BoundMethod + Models map[packageName]map[structName]*Model } -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) - } - } - } +func ParseProject(projectPath string) (*Project, error) { + result := &Project{ + BoundMethods: make(map[packageName]map[structName][]*BoundMethod), + Models: make(map[packageName]map[structName]*Model), } - 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) + pkgs, err := result.parseDirectory(projectPath) 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) + println("Parsed " + projectPath) + err = result.findApplicationNewCalls(pkgs) 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) + return result, nil } -func ParseDirectory(dir string) (*Context, error) { +func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) { + println("Parsing directory " + dir) + if packageCache[dir] != nil { + println("Found directory in cache!") + return map[string]*ParsedPackage{dir: packageCache[dir]}, nil + } // Parse the directory fset := token.NewFileSet() if dir == "." || dir == "" { @@ -95,70 +82,28 @@ func ParseDirectory(dir string) (*Context, error) { } dir = cwd } - println("Parsing directory " + dir) pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments) if err != nil { return nil, err } - - context := &Context{ - dir: dir, - packages: make(map[string]*parsedPackage), + var result = make(map[string]*ParsedPackage) + for packageName, pkg := range pkgs { + parsedPackage := &ParsedPackage{Pkg: pkg} + packageCache[dir] = parsedPackage + result[packageName] = parsedPackage } - - // Iterate through the packages - for _, pkg := range pkgs { - context.packages[pkg.Name] = &parsedPackage{ - name: pkg.Name, - pkg: pkg, - boundStructs: make(map[string]*BoundStruct), - } - } - - findApplicationNewCalls(context) - err = findStructDefinitions(context) - if err != nil { - return nil, err - } - - return context, nil + return result, 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 (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err error) { -func findApplicationNewCalls(context *Context) { - println("Finding application.New calls") - // Iterate through the packages - currentPackages := lo.Keys(context.packages) + var callFound bool - for _, packageName := range currentPackages { - thisPackage := context.packages[packageName] - debug("Parsing package: %s", packageName) + for packageName, pkg := range pkgs { + thisPackage := pkg.Pkg + println(" - Looking in package: " + packageName) // Iterate through the package's files - for _, file := range thisPackage.pkg.Files { + for _, file := range thisPackage.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 @@ -225,6 +170,7 @@ func findApplicationNewCalls(context *Context) { if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok { continue } + callFound = true // Iterate through the slice elements for _, elt := range sliceExpr.Elts { // Check the element is a unary expression @@ -243,46 +189,59 @@ func findApplicationNewCalls(context *Context) { // 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 + err = p.parseBoundStructMethods(ident.Name, thisPackage) + if err != nil { + return true } + 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 - } + //if typeSpec, ok := ident.Obj.Decl.(*ast.TypeSpec); ok { + // var parsedStruct *StructDefinition + // parsedStruct, err = p.parseStruct(typeSpec, thisPackage) + // if err != nil { + // return true + // } + // p.addModel(thisPackage.Name, parsedStruct) + // p.addBoundStruct(thisPackage.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 - } + //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, - } + if _, 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, + //} + //p.parseStructFromExternalPackage(selector.Sel.Name, ident.Name, thisPackage) + //p.addBoundStruct(ident.Name, selector.Sel.Name) + continue } continue } @@ -293,166 +252,121 @@ func findApplicationNewCalls(context *Context) { return true }) } - } -} - -//type Method struct { -// Name string -// Type *ast.FuncType -//} - -//func getStructTypeSpec(pkg *ast.Package, structName string) (*ast.TypeSpec, []Method) { -// 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 -} - -type FuncSignature struct { - Comments []string - Inputs []Arg - Outputs []Arg -} - -func FuncTypeToSignature(ft *ast.FuncType) *FuncSignature { - sig := &FuncSignature{} - - // process input arguments - if ft.Params != nil { - for _, field := range ft.Params.List { - arg := Arg{} - for _, name := range field.Names { - arg.Name = name.Name - } - arg.Type = tokenToString(field.Type) - sig.Inputs = append(sig.Inputs, arg) + if !callFound { + return fmt.Errorf("no Bound structs found") } } + return nil +} - // process output arguments - if ft.Results != nil { - for _, field := range ft.Results.List { - arg := Arg{} - arg.Type = tokenToString(field.Type) - sig.Outputs = append(sig.Outputs, arg) - } +func (p *Project) addBoundMethods(packageName string, name string, boundMethods []*BoundMethod) { + _, ok := p.BoundMethods[packageName] + if !ok { + p.BoundMethods[packageName] = make(map[structName][]*BoundMethod) } - - return sig + p.BoundMethods[packageName][name] = boundMethods } -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 - +func (p *Project) parseBoundStructMethods(name string, pkg *ast.Package) error { + var methods []*BoundMethod // 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 { + 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) + if ident, ok := recvType.X.(*ast.Ident); ok && ident.Name == name { + // Add the method to the list of methods + method := &BoundMethod{ + Name: funcDecl.Name.Name, + DocComment: funcDecl.Doc.Text(), + Inputs: make([]*Parameter, 0), + Outputs: make([]*Parameter, 0), } + + method.Inputs = p.parseParameters(funcDecl.Type.Params) + method.Outputs = p.parseParameters(funcDecl.Type.Results) + + methods = append(methods, method) } } } } } - - return typeSpec, methods, structComments + p.addBoundMethods(pkg.Name, name, methods) + return nil +} + +func (p *Project) parseParameters(params *ast.FieldList) []*Parameter { + var result []*Parameter + for _, field := range params.List { + var theseFields []*Parameter + if len(field.Names) > 0 { + for _, name := range field.Names { + theseFields = append(theseFields, &Parameter{ + Name: name.Name, + }) + } + } else { + theseFields = append(theseFields, &Parameter{ + Name: "", + }) + } + // loop over fields + for _, thisField := range theseFields { + thisField.Type = getTypeString(field.Type) + switch t := field.Type.(type) { + case *ast.StarExpr: + thisField.IsStruct = isStructType(t.X) + thisField.IsPointer = true + case *ast.StructType: + thisField.IsStruct = true + case *ast.ArrayType: + thisField.IsSlice = true + thisField.IsStruct = isStructType(t.Elt) + case *ast.MapType: + thisField.IsSlice = true + thisField.IsStruct = isStructType(t.Value) + } + result = append(result, thisField) + } + } + return result +} + +func getTypeString(expr ast.Expr) string { + switch t := expr.(type) { + case *ast.Ident: + return t.Name + case *ast.StarExpr: + return getTypeString(t.X) + case *ast.ArrayType: + return getTypeString(t.Elt) + //case *ast.MapType: + // return "map[" + getTypeString(t.Key) + "]" + getTypeString(t.Value) + default: + return "any" + } +} + +func isStructType(expr ast.Expr) bool { + switch e := expr.(type) { + case *ast.StructType: + return true + case *ast.StarExpr: + return isStructType(e.X) + case *ast.SelectorExpr: + return isStructType(e.Sel) + case *ast.ArrayType: + return isStructType(e.Elt) + case *ast.SliceExpr: + return isStructType(e.X) + case *ast.Ident: + return e.Obj != nil && e.Obj.Kind == ast.Typ + default: + return false + } } diff --git a/v3/internal/parser/parser_test.go b/v3/internal/parser/parser_test.go index 6eead63b4..e5b6d9398 100644 --- a/v3/internal/parser/parser_test.go +++ b/v3/internal/parser/parser_test.go @@ -3,60 +3,215 @@ package parser import ( "testing" - "github.com/samber/lo" - - "github.com/stretchr/testify/require" + "github.com/google/go-cmp/cmp" ) func TestParseDirectory(t *testing.T) { tests := []struct { - name string - dir string - want []string - wantErr bool + name string + dir string + wantBoundMethods map[string]map[string][]*BoundMethod + wantErr bool }{ { - name: "should find single bound service", - dir: "testdata/struct_literal_single", - want: []string{"main.GreetService"}, - wantErr: false, - }, - { - name: "should find multiple bound services", - dir: "testdata/struct_literal_multiple", - want: []string{"main.GreetService", "main.OtherService"}, - wantErr: false, - }, - { - name: "should find multiple bound services over multiple files", - dir: "testdata/struct_literal_multiple_files", - want: []string{"main.GreetService", "main.OtherService"}, - wantErr: false, - }, - { - name: "should find multiple bound services over multiple packages", - dir: "testdata/struct_literal_multiple_other", - want: []string{"main.GreetService", "services.OtherService"}, + name: "should find single bound service", + dir: "testdata/struct_literal_single", + //wantModels: []string{"main.GreetService"}, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: "string", + IsStruct: false, + IsSlice: false, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: "string", + IsStruct: false, + IsSlice: false, + }, + }, + }, + { + Name: "NoInputsStringOut", + DocComment: "", + Inputs: nil, + Outputs: []*Parameter{ + { + Name: "", + Type: "string", + IsStruct: false, + IsSlice: false, + }, + }, + }, + { + Name: "StringArrayInputStringOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: "string", + IsSlice: true, + }, + }, + Outputs: []*Parameter{ + { + Type: "string", + }, + }, + }, + { + Name: "StringArrayInputStringArrayOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: "string", + IsSlice: true, + }, + }, + Outputs: []*Parameter{ + { + Type: "string", + IsSlice: true, + }, + }, + }, + { + Name: "StringArrayInputNamedOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: "string", + IsSlice: true, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: "string", + IsSlice: true, + }, + }, + }, + { + Name: "StringArrayInputNamedOutputs", + Inputs: []*Parameter{ + { + Name: "in", + Type: "string", + IsSlice: true, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: "string", + IsSlice: true, + }, + { + Name: "err", + Type: "error", + }, + }, + }, + { + Name: "IntPointerInputNamedOutputs", + Inputs: []*Parameter{ + { + Name: "in", + Type: "int", + IsPointer: true, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: "int", + IsPointer: true, + }, + { + Name: "err", + Type: "error", + }, + }, + }, + { + Name: "StructPointerInputErrorOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: "Person", + IsStruct: true, + IsPointer: true, + }, + }, + Outputs: []*Parameter{ + { + Type: "error", + }, + }, + }, + { + Name: "StructPointerInputStructPointerOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: "Person", + IsStruct: true, + IsPointer: true, + }, + }, + Outputs: []*Parameter{ + { + Type: "Person", + IsPointer: true, + IsStruct: true, + }, + }, + }, + }, + }, + }, wantErr: false, }, + //{ + // name: "should find multiple bound services", + // dir: "testdata/struct_literal_multiple", + // //wantModels: []string{"main.GreetService", "main.OtherService"}, + // wantErr: false, + //}, + //{ + // name: "should find multiple bound services over multiple files", + // dir: "testdata/struct_literal_multiple_files", + // //wantModels: []string{"main.GreetService", "main.OtherService"}, + // wantErr: false, + //}, + //{ + // name: "should find multiple bound services over multiple packages", + // dir: "testdata/struct_literal_multiple_other", + // //wantModels: []string{"main.GreetService", "services.OtherService", "main.Person"}, + // wantErr: false, + //}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - Debug = true - got, err := ParseDirectory(tt.dir) + got, err := ParseProject(tt.dir) if (err != nil) != tt.wantErr { t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr) return } - for name, pkg := range got.packages { - for structName, structType := range pkg.boundStructs { - require.NotNil(t, structType) - require.True(t, lo.Contains(tt.want, name+"."+structName)) - tt.want = lo.Without(tt.want, name+"."+structName) - } + if diff := cmp.Diff(tt.wantBoundMethods, got.BoundMethods); diff != "" { + t.Errorf("ParseDirectory() failed:\n" + diff) } - require.Empty(t, tt.want) }) } @@ -66,13 +221,13 @@ func TestParseDirectory(t *testing.T) { // tests := []struct { // name string // dir string -// want string +// wantModels string // wantErr bool // }{ // { // name: "should find single bound service", // dir: "testdata/struct_literal_single", -// want: `namespace main { +// wantModels: `namespace main { // class GreetService { // SomeVariable: number; // } @@ -83,7 +238,7 @@ func TestParseDirectory(t *testing.T) { // { // name: "should find multiple bound services", // dir: "testdata/struct_literal_multiple", -// want: `namespace main { +// wantModels: `namespace main { // class GreetService { // SomeVariable: number; // } @@ -96,7 +251,7 @@ func TestParseDirectory(t *testing.T) { // { // name: "should find multiple bound services over multiple files", // dir: "testdata/struct_literal_multiple_files", -// want: `namespace main { +// wantModels: `namespace main { // class GreetService { // SomeVariable: number; // } @@ -109,7 +264,7 @@ func TestParseDirectory(t *testing.T) { // { // name: "should find bound services from other packages", // dir: "../../examples/binding", -// want: `namespace main { +// wantModels: `namespace main { // class localStruct { // } //} @@ -139,7 +294,7 @@ func TestParseDirectory(t *testing.T) { // // ts, err := GenerateModels(context) // require.NoError(t, err) -// require.Equal(t, tt.want, string(ts)) +// require.Equal(t, tt.wantModels, string(ts)) // // }) // } diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/main.go b/v3/internal/parser/testdata/struct_literal_multiple_other/main.go index 76cd859a3..cf020662f 100644 --- a/v3/internal/parser/testdata/struct_literal_multiple_other/main.go +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/main.go @@ -12,6 +12,11 @@ import ( type GreetService struct { SomeVariable int lowerCase string + target *Person +} + +type Person struct { + Name string } // Greet does XYZ @@ -19,6 +24,11 @@ func (*GreetService) Greet(name string) string { return "Hello " + name } +// NewPerson creates a new person +func (*GreetService) NewPerson(name string) *Person { + return &Person{Name: name} +} + func main() { app := application.New(application.Options{ Bind: []interface{}{ diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go b/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go index 966061442..76c0c5b00 100644 --- a/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go @@ -7,6 +7,6 @@ type OtherService struct { } // Yay does this and that -func (o *OtherService) Yay() { - +func (o *OtherService) Yay() []int { + return []int{0, 1, 2} } diff --git a/v3/internal/parser/testdata/struct_literal_single/main.go b/v3/internal/parser/testdata/struct_literal_single/main.go index ac7f05f8d..d544638ed 100644 --- a/v3/internal/parser/testdata/struct_literal_single/main.go +++ b/v3/internal/parser/testdata/struct_literal_single/main.go @@ -3,19 +3,58 @@ package main import ( _ "embed" "log" + "strings" "github.com/wailsapp/wails/v3/pkg/application" ) +type Person struct { + Name string +} + +// GreetService is great type GreetService struct { SomeVariable int lowerCase string } +// Greet someone func (*GreetService) Greet(name string) string { return "Hello " + name } +func (*GreetService) NoInputsStringOut() string { + return "Hello" +} + +func (*GreetService) StringArrayInputStringOut(in []string) string { + return strings.Join(in, ",") +} + +func (*GreetService) StringArrayInputStringArrayOut(in []string) []string { + return in +} + +func (*GreetService) StringArrayInputNamedOutput(in []string) (output []string) { + return in +} + +func (*GreetService) StringArrayInputNamedOutputs(in []string) (output []string, err error) { + return in, nil +} + +func (*GreetService) IntPointerInputNamedOutputs(in *int) (output *int, err error) { + return in, nil +} + +func (*GreetService) StructPointerInputErrorOutput(in *Person) error { + return nil +} + +func (*GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { + return in +} + func main() { app := application.New(application.Options{ Bind: []interface{}{