diff --git a/v2/internal/binding/binding_test/binding_type_alias_test.go b/v2/internal/binding/binding_test/binding_type_alias_test.go new file mode 100644 index 000000000..8e7c7ca6d --- /dev/null +++ b/v2/internal/binding/binding_test/binding_type_alias_test.go @@ -0,0 +1,64 @@ +package binding_test + +import ( + "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/int_package" + "io/fs" + "os" + "testing" + + "github.com/wailsapp/wails/v2/internal/binding" + "github.com/wailsapp/wails/v2/internal/logger" +) + +const expectedTypeAliasBindings = `// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +import {binding_test} from '../models'; +import {int_package} from '../models'; + +export function Map():Promise<{[key: string]: string}>; + +export function MapAlias():Promise; + +export function MapWithImportedStructValue():Promise<{[key: string]: int_package.SomeStruct}>; + +export function Slice():Promise>; + +export function SliceImportedStruct():Promise>; +` + +type AliasTest struct{} +type MapAlias map[string]string + +func (h *AliasTest) Map() map[string]string { return nil } +func (h *AliasTest) MapAlias() MapAlias { return nil } +func (h *AliasTest) MapWithImportedStructValue() map[string]int_package.SomeStruct { return nil } +func (h *AliasTest) Slice() []string { return nil } +func (h *AliasTest) SliceImportedStruct() []int_package.SomeStruct { return nil } + +func TestAliases(t *testing.T) { + // given + generationDir := t.TempDir() + + // setup + testLogger := &logger.Logger{} + b := binding.NewBindings(testLogger, []interface{}{&AliasTest{}}, []interface{}{}, false) + + // then + err := b.GenerateGoBindings(generationDir) + if err != nil { + t.Fatalf("could not generate the Go bindings: %v", err) + } + + // then + rawGeneratedBindings, err := fs.ReadFile(os.DirFS(generationDir), "binding_test/AliasTest.d.ts") + if err != nil { + t.Fatalf("could not read the generated bindings: %v", err) + } + + // then + generatedBindings := string(rawGeneratedBindings) + if generatedBindings != expectedTypeAliasBindings { + t.Fatalf("the generated bindings does not match the expected ones.\nWanted:\n%s\n\nGot:\n%s", expectedTypeAliasBindings, + generatedBindings) + } +} diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go index 6513f293f..0e2294f22 100644 --- a/v2/internal/binding/generate.go +++ b/v2/internal/binding/generate.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "sort" "strings" @@ -14,6 +15,22 @@ import ( "github.com/leaanthony/slicer" ) +var mapRegex *regexp.Regexp +var keyPackageIndex int +var keyTypeIndex int +var valueArrayIndex int +var valuePackageIndex int +var valueTypeIndex int + +func init() { + mapRegex = regexp.MustCompile(`(?:map\[(?:(?P\w+)\.)?(?P\w+)])?(?P\[])?(?:(?P\w+)\.)?(?P.+)`) + keyPackageIndex = mapRegex.SubexpIndex("keyPackage") + keyTypeIndex = mapRegex.SubexpIndex("keyType") + valueArrayIndex = mapRegex.SubexpIndex("valueArray") + valuePackageIndex = mapRegex.SubexpIndex("valuePackage") + valueTypeIndex = mapRegex.SubexpIndex("valueType") +} + func (b *Bindings) GenerateGoBindings(baseDir string) error { store := b.db.store var obfuscatedBindings map[string]int @@ -128,56 +145,85 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error { return nil } -func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) string { - // Verifying this first to ensure we are not converting a type - // coming from a package that has a name matching a golang type, such as: - // - interactor -> int - // - mapper -> map - if strings.ContainsRune(input, '.') { - namespace := getPackageName(input) - importNamespaces.Add(namespace) - return namespace + "." + strings.Split(input, ".")[1] +func fullyQualifiedName(packageName string, typeName string) string { + if len(packageName) > 0 { + return packageName + "." + typeName } switch true { - case input == "interface {}" || input == "interface{}": + case len(typeName) == 0: + return "" + case typeName == "interface{}" || typeName == "interface {}": return "any" - case input == "string": + case typeName == "string": return "string" - case input == "error": + case typeName == "error": return "Error" case - strings.HasPrefix(input, "int"), - strings.HasPrefix(input, "uint"), - strings.HasPrefix(input, "float"): + strings.HasPrefix(typeName, "int"), + strings.HasPrefix(typeName, "uint"), + strings.HasPrefix(typeName, "float"): return "number" - case input == "bool": + case typeName == "bool": return "boolean" - case input == "[]byte": - return "string" - case strings.HasPrefix(input, "map"): - temp := strings.TrimPrefix(input, "map[") - // Split the string into the key and value types - tempSplit := strings.SplitN(temp, "]", 2) - if len(tempSplit) < 2 { - panic("Invalid map type provided: " + input) - } - keyType := tempSplit[0] - valueType := tempSplit[1] - return fmt.Sprintf("{[key: %s]: %s}", goTypeToJSDocType(keyType, importNamespaces), goTypeToJSDocType(valueType, importNamespaces)) - case strings.HasPrefix(input, "[]"): - arrayType := goTypeToJSDocType(input[2:], importNamespaces) - return "Array<" + arrayType + ">" default: return "any" } } -func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string { - if strings.HasPrefix(input, "[]") { - arrayType := goTypeToJSDocType(input[2:], importNamespaces) - return "Array<" + arrayType + ">" +func arrayifyValue(valueArray string, valueType string) string { + if len(valueArray) == 0 { + return valueType } + + return "Array<" + valueType + ">" +} + +func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) string { + matches := mapRegex.FindStringSubmatch(input) + keyPackage := matches[keyPackageIndex] + keyType := matches[keyTypeIndex] + valueArray := matches[valueArrayIndex] + valuePackage := matches[valuePackageIndex] + valueType := matches[valueTypeIndex] + //fmt.Printf("input=%s, keyPackage=%s, keyType=%s, valueArray=%s, valuePackage=%s, valueType=%s\n", + // input, + // keyPackage, + // keyType, + // valueArray, + // valuePackage, + // valueType) + + // byte array is special case + if valueArray == "[]" && valueType == "byte" { + return "string" + } + + // if any packages, make sure they're saved + if len(keyPackage) > 0 { + importNamespaces.Add(keyPackage) + } + + if len(valuePackage) > 0 { + importNamespaces.Add(valuePackage) + } + + key := fullyQualifiedName(keyPackage, keyType) + var value string + if strings.HasPrefix(valueType, "map") { + value = goTypeToJSDocType(valueType, importNamespaces) + } else { + value = fullyQualifiedName(valuePackage, valueType) + } + + if len(key) > 0 { + return fmt.Sprintf("{[key: %s]: %s}", key, arrayifyValue(valueArray, value)) + } + + return arrayifyValue(valueArray, value) +} + +func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string { return goTypeToJSDocType(input, importNamespaces) }