From 868b769e7f7cd5229620fd68e9feda883d1ca3d4 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 1 Mar 2023 21:31:29 +1100 Subject: [PATCH] Initial bindings generation --- v3/examples/binding/bindings.js | 9 ++-- v3/internal/parser/bindings.go | 80 +++++++++++++++++++++++++++++ v3/internal/parser/bindings_test.go | 66 ++++++++++++++++++++++++ v3/internal/parser/parser.go | 36 +++++++++++++ 4 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 v3/internal/parser/bindings.go create mode 100644 v3/internal/parser/bindings_test.go diff --git a/v3/examples/binding/bindings.js b/v3/examples/binding/bindings.js index 895537e12..1f8ce7495 100644 --- a/v3/examples/binding/bindings.js +++ b/v3/examples/binding/bindings.js @@ -17,10 +17,9 @@ function Greet(name) { return wails.Call(GreetService("Greet", name)); } -window.go = { - main: { - GreetService: { - Greet, - } +window.go = window.go || {}; +Object.window.go.main = { + GreetService: { + Greet, } }; diff --git a/v3/internal/parser/bindings.go b/v3/internal/parser/bindings.go new file mode 100644 index 000000000..de4a3c8cc --- /dev/null +++ b/v3/internal/parser/bindings.go @@ -0,0 +1,80 @@ +package parser + +import ( + "strings" +) + +const helperTemplate = `function {{structName}}(method) { + return { + packageName: "{{packageName}}", + serviceName: "{{structName}}", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +}` + +func GenerateHelper(packageName, structName string) string { + result := strings.ReplaceAll(helperTemplate, "{{packageName}}", packageName) + result = strings.ReplaceAll(result, "{{structName}}", structName) + return result +} + +const bindingTemplate = ` + +/** + * {{structName}}.{{methodName}} + * Comments + * @param name {string} + * @returns {Promise} + */ +function {{methodName}}({{args}}) { + return wails.Call({{structName}}("{{methodName}}", {{args}})); +} +` + +func GenerateBinding(structName string, method *BoundMethod) string { + result := strings.ReplaceAll(bindingTemplate, "{{structName}}", structName) + result = strings.ReplaceAll(result, "{{methodName}}", method.Name) + result = strings.ReplaceAll(result, "Comments", strings.TrimSpace(method.DocComment)) + var params string + for _, input := range method.Inputs { + params += " * @param " + input.Name + " {" + input.JSType() + "}\n" + } + params = strings.TrimSuffix(params, "\n") + result = strings.ReplaceAll(result, " * @param name {string}", params) + var args string + for _, input := range method.Inputs { + args += input.Name + ", " + } + args = strings.TrimSuffix(args, ", ") + result = strings.ReplaceAll(result, "{{args}}", args) + return result +} + +func GenerateBindings(bindings map[string]map[string][]*BoundMethod) string { + + var result string + for packageName, packageBindings := range bindings { + for structName, bindings := range packageBindings { + result += GenerateHelper(packageName, structName) + for _, binding := range bindings { + result += GenerateBinding(structName, binding) + } + } + } + result += ` +window.go = window.go || {}; +` + for packageName, packageBindings := range bindings { + result += "Object.window.go." + packageName + " = {\n" + for structName, methods := range packageBindings { + result += " " + structName + ": {\n" + for _, method := range methods { + result += " " + method.Name + ",\n" + } + result += " }\n" + } + result += "};\n" + } + return result +} diff --git a/v3/internal/parser/bindings_test.go b/v3/internal/parser/bindings_test.go new file mode 100644 index 000000000..cb1c342f6 --- /dev/null +++ b/v3/internal/parser/bindings_test.go @@ -0,0 +1,66 @@ +package parser + +import ( + "github.com/google/go-cmp/cmp" + "testing" +) + +const expectedGreetService = `function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + */ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} + +window.go = window.go || {}; +Object.window.go.main = { + GreetService: { + Greet, + } +}; +` + +func TestGenerateGreetService(t *testing.T) { + parsedMethods := 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", + }, + }, + }, + }, + }, + }, + } + got := GenerateBindings(parsedMethods) + if diff := cmp.Diff(expectedGreetService, got); diff != "" { + t.Fatalf("GenerateService() mismatch (-want +got):\n%s", diff) + } +} diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go index 59b25d1f9..b1887ad67 100644 --- a/v3/internal/parser/parser.go +++ b/v3/internal/parser/parser.go @@ -38,6 +38,42 @@ type Parameter struct { Type *ParameterType } +func (p *Parameter) JSType() string { + // Convert type to javascript equivalent type + var typeName string + switch p.Type.Name { + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr": + typeName = "number" + case "string": + typeName = "string" + case "bool": + typeName = "boolean" + default: + typeName = p.Type.Name + } + + // if the type is a struct, we need to add the package name + if p.Type.IsStruct { + if p.Type.Package != "" { + parts := strings.Split(p.Type.Package, "/") + typeName = parts[len(parts)-1] + "." + typeName + // TODO: Check if this is a duplicate package name + } + } + + // Add slice suffix + if p.Type.IsSlice { + typeName += "[]" + } + + // Add pointer suffix + if p.Type.IsPointer { + typeName += " | null" + } + + return typeName +} + type BoundMethod struct { Name string DocComment string