From 71aa7c9731af5176dc86ef816f8164e747fcd2eb Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Fri, 3 Mar 2023 19:54:12 +1100 Subject: [PATCH] Initial bindings.js generation --- v3/internal/parser/bindings.go | 100 ++++++++++++++++++++++++---- v3/internal/parser/bindings_test.go | 92 ++++++++++--------------- v3/internal/parser/parser.go | 2 +- 3 files changed, 123 insertions(+), 71 deletions(-) diff --git a/v3/internal/parser/bindings.go b/v3/internal/parser/bindings.go index de4a3c8cc..a6c513e19 100644 --- a/v3/internal/parser/bindings.go +++ b/v3/internal/parser/bindings.go @@ -2,6 +2,8 @@ package parser import ( "strings" + + "github.com/samber/lo" ) const helperTemplate = `function {{structName}}(method) { @@ -11,7 +13,8 @@ const helperTemplate = `function {{structName}}(method) { methodName: method, args: Array.prototype.slice.call(arguments, 1), }; -}` +} +` func GenerateHelper(packageName, structName string) string { result := strings.ReplaceAll(helperTemplate, "{{packageName}}", packageName) @@ -20,45 +23,109 @@ func GenerateHelper(packageName, structName string) string { } const bindingTemplate = ` - /** * {{structName}}.{{methodName}} * Comments * @param name {string} * @returns {Promise} - */ -function {{methodName}}({{args}}) { - return wails.Call({{structName}}("{{methodName}}", {{args}})); + **/ +function {{methodName}}({{inputs}}) { + return wails.Call({{structName}}("{{methodName}}"{{args}})); } ` -func GenerateBinding(structName string, method *BoundMethod) string { +func sanitiseJSVarName(name string) string { + // if the name is a reserved word, prefix with an + // underscore + if strings.Contains("break,case,catch,class,const,continue,debugger,default,delete,do,else,enum,export,extends,false,finally,for,function,if,implements,import,in,instanceof,interface,let,new,null,package,private,protected,public,return,static,super,switch,this,throw,true,try,typeof,var,void,while,with,yield", name) { + return "_" + name + } + return name +} + +func GenerateBinding(structName string, method *BoundMethod) (string, []string) { + var models []string result := strings.ReplaceAll(bindingTemplate, "{{structName}}", structName) result = strings.ReplaceAll(result, "{{methodName}}", method.Name) - result = strings.ReplaceAll(result, "Comments", strings.TrimSpace(method.DocComment)) + comments := strings.TrimSpace(method.DocComment) + result = strings.ReplaceAll(result, "Comments", comments) var params string for _, input := range method.Inputs { - params += " * @param " + input.Name + " {" + input.JSType() + "}\n" + pkgName := getPackageName(input) + if pkgName != "" { + models = append(models, pkgName) + } + params += " * @param " + sanitiseJSVarName(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 + ", " + if len(params) == 0 { + params = " *" } - args = strings.TrimSuffix(args, ", ") + ////params += "\n" + result = strings.ReplaceAll(result, " * @param name {string}", params) + var inputs string + for _, input := range method.Inputs { + pkgName := getPackageName(input) + if pkgName != "" { + models = append(models, pkgName) + } + inputs += sanitiseJSVarName(input.Name) + ", " + } + inputs = strings.TrimSuffix(inputs, ", ") + args := inputs + if len(args) > 0 { + args = ", " + args + } + result = strings.ReplaceAll(result, "{{inputs}}", inputs) result = strings.ReplaceAll(result, "{{args}}", args) + + // outputs + var returns string + if len(method.Outputs) == 0 { + returns = " * @returns {Promise}" + } else { + returns = " * @returns {Promise<" + for _, output := range method.Outputs { + pkgName := getPackageName(output) + if pkgName != "" { + models = append(models, pkgName) + } + jsType := output.JSType() + if jsType == "error" { + jsType = "void" + } + returns += jsType + ", " + } + returns = strings.TrimSuffix(returns, ", ") + returns += ">}" + } + result = strings.ReplaceAll(result, " * @returns {Promise}", returns) + + return result, lo.Uniq(models) +} + +func getPackageName(input *Parameter) string { + if !input.Type.IsStruct { + return "" + } + result := input.Type.Package + if result == "" { + result = "main" + } return result } func GenerateBindings(bindings map[string]map[string][]*BoundMethod) string { var result string + var allModels []string for packageName, packageBindings := range bindings { for structName, bindings := range packageBindings { result += GenerateHelper(packageName, structName) for _, binding := range bindings { - result += GenerateBinding(structName, binding) + thisBinding, models := GenerateBinding(structName, binding) + result += thisBinding + allModels = append(allModels, models...) } } } @@ -76,5 +143,10 @@ window.go = window.go || {}; } result += "};\n" } + + // add imports + imports := "import {" + strings.Join(lo.Uniq(allModels), ", ") + "} from './models';\n" + result = imports + "\n" + result + return result } diff --git a/v3/internal/parser/bindings_test.go b/v3/internal/parser/bindings_test.go index cb1c342f6..fc7100d8c 100644 --- a/v3/internal/parser/bindings_test.go +++ b/v3/internal/parser/bindings_test.go @@ -1,66 +1,46 @@ package parser import ( - "github.com/google/go-cmp/cmp" + "os" "testing" + + "github.com/google/go-cmp/cmp" ) -const expectedGreetService = `function GreetService(method) { - return { - packageName: "main", - serviceName: "GreetService", - methodName: method, - args: Array.prototype.slice.call(arguments, 1), - }; -} +func TestGenerateBindings(t *testing.T) { -/** - * 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", - }, - }, - }, - }, - }, - }, + tests := []string{ + "struct_literal_single", } - got := GenerateBindings(parsedMethods) - if diff := cmp.Diff(expectedGreetService, got); diff != "" { - t.Fatalf("GenerateService() mismatch (-want +got):\n%s", diff) + for _, projectDir := range tests { + t.Run(projectDir, func(t *testing.T) { + projectDir = "testdata/" + projectDir + // Run parser on directory + project, err := ParseProject(projectDir) + if err != nil { + t.Errorf("ParseProject() error = %v", err) + return + } + + // Generate Bindings + got := GenerateBindings(project.BoundMethods) + // Write file to project directory + err = os.WriteFile(projectDir+"/bindings.got.js", []byte(got), 0644) + if err != nil { + t.Errorf("os.WriteFile() error = %v", err) + return + } + // Load bindings.js from project directory + expected, err := os.ReadFile(projectDir + "/bindings.js") + if err != nil { + t.Errorf("os.ReadFile() error = %v", err) + return + } + + // Compare + if diff := cmp.Diff(string(expected), 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 b1887ad67..d69fbe061 100644 --- a/v3/internal/parser/parser.go +++ b/v3/internal/parser/parser.go @@ -42,7 +42,7 @@ 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": + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64": typeName = "number" case "string": typeName = "string"