diff --git a/v2/pkg/commands/build/internal/backendjs/assignments.go b/v2/pkg/commands/build/internal/backendjs/assignments.go index ea6bb9b4..30a33c7b 100644 --- a/v2/pkg/commands/build/internal/backendjs/assignments.go +++ b/v2/pkg/commands/build/internal/backendjs/assignments.go @@ -1,8 +1,10 @@ package backendjs -import "go/ast" +import ( + "go/ast" +) -func (p *Parser) parseAssignment(assignStmt *ast.AssignStmt) { +func (p *Parser) parseAssignment(assignStmt *ast.AssignStmt, pkg *Package) { for _, rhs := range assignStmt.Rhs { ce, ok := rhs.(*ast.CallExpr) if ok { @@ -34,7 +36,7 @@ func (p *Parser) parseAssignment(assignStmt *ast.AssignStmt) { if ok { // Store the variable -> Function mapping // so we can later resolve the type - p.variablesThatWereAssignedByFunctions[i.Name] = fe.Name + pkg.variablesThatWereAssignedByFunctions[i.Name] = fe.Name } } } @@ -51,7 +53,20 @@ func (p *Parser) parseAssignment(assignStmt *ast.AssignStmt) { if len(assignStmt.Lhs) == 1 { i, ok := assignStmt.Lhs[0].(*ast.Ident) if ok { - p.variablesThatWereAssignedByStructLiterals[i.Name] = t.Name + pkg.variablesThatWereAssignedByStructLiterals[i.Name] = t.Name + } + } + } + } + } else { + cl, ok := rhs.(*ast.CompositeLit) + if ok { + t, ok := cl.Type.(*ast.Ident) + if ok { + if len(assignStmt.Lhs) == 1 { + i, ok := assignStmt.Lhs[0].(*ast.Ident) + if ok { + pkg.variablesThatWereAssignedByStructLiterals[i.Name] = t.Name } } } diff --git a/v2/pkg/commands/build/internal/backendjs/calls.go b/v2/pkg/commands/build/internal/backendjs/calls.go index fbe7245c..7d202b25 100644 --- a/v2/pkg/commands/build/internal/backendjs/calls.go +++ b/v2/pkg/commands/build/internal/backendjs/calls.go @@ -2,7 +2,7 @@ package backendjs import "go/ast" -func (p *Parser) parseCallExpressions(x *ast.CallExpr) { +func (p *Parser) parseCallExpressions(x *ast.CallExpr, pkg *Package) { f, ok := x.Fun.(*ast.SelectorExpr) if ok { n, ok := f.X.(*ast.Ident) @@ -14,9 +14,7 @@ func (p *Parser) parseCallExpressions(x *ast.CallExpr) { if ok { fn, ok := ce.Fun.(*ast.Ident) if ok { - // We found a bind method using a function call - // EG: app.Bind( newMyStruct() ) - p.structMethodsThatWereBound.Add(fn.Name) + pkg.structMethodsThatWereBound.Add(fn.Name) } } else { // We also want to check for Bind( &MyStruct{} ) @@ -27,8 +25,7 @@ func (p *Parser) parseCallExpressions(x *ast.CallExpr) { if ok { t, ok := cl.Type.(*ast.Ident) if ok { - // We have found Bind( &MyStruct{} ) - p.structPointerLiteralsThatWereBound.Add(t.Name) + pkg.structPointerLiteralsThatWereBound.Add(t.Name) } } } @@ -40,7 +37,8 @@ func (p *Parser) parseCallExpressions(x *ast.CallExpr) { if ok { t, ok := cl.Type.(*ast.Ident) if ok { - p.structLiteralsThatWereBound.Add(t.Name) + pkg.structLiteralsThatWereBound.Add(t.Name) + } } else { // Also check for when we bind a variable @@ -48,7 +46,7 @@ func (p *Parser) parseCallExpressions(x *ast.CallExpr) { // app.Bind( myVariable ) i, ok := x.Args[0].(*ast.Ident) if ok { - p.variablesThatWereBound.Add(i.Name) + pkg.variablesThatWereBound.Add(i.Name) } } } diff --git a/v2/pkg/commands/build/internal/backendjs/functions.go b/v2/pkg/commands/build/internal/backendjs/functions.go index fbfdc3f1..5756175d 100644 --- a/v2/pkg/commands/build/internal/backendjs/functions.go +++ b/v2/pkg/commands/build/internal/backendjs/functions.go @@ -132,7 +132,7 @@ func (p *Parser) parseFunctionDeclaration(funcDecl *ast.FuncDecl, pkg *Package) s, ok := t.X.(*ast.Ident) if ok { // println("*** Function", functionName, "found which returns: *"+s.Name) - p.functionsThatReturnStructPointers[functionName] = s.Name + pkg.functionsThatReturnStructPointers[functionName] = s.Name } } else { // Check for functions that return a struct @@ -140,7 +140,7 @@ func (p *Parser) parseFunctionDeclaration(funcDecl *ast.FuncDecl, pkg *Package) t, ok := funcDecl.Type.Results.List[0].Type.(*ast.Ident) if ok { // println("*** Function", functionName, "found which returns: "+t.Name) - p.functionsThatReturnStructs[functionName] = t.Name + pkg.functionsThatReturnStructs[functionName] = t.Name } } } diff --git a/v2/pkg/commands/build/internal/backendjs/package.d.template b/v2/pkg/commands/build/internal/backendjs/package.d.template index 4dfd92b5..a5ca6288 100644 --- a/v2/pkg/commands/build/internal/backendjs/package.d.template +++ b/v2/pkg/commands/build/internal/backendjs/package.d.template @@ -4,17 +4,22 @@ {{- range .DeclarationReferences}} /// {{- end}} -declare module {{.Name}} { {{range .Structs}} - {{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{end}} - interface {{.Name}} { {{ if $.StructIsUsedAsData .Name }} +declare module {{.Name}} { {{- range .Structs}} + {{- $usedAsData := $.StructIsUsedAsData .Name }} + {{- if or .IsBound $usedAsData}} + {{- if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{- end}} + + interface {{.Name}} { {{ if $usedAsData }} {{- range .Fields}}{{if .Comments }} - {{range .Comments}}//{{ . }}{{end}}{{end}} + {{range .Comments}}//{{ . }}{{end}}{{- end}} {{.Name}}: {{.TypeAsTSType}}; {{- end}} {{ end }} + {{- if .IsBound }} {{- range .Methods}} {{- range .Comments}} // {{ . }}{{- end}} {{.Name}}({{.InputsAsTSText}}): Promise<{{.OutputsAsTSText}}>; - {{- end}} - }{{end}} + {{- end}}{{end}} + }{{- end}} + {{- end}} } \ No newline at end of file diff --git a/v2/pkg/commands/build/internal/backendjs/package.go b/v2/pkg/commands/build/internal/backendjs/package.go index d05376ab..c3ed685a 100644 --- a/v2/pkg/commands/build/internal/backendjs/package.go +++ b/v2/pkg/commands/build/internal/backendjs/package.go @@ -25,12 +25,46 @@ type Package struct { // These are the structs declared in this package // that are used as data by either this or other packages structsUsedAsData slicer.StringSlicer + + // A list of functions that return struct pointers + functionsThatReturnStructPointers map[string]string + + // A list of functions that return structs + functionsThatReturnStructs map[string]string + + // A list of struct literals that were bound to the application + // EG: app.Bind( &mystruct{} ) + structLiteralsThatWereBound slicer.StringSlicer + + // A list of struct pointer literals that were bound to the application + // EG: app.Bind( &mystruct{} ) + structPointerLiteralsThatWereBound slicer.StringSlicer + + // A list of methods that returns structs to the Bind method + // EG: app.Bind( newMyStruct() ) + structMethodsThatWereBound slicer.StringSlicer + + // A list of variables that were used for binding + // Eg: myVar := &mystruct{}; app.Bind( myVar ) + variablesThatWereBound slicer.StringSlicer + + // A list of variables that were assigned using a function call + // EG: myVar := newStruct() + variablesThatWereAssignedByFunctions map[string]string + + // A map of variables that were assigned using a struct literal + // EG: myVar := MyStruct{} + variablesThatWereAssignedByStructLiterals map[string]string } func (p *Parser) parsePackage(pkg *packages.Package, fset *token.FileSet) (*Package, error) { result := &Package{ - Name: pkg.Name, - Structs: make(map[string]*Struct), + Name: pkg.Name, + Structs: make(map[string]*Struct), + functionsThatReturnStructPointers: make(map[string]string), + functionsThatReturnStructs: make(map[string]string), + variablesThatWereAssignedByFunctions: make(map[string]string), + variablesThatWereAssignedByStructLiterals: make(map[string]string), } // Get the absolute path to the project's main.go file @@ -74,12 +108,12 @@ func (p *Parser) parsePackage(pkg *packages.Package, fset *token.FileSet) (*Pack // Capture call expressions if callExpr, ok := n.(*ast.CallExpr); ok { - p.parseCallExpressions(callExpr) + p.parseCallExpressions(callExpr, result) } // Parse Assignments if assignStmt, ok := n.(*ast.AssignStmt); ok { - p.parseAssignment(assignStmt) + p.parseAssignment(assignStmt, result) } // Parse Function declarations @@ -158,3 +192,27 @@ func (p *Package) DeclarationReferences() []string { func (p *Package) StructIsUsedAsData(structName string) bool { return p.structsUsedAsData.Contains(structName) } + +func (p *Package) resolveBoundStructLiterals() { + p.structLiteralsThatWereBound.Each(func(structName string) { + strct := p.Structs[structName] + if strct == nil { + println("Warning: Cannot find bound struct", structName, "in package", p.Name) + return + } + println("Bound struct", strct.Name, "in package", p.Name) + strct.IsBound = true + }) +} + +func (p *Package) resolveBoundStructPointerLiterals() { + p.structPointerLiteralsThatWereBound.Each(func(structName string) { + strct := p.Structs[structName] + if strct == nil { + println("Warning: Cannot find bound struct", structName, "in package", p.Name) + return + } + println("Bound struct pointer", strct.Name, "in package", p.Name) + strct.IsBound = true + }) +} diff --git a/v2/pkg/commands/build/internal/backendjs/package.template b/v2/pkg/commands/build/internal/backendjs/package.template index e5d32a57..44e5df7f 100644 --- a/v2/pkg/commands/build/internal/backendjs/package.template +++ b/v2/pkg/commands/build/internal/backendjs/package.template @@ -1,7 +1,9 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT -{{range $struct := .Structs }} {{ if .Methods }} +{{range $struct := .Structs }} +{{- if .IsBound }} +{{ if .Methods }} export const {{.Name}} = { {{range .Methods }} /**{{if .Comments }} @@ -18,6 +20,7 @@ export const {{.Name}} = { {{end}} } {{end}} +{{- end}} {{end}} diff --git a/v2/pkg/commands/build/internal/backendjs/parser.go b/v2/pkg/commands/build/internal/backendjs/parser.go index 9885979c..b04be117 100644 --- a/v2/pkg/commands/build/internal/backendjs/parser.go +++ b/v2/pkg/commands/build/internal/backendjs/parser.go @@ -1,6 +1,7 @@ package backendjs import ( + "github.com/davecgh/go-spew/spew" "github.com/leaanthony/slicer" ) @@ -17,52 +18,154 @@ type Parser struct { // import mywails "github.com/wailsapp/wails/v2" -> mywails wailsPackageVariable string - // A list of methods that returns structs to the Bind method - // EG: app.Bind( newMyStruct() ) - structMethodsThatWereBound slicer.StringSlicer - - // A list of struct literals that were bound to the application - // EG: app.Bind( &mystruct{} ) - structLiteralsThatWereBound slicer.StringSlicer - - // A list of struct pointer literals that were bound to the application - // EG: app.Bind( &mystruct{} ) - structPointerLiteralsThatWereBound slicer.StringSlicer - - // A list of variables that were used for binding - // Eg: myVar := &mystruct{}; app.Bind( myVar ) - variablesThatWereBound slicer.StringSlicer - - // A list of variables that were assigned using a function call - // EG: myVar := newStruct() - variablesThatWereAssignedByFunctions map[string]string - - // A map of variables that were assigned using a struct literal - // EG: myVar := MyStruct{} - variablesThatWereAssignedByStructLiterals map[string]string - // Internal methods (WailsInit/WailsShutdown) internalMethods *slicer.StringSlicer - - // A list of functions that return struct pointers - functionsThatReturnStructPointers map[string]string - - // A list of functions that return structs - functionsThatReturnStructs map[string]string } // NewParser creates a new Wails Project parser func NewParser() *Parser { return &Parser{ - Packages: make(map[string]*Package), - variablesThatWereAssignedByFunctions: make(map[string]string), - variablesThatWereAssignedByStructLiterals: make(map[string]string), - functionsThatReturnStructPointers: make(map[string]string), - functionsThatReturnStructs: make(map[string]string), - internalMethods: slicer.String([]string{"WailsInit", "WailsShutdown"}), + Packages: make(map[string]*Package), + internalMethods: slicer.String([]string{"WailsInit", "WailsShutdown"}), } } func (p *Parser) resolve() error { + + // Resolve bound structs + err := p.resolveBoundStructs() + if err != nil { + return err + } return nil } + +func (p *Parser) resolveBoundStructs() error { + + // Resolve Struct Literals + p.resolveBoundStructLiterals() + + // Resolve Struct Pointer Literals + p.resolveBoundStructPointerLiterals() + + // Resolve functions that were bound + // EG: app.Bind( newBasic() ) + p.resolveBoundFunctions() + + // Resolve variables that were bound + p.resolveBoundVariables() + + return nil +} +func (p *Parser) resolveBoundStructLiterals() { + + // Resolve struct literals in each package + for _, pkg := range p.Packages { + pkg.resolveBoundStructLiterals() + } +} + +func (p *Parser) resolveBoundStructPointerLiterals() { + + // Resolve struct pointer literals + for _, pkg := range p.Packages { + pkg.resolveBoundStructPointerLiterals() + } +} + +func (p *Parser) resolveBoundFunctions() { + + // Loop over packages + for _, pkg := range p.Packages { + + // Iterate over the method names + pkg.structMethodsThatWereBound.Each(func(functionName string) { + println("Resolving: ", functionName) + // Resolve each method name + structName := p.resolveFunctionReturnType(pkg, functionName) + + strct := pkg.Structs[structName] + if strct == nil { + println("WARNING: Unable to find definition for struct", structName) + } + strct.IsBound = true + }) + + } +} + +// resolveFunctionReturnType gets the return type for the given package/function name combination +func (p *Parser) resolveFunctionReturnType(pkg *Package, functionName string) string { + structName := pkg.functionsThatReturnStructPointers[functionName] + if structName == "" { + spew.Dump(pkg.functionsThatReturnStructs) + structName = pkg.functionsThatReturnStructs[functionName] + } + if structName == "" { + println("WARNING: Unable to resolve bound function", functionName, "in package", pkg.Name) + } + return structName +} + +func (p *Parser) markStructAsBound(pkg *Package, structName string) { + strct := pkg.Structs[structName] + if strct == nil { + println("WARNING: Unable to find definition for struct", structName) + } + println("Found bound struct:", strct.Name) + strct.IsBound = true +} + +func (p *Parser) resolveBoundVariables() { + + for _, pkg := range p.Packages { + + // Iterate over the method names + pkg.variablesThatWereBound.Each(func(variableName string) { + println("Resolving variable: ", variableName) + + var structName string + + // Resolve each method name + funcName := pkg.variablesThatWereAssignedByFunctions[variableName] + if funcName != "" { + // Found function name - resolve Function return type + structName = p.resolveFunctionReturnType(pkg, funcName) + } + + // If we couldn't resolve to a function, then let's try struct literals + if structName == "" { + funcName = pkg.variablesThatWereAssignedByStructLiterals[variableName] + if funcName != "" { + // Found function name - resolve Function return type + structName = p.resolveFunctionReturnType(pkg, funcName) + } + } + + if structName == "" { + println("WARNING: Unable to resolve bound variable", variableName, "in package", pkg.Name) + return + } + + p.markStructAsBound(pkg, structName) + }) + } +} + +func (p *Parser) bindStructByStructName(sn *StructName) { + // Get package + pkg := p.Packages[sn.Package] + if pkg == nil { + // Ignore, it will get picked up by the compiler + return + } + + strct := pkg.Structs[sn.Name] + if strct == nil { + // Ignore, it will get picked up by the compiler + return + } + + println("Found bound Struct:", sn.ToString()) + strct.IsBound = true +} diff --git a/v2/pkg/commands/build/internal/backendjs/struct.go b/v2/pkg/commands/build/internal/backendjs/struct.go index 33e9738a..fd63059c 100644 --- a/v2/pkg/commands/build/internal/backendjs/struct.go +++ b/v2/pkg/commands/build/internal/backendjs/struct.go @@ -29,7 +29,7 @@ type StructName struct { Package string } -// ToString returns a text representation of the struct name +// ToString returns a text representation of the struct anme func (s *StructName) ToString() string { result := "" if s.Package != "" { @@ -48,7 +48,7 @@ type Field struct { // JSType returns the Javascript type for this field func (f *Field) JSType() string { - return goTypeToJS(f) + return string(goTypeToJS(f)) } // TypeAsTSType converts the Field type to something TS wants