diff --git a/v3/internal/parser/README.md b/v3/internal/parser/README.md index 179bc16f8..33adb9edc 100644 --- a/v3/internal/parser/README.md +++ b/v3/internal/parser/README.md @@ -7,9 +7,17 @@ This package contains the static analyser used for parsing Wails projects so tha ## Implemented -- [ ] Parsing of bound methods +- [ ] Bound types + - [x] Struct Literal Pointer + - [ ] Variable + - [ ] Assignment + - [x] Struct Literal Pointer + - [ ] Function + - [ ] Function + +- [x] Parsing of bound methods - [x] Method names - - [ ] Method parameters + - [x] Method parameters - [x] Zero parameters - [x] Single input parameter - [x] Single output parameter @@ -26,22 +34,22 @@ This package contains the static analyser used for parsing Wails projects so tha - [x] Pointer - [x] bool - [x] Pointer - - [ ] Struct + - [x] Struct - [x] Pointer - - [x] Recursive - [x] Slices - [x] Pointer - [x] Maps - [x] Pointer -- [ ] Model Parsing +- [x] Model Parsing - [x] In same package - - [ ] In different package - - [ ] Multiple named fields, e.g. one,two,three string - - [ ] Scalars - - [ ] Arrays - - [ ] Maps - - [ ] Structs - - [ ] Anonymous structs + - [x] In different package + - [x] Multiple named fields, e.g. one,two,three string + - [x] Scalars + - [x] Arrays + - [x] Maps + - [x] Structs + - [x] Recursive + - [x] Anonymous - [ ] Generation of models - [ ] Scalars - [ ] Arrays @@ -51,7 +59,7 @@ This package contains the static analyser used for parsing Wails projects so tha ## Limitations -There are many ways to write a Go program so there are many different program structures that we would need to support. This is a work in progress and will be improved over time. The current limitations are: +There are many ways to write a Go program so there are many program structures that we would need to support. This is a work in progress and will be improved over time. The current limitations are: - The call to `application.New()` must be in the `main` package - Bound structs must be declared as struct literals diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go index cdcfdce5c..8c78aa052 100644 --- a/v3/internal/parser/parser.go +++ b/v3/internal/parser/parser.go @@ -12,8 +12,6 @@ import ( "strconv" ) -var packageCache = make(map[string]*ParsedPackage) - type packagePath = string type structName = string @@ -59,6 +57,7 @@ type ParsedPackage struct { } type Project struct { + packageCache map[string]*ParsedPackage Path string BoundMethods map[packagePath]map[structName][]*BoundMethod Models map[packagePath]map[structName]*StructDef @@ -68,6 +67,7 @@ type Project struct { func ParseProject(projectPath string) (*Project, error) { result := &Project{ BoundMethods: make(map[packagePath]map[structName][]*BoundMethod), + packageCache: make(map[string]*ParsedPackage), } pkgs, err := result.parseDirectory(projectPath) if err != nil { @@ -78,7 +78,7 @@ func ParseProject(projectPath string) (*Project, error) { if err != nil { return nil, err } - for _, pkg := range packageCache { + for _, pkg := range result.packageCache { if len(pkg.StructCache) > 0 { if result.Models == nil { result.Models = make(map[packagePath]map[structName]*StructDef) @@ -90,8 +90,8 @@ func ParseProject(projectPath string) (*Project, error) { } func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) { - if packageCache[dir] != nil { - return map[string]*ParsedPackage{dir: packageCache[dir]}, nil + if p.packageCache[dir] != nil { + return map[string]*ParsedPackage{dir: p.packageCache[dir]}, nil } // Parse the directory fset := token.NewFileSet() @@ -115,7 +115,7 @@ func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) Dir: getDirectoryForPackage(pkg), StructCache: make(map[structName]*StructDef), } - packageCache[packageName] = parsedPackage + p.packageCache[packageName] = parsedPackage result[packageName] = parsedPackage } return result, nil @@ -201,66 +201,44 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e // 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 { + result, shouldContinue := p.parseBoundUnaryExpression(unaryExpr, pkg) + if shouldContinue { continue } - // Check if the composite lit is a struct - if _, ok := boundStructLit.Type.(*ast.StructType); ok { - // Parse struct + return result + } + // Check the element is a variable + ident, ok := elt.(*ast.Ident) + if ok { + println("found: ", ident.Name) + if ident.Obj == nil { continue } - // Check if the lit is an ident - ident, ok := boundStructLit.Type.(*ast.Ident) - if ok { - err = p.parseBoundStructMethods(ident.Name, pkg) - if err != nil { - return true - } + // Check if the variable is a struct + if _, ok := ident.Obj.Decl.(*ast.StructType); ok { continue - // Check if the ident is a struct type - //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 - //} - } - // Check if the lit is a selector - selector, ok := boundStructLit.Type.(*ast.SelectorExpr) - if ok { - // Check if the selector is an ident - if _, ok := selector.X.(*ast.Ident); ok { - // Look up the package - var parsedPackage *ParsedPackage - parsedPackage, err = p.getParsedPackageFromName(selector.X.(*ast.Ident).Name, pkg) - if err != nil { - return true - } - err = p.parseBoundStructMethods(selector.Sel.Name, parsedPackage) - if err != nil { - return true - } + // Check if the variable is a type + if typeSpec, ok := ident.Obj.Decl.(*ast.TypeSpec); ok { + // Check if the type is a struct + if _, ok := typeSpec.Type.(*ast.StructType); ok { continue } - continue + } + // Check if it's an assignment + if assign, ok := ident.Obj.Decl.(*ast.AssignStmt); ok { + // Check if the assignment is a struct + if _, ok := assign.Rhs[0].(*ast.StructType); ok { + continue + } + if _, ok := assign.Rhs[0].(*ast.UnaryExpr); ok { + result, shouldContinue := p.parseBoundUnaryExpression(assign.Rhs[0].(*ast.UnaryExpr), pkg) + if shouldContinue { + continue + } + return result + } + } } } @@ -276,6 +254,47 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e return nil } +func (p *Project) parseBoundUnaryExpression(unaryExpr *ast.UnaryExpr, pkg *ParsedPackage) (bool, bool) { + // Check the unary expression is a composite lit + boundStructLit, ok := unaryExpr.X.(*ast.CompositeLit) + if !ok { + return false, true + } + // Check if the composite lit is a struct + if _, ok := boundStructLit.Type.(*ast.StructType); ok { + // Parse struct + return false, true + } + // Check if the lit is an ident + ident, ok := boundStructLit.Type.(*ast.Ident) + if ok { + err := p.parseBoundStructMethods(ident.Name, pkg) + if err != nil { + return true, false + } + } + // Check if the lit is a selector + selector, ok := boundStructLit.Type.(*ast.SelectorExpr) + if ok { + // Check if the selector is an ident + if _, ok := selector.X.(*ast.Ident); ok { + // Look up the package + var parsedPackage *ParsedPackage + parsedPackage, err := p.getParsedPackageFromName(selector.X.(*ast.Ident).Name, pkg) + if err != nil { + return true, false + } + err = p.parseBoundStructMethods(selector.Sel.Name, parsedPackage) + if err != nil { + return true, false + } + return false, true + } + return false, true + } + return false, true +} + func (p *Project) addBoundMethods(packagePath string, name string, boundMethods []*BoundMethod) { _, ok := p.BoundMethods[packagePath] if !ok { @@ -488,7 +507,7 @@ func (p *Project) getParsedPackageFromName(packageName string, currentPackage *P Dir: dir, StructCache: make(map[string]*StructDef), } - packageCache[path] = result + p.packageCache[path] = result return result, nil } } diff --git a/v3/internal/parser/parser_test.go b/v3/internal/parser/parser_test.go index 931960d9b..c9f8ff6b8 100644 --- a/v3/internal/parser/parser_test.go +++ b/v3/internal/parser/parser_test.go @@ -702,6 +702,28 @@ func TestParseDirectory(t *testing.T) { }, }, }, + { + Name: "StructInputStructOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + Package: "main", + }, + }, + }, + }, { Name: "StructPointerInputStructPointerOutput", Inputs: []*Parameter{ @@ -1101,6 +1123,37 @@ func TestParseDirectory(t *testing.T) { }, }, }, + { + name: "should find a bound services using a variable", + dir: "testdata/variable_single", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/v3/internal/parser/testdata/struct_literal_single/main.go b/v3/internal/parser/testdata/struct_literal_single/main.go index 50dc571b2..2438a69cb 100644 --- a/v3/internal/parser/testdata/struct_literal_single/main.go +++ b/v3/internal/parser/testdata/struct_literal_single/main.go @@ -160,6 +160,10 @@ func (*GreetService) StructPointerInputErrorOutput(in *Person) error { return nil } +func (*GreetService) StructInputStructOutput(in Person) Person { + return in +} + func (*GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { return in } diff --git a/v3/internal/parser/testdata/variable_single/main.go b/v3/internal/parser/testdata/variable_single/main.go new file mode 100644 index 000000000..baffd783c --- /dev/null +++ b/v3/internal/parser/testdata/variable_single/main.go @@ -0,0 +1,36 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func main() { + greetService := &GreetService{} + app := application.New(application.Options{ + Bind: []interface{}{ + greetService, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +}