From 42c3e070cdbd3a2516f38481e6f6bac4f5dab459 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Wed, 1 Jan 2025 00:58:55 -0500 Subject: [PATCH 01/15] Do not attempt to export fields that cannot be json-encoded --- v2/internal/binding/binding.go | 7 +++++++ v2/internal/binding/binding_test/binding_notags_test.go | 1 + v2/internal/typescriptify/typescriptify.go | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/v2/internal/binding/binding.go b/v2/internal/binding/binding.go index b42718bffd2..590bac4592b 100644 --- a/v2/internal/binding/binding.go +++ b/v2/internal/binding/binding.go @@ -350,6 +350,13 @@ func (b *Bindings) hasExportedJSONFields(typeOf reflect.Type) bool { for i := 0; i < typeOf.NumField(); i++ { jsonFieldName := "" f := typeOf.Field(i) + // function, complex, and channel types cannot be json-encoded + if f.Type.Kind() == reflect.Chan || + f.Type.Kind() == reflect.Func || + f.Type.Kind() == reflect.Complex128 || + f.Type.Kind() == reflect.Complex64 { + continue + } jsonTag, hasTag := f.Tag.Lookup("json") if !hasTag && f.IsExported() { return true diff --git a/v2/internal/binding/binding_test/binding_notags_test.go b/v2/internal/binding/binding_test/binding_notags_test.go index c59a86e1b2b..d4d9997e0c0 100644 --- a/v2/internal/binding/binding_test/binding_notags_test.go +++ b/v2/internal/binding/binding_test/binding_notags_test.go @@ -5,6 +5,7 @@ type NoFieldTags struct { Address string Zip *string Spouse *NoFieldTags + NoFunc func() string } func (n NoFieldTags) Get() NoFieldTags { diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index 1d22a1b65d7..2a8a98e5744 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -553,6 +553,13 @@ func (t *TypeScriptify) getFieldOptions(structType reflect.Type, field reflect.S func (t *TypeScriptify) getJSONFieldName(field reflect.StructField, isPtr bool) string { jsonFieldName := "" + // function, complex, and channel types cannot be json-encoded + if field.Type.Kind() == reflect.Chan || + field.Type.Kind() == reflect.Func || + field.Type.Kind() == reflect.Complex128 || + field.Type.Kind() == reflect.Complex64 { + return "" + } jsonTag, hasTag := field.Tag.Lookup("json") if !hasTag && field.IsExported() { jsonFieldName = field.Name From f9d053af9a8ec0f715d1a35de549c6e888038ae1 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Wed, 1 Jan 2025 01:13:04 -0500 Subject: [PATCH 02/15] update changelog w/ PR --- website/src/pages/changelog.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index a0e491134cf..bbe52a84b92 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed incorrect TS definition of `WindowSetSize` by @leaanthony - chore: fix some comments in [PR](https://github.com/wailsapp/wails/pull/3932) by @lvyaoting - [windows] Fixed frameless window flickering when minimizing/restoring by preventing unnecessary redraws [#3951](https://github.com/wailsapp/wails/issues/3951) - +- Fixed failed models.ts build due to non-json-encodable Go types [PR](https://github.com/wailsapp/wails/pull/3975) by [@pbnjay](https://github.com/pbnjay) ### Changed - Allow to specify macos-min-version externally. Implemented by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/3756) From e8110bc77ae1322dd9c07403781cfd153c6f4e6e Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Wed, 1 Jan 2025 02:04:42 -0500 Subject: [PATCH 03/15] also skip UnsafePointers --- v2/internal/binding/binding.go | 1 + v2/internal/typescriptify/typescriptify.go | 1 + 2 files changed, 2 insertions(+) diff --git a/v2/internal/binding/binding.go b/v2/internal/binding/binding.go index 590bac4592b..b7794876bf2 100644 --- a/v2/internal/binding/binding.go +++ b/v2/internal/binding/binding.go @@ -353,6 +353,7 @@ func (b *Bindings) hasExportedJSONFields(typeOf reflect.Type) bool { // function, complex, and channel types cannot be json-encoded if f.Type.Kind() == reflect.Chan || f.Type.Kind() == reflect.Func || + f.Type.Kind() == reflect.UnsafePointer || f.Type.Kind() == reflect.Complex128 || f.Type.Kind() == reflect.Complex64 { continue diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index 2a8a98e5744..f8cb14838eb 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -556,6 +556,7 @@ func (t *TypeScriptify) getJSONFieldName(field reflect.StructField, isPtr bool) // function, complex, and channel types cannot be json-encoded if field.Type.Kind() == reflect.Chan || field.Type.Kind() == reflect.Func || + field.Type.Kind() == reflect.UnsafePointer || field.Type.Kind() == reflect.Complex128 || field.Type.Kind() == reflect.Complex64 { return "" From a4345e5cb7c19743c2cbcbdae07bedae8c13124d Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Wed, 1 Jan 2025 15:20:15 -0500 Subject: [PATCH 04/15] WIP to allow conversion from Go generic types to typescript --- v2/internal/typescriptify/typescriptify.go | 50 ++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index f8cb14838eb..8d9e25912d0 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -261,15 +261,19 @@ func (t *TypeScriptify) AddType(typeOf reflect.Type) *TypeScriptify { func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.StructField) { keyType := field.Type.Key() valueType := field.Type.Elem() - valueTypeName := valueType.Name() + valueTypeName := t.nameTypeOf(valueType) if name, ok := t.types[valueType.Kind()]; ok { valueTypeName = name } if valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Slice { - valueTypeName = valueType.Elem().Name() + "[]" + valueTypeName = t.nameTypeOf(valueType.Elem()) + "[]" } if valueType.Kind() == reflect.Ptr { - valueTypeName = valueType.Elem().Name() + valueTypeName = t.nameTypeOf(valueType.Elem()) + } + if valueType.Kind() == reflect.Map { + // TODO: support nested maps + valueTypeName = "any" // valueType.Elem().Name() } if valueType.Kind() == reflect.Struct && differentNamespaces(t.namespace, valueType) { valueTypeName = valueType.String() @@ -296,9 +300,11 @@ func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.Str } t.fields = append(t.fields, fmt.Sprintf("%s%s: {[key: %s]: %s};", t.indent, fieldName, keyTypeStr, valueTypeName)) if valueType.Kind() == reflect.Struct { - t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = this.convertValues(source[\"%s\"], %s, true);", t.indent, t.indent, dotField, strippedFieldName, t.prefix+valueTypeName+t.suffix)) + t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = this.convertValues(source[\"%s\"], %s, true);", + t.indent, t.indent, dotField, strippedFieldName, t.prefix+valueTypeName+t.suffix)) } else { - t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = source[\"%s\"];", t.indent, t.indent, dotField, strippedFieldName)) + t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = source[\"%s\"];", + t.indent, t.indent, dotField, strippedFieldName)) } } @@ -501,7 +507,7 @@ func (t *TypeScriptify) convertEnum(depth int, typeOf reflect.Type, elements []e } t.alreadyConverted[typeOf.String()] = true - entityName := t.Prefix + typeOf.Name() + t.Suffix + entityName := t.Prefix + t.nameTypeOf(typeOf) + t.Suffix result := "enum " + entityName + " {\n" for _, val := range elements { @@ -595,6 +601,15 @@ func (t *TypeScriptify) getJSONFieldName(field reflect.StructField, isPtr bool) return jsonFieldName } +func (t *TypeScriptify) nameTypeOf(typeOf reflect.Type) string { + tname := typeOf.Name() + if strings.Contains(tname, "[") { + tname = strings.ReplaceAll(tname, "[", "_") + tname = strings.ReplaceAll(tname, "]", "_") + } + return tname +} + func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode map[string]string) (string, error) { if _, found := t.alreadyConverted[typeOf.String()]; found { // Already converted return "", nil @@ -607,7 +622,7 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m t.alreadyConverted[typeOf.String()] = true - entityName := t.Prefix + typeOf.Name() + t.Suffix + entityName := t.Prefix + t.nameTypeOf(typeOf) + t.Suffix if typeClashWithReservedKeyword(entityName) { warnAboutTypesClash(entityName) @@ -807,8 +822,18 @@ type typeScriptClassBuilder struct { namespace string } +func (t *typeScriptClassBuilder) nameTypeOf(typeOf reflect.Type) string { + tname := typeOf.Name() + if strings.Contains(tname, "[") { + tname = strings.ReplaceAll(tname, "[", "_") + tname = strings.ReplaceAll(tname, "]", "_") + } + return tname +} + func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field reflect.StructField, arrayDepth int, opts TypeOptions) error { - fieldType, kind := field.Type.Elem().Name(), field.Type.Elem().Kind() + fieldType := t.nameTypeOf(field.Type.Elem()) + kind := field.Type.Elem().Kind() typeScriptType := t.types[kind] if len(fieldName) > 0 { @@ -828,7 +853,8 @@ func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field ref } func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect.StructField, opts TypeOptions) error { - fieldType, kind := field.Type.Name(), field.Type.Kind() + fieldType := t.nameTypeOf(field.Type) + kind := field.Type.Kind() typeScriptType := t.types[kind] if len(opts.TSType) > 0 { @@ -852,7 +878,7 @@ func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect. } func (t *typeScriptClassBuilder) AddEnumField(fieldName string, field reflect.StructField) { - fieldType := field.Type.Name() + fieldType := t.nameTypeOf(field.Type) t.addField(fieldName, t.prefix+fieldType+t.suffix, false) strippedFieldName := strings.ReplaceAll(fieldName, "?", "") t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) @@ -862,7 +888,7 @@ func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect. strippedFieldName := strings.ReplaceAll(fieldName, "?", "") classname := "null" namespace := strings.Split(field.Type.String(), ".")[0] - fqname := t.prefix + field.Type.Name() + t.suffix + fqname := t.prefix + t.nameTypeOf(field.Type) + t.suffix if namespace != t.namespace { fqname = namespace + "." + fqname } @@ -881,7 +907,7 @@ func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect. } func (t *typeScriptClassBuilder) AddArrayOfStructsField(fieldName string, field reflect.StructField, arrayDepth int) { - fieldType := field.Type.Elem().Name() + fieldType := t.nameTypeOf(field.Type.Elem()) if differentNamespaces(t.namespace, field.Type.Elem()) { fieldType = field.Type.Elem().String() } From 3fbb23048a78eb24d91c8c337f9d44dfb6b7c34b Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Wed, 1 Jan 2025 16:06:13 -0500 Subject: [PATCH 05/15] support for non-primitive generics also :) --- v2/internal/typescriptify/typescriptify.go | 58 ++++++++++------------ 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index 8d9e25912d0..d452bbdf73d 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -40,6 +40,20 @@ const ( jsVariableNameRegex = `^([A-Z]|[a-z]|\$|_)([A-Z]|[a-z]|[0-9]|\$|_)*$` ) +var ( + jsVariableUnsafeChars = regexp.MustCompile(`[^A-Za-z0-9_]`) +) + +func nameTypeOf(typeOf reflect.Type) string { + tname := typeOf.Name() + gidx := strings.IndexRune(tname, '[') + if gidx > 0 { // its a generic type + rem := strings.SplitN(tname, "[", 2) + tname = rem[0] + "_" + jsVariableUnsafeChars.ReplaceAllLiteralString(rem[1], "_") + } + return tname +} + // TypeOptions overrides options set by `ts_*` tags. type TypeOptions struct { TSType string @@ -261,15 +275,15 @@ func (t *TypeScriptify) AddType(typeOf reflect.Type) *TypeScriptify { func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.StructField) { keyType := field.Type.Key() valueType := field.Type.Elem() - valueTypeName := t.nameTypeOf(valueType) + valueTypeName := nameTypeOf(valueType) if name, ok := t.types[valueType.Kind()]; ok { valueTypeName = name } if valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Slice { - valueTypeName = t.nameTypeOf(valueType.Elem()) + "[]" + valueTypeName = nameTypeOf(valueType.Elem()) + "[]" } if valueType.Kind() == reflect.Ptr { - valueTypeName = t.nameTypeOf(valueType.Elem()) + valueTypeName = nameTypeOf(valueType.Elem()) } if valueType.Kind() == reflect.Map { // TODO: support nested maps @@ -507,7 +521,7 @@ func (t *TypeScriptify) convertEnum(depth int, typeOf reflect.Type, elements []e } t.alreadyConverted[typeOf.String()] = true - entityName := t.Prefix + t.nameTypeOf(typeOf) + t.Suffix + entityName := t.Prefix + nameTypeOf(typeOf) + t.Suffix result := "enum " + entityName + " {\n" for _, val := range elements { @@ -601,15 +615,6 @@ func (t *TypeScriptify) getJSONFieldName(field reflect.StructField, isPtr bool) return jsonFieldName } -func (t *TypeScriptify) nameTypeOf(typeOf reflect.Type) string { - tname := typeOf.Name() - if strings.Contains(tname, "[") { - tname = strings.ReplaceAll(tname, "[", "_") - tname = strings.ReplaceAll(tname, "]", "_") - } - return tname -} - func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode map[string]string) (string, error) { if _, found := t.alreadyConverted[typeOf.String()]; found { // Already converted return "", nil @@ -622,7 +627,7 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m t.alreadyConverted[typeOf.String()] = true - entityName := t.Prefix + t.nameTypeOf(typeOf) + t.Suffix + entityName := t.Prefix + nameTypeOf(typeOf) + t.Suffix if typeClashWithReservedKeyword(entityName) { warnAboutTypesClash(entityName) @@ -681,9 +686,9 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m } } - isKnownType := t.KnownStructs.Contains(getStructFQN(field.Type.String())) - println("KnownStructs:", t.KnownStructs.Join("\t")) - println(getStructFQN(field.Type.String())) + isKnownType := true // t.KnownStructs.Contains(getStructFQN(field.Type.String())) + //println("KnownStructs:", t.KnownStructs.Join("\t")) + //println(getStructFQN(field.Type.String())) builder.AddStructField(jsonFieldName, field, !isKnownType) } else if field.Type.Kind() == reflect.Map { t.logf(depth, "- map field %s.%s", typeOf.Name(), field.Name) @@ -822,17 +827,8 @@ type typeScriptClassBuilder struct { namespace string } -func (t *typeScriptClassBuilder) nameTypeOf(typeOf reflect.Type) string { - tname := typeOf.Name() - if strings.Contains(tname, "[") { - tname = strings.ReplaceAll(tname, "[", "_") - tname = strings.ReplaceAll(tname, "]", "_") - } - return tname -} - func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field reflect.StructField, arrayDepth int, opts TypeOptions) error { - fieldType := t.nameTypeOf(field.Type.Elem()) + fieldType := nameTypeOf(field.Type.Elem()) kind := field.Type.Elem().Kind() typeScriptType := t.types[kind] @@ -853,7 +849,7 @@ func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field ref } func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect.StructField, opts TypeOptions) error { - fieldType := t.nameTypeOf(field.Type) + fieldType := nameTypeOf(field.Type) kind := field.Type.Kind() typeScriptType := t.types[kind] @@ -878,7 +874,7 @@ func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect. } func (t *typeScriptClassBuilder) AddEnumField(fieldName string, field reflect.StructField) { - fieldType := t.nameTypeOf(field.Type) + fieldType := nameTypeOf(field.Type) t.addField(fieldName, t.prefix+fieldType+t.suffix, false) strippedFieldName := strings.ReplaceAll(fieldName, "?", "") t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) @@ -888,7 +884,7 @@ func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect. strippedFieldName := strings.ReplaceAll(fieldName, "?", "") classname := "null" namespace := strings.Split(field.Type.String(), ".")[0] - fqname := t.prefix + t.nameTypeOf(field.Type) + t.suffix + fqname := t.prefix + nameTypeOf(field.Type) + t.suffix if namespace != t.namespace { fqname = namespace + "." + fqname } @@ -907,7 +903,7 @@ func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect. } func (t *typeScriptClassBuilder) AddArrayOfStructsField(fieldName string, field reflect.StructField, arrayDepth int) { - fieldType := t.nameTypeOf(field.Type.Elem()) + fieldType := nameTypeOf(field.Type.Elem()) if differentNamespaces(t.namespace, field.Type.Elem()) { fieldType = field.Type.Elem().String() } From 48a826878893b1959f30f042376bc1a45837a2c5 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Wed, 1 Jan 2025 16:33:32 -0500 Subject: [PATCH 06/15] fix generic types in parameters / return args --- v2/internal/binding/generate.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go index 02a0bd29272..f1a15900079 100644 --- a/v2/internal/binding/generate.go +++ b/v2/internal/binding/generate.go @@ -223,8 +223,18 @@ func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) stri return arrayifyValue(valueArray, value) } +var ( + jsVariableUnsafeChars = regexp.MustCompile(`[^A-Za-z0-9_]`) +) + func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string { - return goTypeToJSDocType(input, importNamespaces) + tname := goTypeToJSDocType(input, importNamespaces) + gidx := strings.IndexRune(tname, '[') + if gidx > 0 { // its a generic type + rem := strings.SplitN(tname, "[", 2) + tname = rem[0] + "_" + jsVariableUnsafeChars.ReplaceAllLiteralString(rem[1], "_") + } + return tname } func entityFullReturnType(input, prefix, suffix string, importNamespaces *slicer.StringSlicer) string { From 82a49525d54e9817e4d038505329e105ac3901c9 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Wed, 1 Jan 2025 17:10:32 -0500 Subject: [PATCH 07/15] fixes a namespacing bug when mapping to pointer to struct --- v2/internal/typescriptify/typescriptify.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index d452bbdf73d..ec08130e24d 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -276,6 +276,9 @@ func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.Str keyType := field.Type.Key() valueType := field.Type.Elem() valueTypeName := nameTypeOf(valueType) + if valueType.Kind() == reflect.Ptr { + valueType = valueType.Elem() + } if name, ok := t.types[valueType.Kind()]; ok { valueTypeName = name } From 2d7ffaa37758a480ec1654e2dae4c349f68dc552 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Wed, 1 Jan 2025 19:54:32 -0500 Subject: [PATCH 08/15] fixing invalid knownstructs --- v2/internal/binding/binding.go | 4 ++-- v2/internal/typescriptify/typescriptify.go | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/v2/internal/binding/binding.go b/v2/internal/binding/binding.go index b7794876bf2..1c5a2cb488a 100644 --- a/v2/internal/binding/binding.go +++ b/v2/internal/binding/binding.go @@ -277,7 +277,7 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, continue } fqname := field.Type.String() - sNameSplit := strings.Split(fqname, ".") + sNameSplit := strings.SplitN(fqname, ".", 2) if len(sNameSplit) < 2 { continue } @@ -293,7 +293,7 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, continue } fqname := field.Type.Elem().String() - sNameSplit := strings.Split(fqname, ".") + sNameSplit := strings.SplitN(fqname, ".", 2) if len(sNameSplit) < 2 { continue } diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index ec08130e24d..c5bfe74cbe0 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -689,9 +689,11 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m } } - isKnownType := true // t.KnownStructs.Contains(getStructFQN(field.Type.String())) - //println("KnownStructs:", t.KnownStructs.Join("\t")) - //println(getStructFQN(field.Type.String())) + isKnownType := t.KnownStructs.Contains(getStructFQN(field.Type.String())) + if !isKnownType { + println("KnownStructs:", t.KnownStructs.Join("\t")) + println("Not found:", getStructFQN(field.Type.String())) + } builder.AddStructField(jsonFieldName, field, !isKnownType) } else if field.Type.Kind() == reflect.Map { t.logf(depth, "- map field %s.%s", typeOf.Name(), field.Name) @@ -953,7 +955,7 @@ func indentLines(str string, i int) string { func getStructFQN(in string) string { result := strings.ReplaceAll(in, "[]", "") - result = strings.ReplaceAll(result, "*", "") + //result = strings.ReplaceAll(result, "*", "") return result } From 9e26774cef9bf37d3e8cb69c9fd794467eb346c9 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Wed, 1 Jan 2025 22:36:05 -0500 Subject: [PATCH 09/15] found a place it mattered, pushing the star replacement to the generate side --- v2/internal/binding/generate.go | 1 + v2/internal/binding/reflect.go | 2 +- v2/internal/typescriptify/typescriptify.go | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go index f1a15900079..de07f8022f6 100644 --- a/v2/internal/binding/generate.go +++ b/v2/internal/binding/generate.go @@ -229,6 +229,7 @@ var ( func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string { tname := goTypeToJSDocType(input, importNamespaces) + tname = strings.ReplaceAll(tname, "*", "") gidx := strings.IndexRune(tname, '[') if gidx > 0 { // its a generic type rem := strings.SplitN(tname, "[", 2) diff --git a/v2/internal/binding/reflect.go b/v2/internal/binding/reflect.go index 57a6335bd5b..d293a743aa2 100644 --- a/v2/internal/binding/reflect.go +++ b/v2/internal/binding/reflect.go @@ -166,7 +166,7 @@ func getPackageName(in string) string { } func getSplitReturn(in string) (string, string) { - result := strings.Split(in, ".") + result := strings.SplitN(in, ".", 2) return result[0], result[1] } diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index c5bfe74cbe0..5ef61449caf 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -955,7 +955,7 @@ func indentLines(str string, i int) string { func getStructFQN(in string) string { result := strings.ReplaceAll(in, "[]", "") - //result = strings.ReplaceAll(result, "*", "") + result = strings.ReplaceAll(result, "*", "") return result } From 75fc0f5e1d6149c15eb4a6d8ae619bacca91661b Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Thu, 2 Jan 2025 01:02:56 -0500 Subject: [PATCH 10/15] descend as much as necessary to find structs caught these examples in http.Request.TLS: PeerCertificates []*x509.Certificate VerifiedChains [][]*x509.Certificate --- v2/internal/binding/binding.go | 39 +++++++++++----------- v2/internal/typescriptify/typescriptify.go | 11 ++++-- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/v2/internal/binding/binding.go b/v2/internal/binding/binding.go index 1c5a2cb488a..d2b437f2b12 100644 --- a/v2/internal/binding/binding.go +++ b/v2/internal/binding/binding.go @@ -262,20 +262,17 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, // Iterate this struct and add any struct field references structType := reflect.TypeOf(s) - if hasElements(structType) { + for hasElements(structType) { structType = structType.Elem() } for i := 0; i < structType.NumField(); i++ { field := structType.Field(i) - if field.Anonymous { + if field.Anonymous || !field.IsExported() { continue } kind := field.Type.Kind() if kind == reflect.Struct { - if !field.IsExported() { - continue - } fqname := field.Type.String() sNameSplit := strings.SplitN(fqname, ".", 2) if len(sNameSplit) < 2 { @@ -288,22 +285,24 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, s := reflect.Indirect(a).Interface() b.AddStructToGenerateTS(pName, sName, s) } - } else if hasElements(field.Type) && field.Type.Elem().Kind() == reflect.Struct { - if !field.IsExported() { - continue - } - fqname := field.Type.Elem().String() - sNameSplit := strings.SplitN(fqname, ".", 2) - if len(sNameSplit) < 2 { - continue + } else { + fType := field.Type + for hasElements(fType) { + fType = fType.Elem() } - sName := sNameSplit[1] - pName := getPackageName(fqname) - typ := field.Type.Elem() - a := reflect.New(typ) - if b.hasExportedJSONFields(typ) { - s := reflect.Indirect(a).Interface() - b.AddStructToGenerateTS(pName, sName, s) + if fType.Kind() == reflect.Struct { + fqname := fType.String() + sNameSplit := strings.SplitN(fqname, ".", 2) + if len(sNameSplit) < 2 { + continue + } + sName := sNameSplit[1] + pName := getPackageName(fqname) + a := reflect.New(fType) + if b.hasExportedJSONFields(fType) { + s := reflect.Indirect(a).Interface() + b.AddStructToGenerateTS(pName, sName, s) + } } } } diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index 5ef61449caf..23542eb3ea1 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -835,7 +835,10 @@ type typeScriptClassBuilder struct { func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field reflect.StructField, arrayDepth int, opts TypeOptions) error { fieldType := nameTypeOf(field.Type.Elem()) kind := field.Type.Elem().Kind() - typeScriptType := t.types[kind] + typeScriptType, ok := t.types[kind] + if !ok { + typeScriptType = "any" + } if len(fieldName) > 0 { strippedFieldName := strings.ReplaceAll(fieldName, "?", "") @@ -857,7 +860,11 @@ func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect. fieldType := nameTypeOf(field.Type) kind := field.Type.Kind() - typeScriptType := t.types[kind] + typeScriptType, ok := t.types[kind] + if !ok { + typeScriptType = "any" + } + if len(opts.TSType) > 0 { typeScriptType = opts.TSType } From 2813db45e810da06cd7f0b85434b273e862ac081 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Thu, 2 Jan 2025 01:12:26 -0500 Subject: [PATCH 11/15] accidently reverted other fix --- v2/internal/typescriptify/typescriptify.go | 1 + 1 file changed, 1 insertion(+) diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index 23542eb3ea1..d63cc9eb06c 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -278,6 +278,7 @@ func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.Str valueTypeName := nameTypeOf(valueType) if valueType.Kind() == reflect.Ptr { valueType = valueType.Elem() + valueTypeName = nameTypeOf(valueType) } if name, ok := t.types[valueType.Kind()]; ok { valueTypeName = name From bf6867f5c349f1afdc2a4c7d115a2a8823618272 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Fri, 3 Jan 2025 23:21:44 -0500 Subject: [PATCH 12/15] switch syntax for typescript record outputs prior syntax is primarily useful for naming keys so not useful here, and this syntax avoids square brackets in output which greatly simplifies generation for Go generics --- .../binding_test/binding_importedmap_test.go | 2 +- .../binding_nonstringmapkey_test.go | 2 +- .../binding_test/binding_type_alias_test.go | 4 +-- v2/internal/binding/generate.go | 26 +++++++++---------- v2/internal/binding/generate_test.go | 4 +-- v2/internal/typescriptify/typescriptify.go | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/v2/internal/binding/binding_test/binding_importedmap_test.go b/v2/internal/binding/binding_test/binding_importedmap_test.go index 7fa11d54b0e..4a4b2996c18 100644 --- a/v2/internal/binding/binding_test/binding_importedmap_test.go +++ b/v2/internal/binding/binding_test/binding_importedmap_test.go @@ -50,7 +50,7 @@ export namespace binding_test { export namespace binding_test_import { export class AMapWrapper { - AMap: {[key: string]: binding_test_nestedimport.A}; + AMap: Record; static createFrom(source: any = {}) { return new AMapWrapper(source); } diff --git a/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go b/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go index 37a61dd2963..9efee710ff3 100644 --- a/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go +++ b/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go @@ -18,7 +18,7 @@ var NonStringMapKeyTest = BindingTest{ want: ` export namespace binding_test { export class NonStringMapKey { - numberMap: {[key: number]: any}; + numberMap: Record; static createFrom(source: any = {}) { return new NonStringMapKey(source); } diff --git a/v2/internal/binding/binding_test/binding_type_alias_test.go b/v2/internal/binding/binding_test/binding_type_alias_test.go index 498c5976ceb..90b009c5f6c 100644 --- a/v2/internal/binding/binding_test/binding_type_alias_test.go +++ b/v2/internal/binding/binding_test/binding_type_alias_test.go @@ -15,11 +15,11 @@ const expectedTypeAliasBindings = `// Cynhyrchwyd y ffeil hon yn awtomatig. PEID import {binding_test} from '../models'; import {int_package} from '../models'; -export function Map():Promise<{[key: string]: string}>; +export function Map():Promise>; export function MapAlias():Promise; -export function MapWithImportedStructValue():Promise<{[key: string]: int_package.SomeStruct}>; +export function MapWithImportedStructValue():Promise>; export function Slice():Promise>; diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go index de07f8022f6..77edc983d76 100644 --- a/v2/internal/binding/generate.go +++ b/v2/internal/binding/generate.go @@ -171,7 +171,18 @@ func fullyQualifiedName(packageName string, typeName string) string { } } +var ( + jsVariableUnsafeChars = regexp.MustCompile(`[^A-Za-z0-9_]`) +) + func arrayifyValue(valueArray string, valueType string) string { + valueType = strings.ReplaceAll(valueType, "*", "") + gidx := strings.IndexRune(valueType, '[') + if gidx > 0 { // its a generic type + rem := strings.SplitN(valueType, "[", 2) + valueType = rem[0] + "_" + jsVariableUnsafeChars.ReplaceAllLiteralString(rem[1], "_") + } + if len(valueArray) == 0 { return valueType } @@ -217,25 +228,14 @@ func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) stri } if len(key) > 0 { - return fmt.Sprintf("{[key: %s]: %s}", key, arrayifyValue(valueArray, value)) + return fmt.Sprintf("Record<%s, %s>", key, arrayifyValue(valueArray, value)) } return arrayifyValue(valueArray, value) } -var ( - jsVariableUnsafeChars = regexp.MustCompile(`[^A-Za-z0-9_]`) -) - func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string { - tname := goTypeToJSDocType(input, importNamespaces) - tname = strings.ReplaceAll(tname, "*", "") - gidx := strings.IndexRune(tname, '[') - if gidx > 0 { // its a generic type - rem := strings.SplitN(tname, "[", 2) - tname = rem[0] + "_" + jsVariableUnsafeChars.ReplaceAllLiteralString(rem[1], "_") - } - return tname + return goTypeToJSDocType(input, importNamespaces) } func entityFullReturnType(input, prefix, suffix string, importNamespaces *slicer.StringSlicer) string { diff --git a/v2/internal/binding/generate_test.go b/v2/internal/binding/generate_test.go index 8d6a833b817..6a1f2a7b572 100644 --- a/v2/internal/binding/generate_test.go +++ b/v2/internal/binding/generate_test.go @@ -116,12 +116,12 @@ func Test_goTypeToJSDocType(t *testing.T) { { name: "map", input: "map[string]float64", - want: "{[key: string]: number}", + want: "Record", }, { name: "map", input: "map[string]map[string]float64", - want: "{[key: string]: {[key: string]: number}}", + want: "Record>", }, { name: "types", diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index d63cc9eb06c..f72bb79f432 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -316,7 +316,7 @@ func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.Str fieldName = fmt.Sprintf(`"%s"?`, strippedFieldName) } } - t.fields = append(t.fields, fmt.Sprintf("%s%s: {[key: %s]: %s};", t.indent, fieldName, keyTypeStr, valueTypeName)) + t.fields = append(t.fields, fmt.Sprintf("%s%s: Record<%s, %s>;", t.indent, fieldName, keyTypeStr, valueTypeName)) if valueType.Kind() == reflect.Struct { t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = this.convertValues(source[\"%s\"], %s, true);", t.indent, t.indent, dotField, strippedFieldName, t.prefix+valueTypeName+t.suffix)) From c0f22a73d75b4a4ed1d505e74cf19580c55238e4 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Sat, 4 Jan 2025 02:00:27 -0500 Subject: [PATCH 13/15] better handle edge cases for nested arrays and slices --- v2/internal/typescriptify/typescriptify.go | 29 ++++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index f72bb79f432..95376b2f4f8 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -276,18 +276,27 @@ func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.Str keyType := field.Type.Key() valueType := field.Type.Elem() valueTypeName := nameTypeOf(valueType) + valueTypeSuffix := "" if valueType.Kind() == reflect.Ptr { valueType = valueType.Elem() valueTypeName = nameTypeOf(valueType) } - if name, ok := t.types[valueType.Kind()]; ok { - valueTypeName = name - } if valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Slice { - valueTypeName = nameTypeOf(valueType.Elem()) + "[]" + arrayDepth := 1 + for valueType.Elem().Kind() == reflect.Array || valueType.Elem().Kind() == reflect.Slice { + valueType = valueType.Elem() + arrayDepth++ + } + valueType = valueType.Elem() + valueTypeName = nameTypeOf(valueType) + valueTypeSuffix = strings.Repeat("[]", arrayDepth) } if valueType.Kind() == reflect.Ptr { - valueTypeName = nameTypeOf(valueType.Elem()) + valueType = valueType.Elem() + valueTypeName = nameTypeOf(valueType) + } + if name, ok := t.types[valueType.Kind()]; ok { + valueTypeName = name } if valueType.Kind() == reflect.Map { // TODO: support nested maps @@ -316,10 +325,10 @@ func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.Str fieldName = fmt.Sprintf(`"%s"?`, strippedFieldName) } } - t.fields = append(t.fields, fmt.Sprintf("%s%s: Record<%s, %s>;", t.indent, fieldName, keyTypeStr, valueTypeName)) + t.fields = append(t.fields, fmt.Sprintf("%s%s: Record<%s, %s>;", t.indent, fieldName, keyTypeStr, valueTypeName+valueTypeSuffix)) if valueType.Kind() == reflect.Struct { t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = this.convertValues(source[\"%s\"], %s, true);", - t.indent, t.indent, dotField, strippedFieldName, t.prefix+valueTypeName+t.suffix)) + t.indent, t.indent, dotField, strippedFieldName, t.prefix+valueTypeName+valueTypeSuffix+t.suffix)) } else { t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = source[\"%s\"];", t.indent, t.indent, dotField, strippedFieldName)) @@ -740,11 +749,15 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m } arrayDepth := 1 - for field.Type.Elem().Kind() == reflect.Slice { // Slice of slices: + for field.Type.Elem().Kind() == reflect.Slice || field.Type.Elem().Kind() == reflect.Array { // Slice of slices: field.Type = field.Type.Elem() arrayDepth++ } + if field.Type.Elem().Kind() == reflect.Ptr { // extract ptr type + field.Type = field.Type.Elem() + } + if field.Type.Elem().Kind() == reflect.Struct { // Slice of structs: t.logf(depth, "- struct slice %s.%s (%s)", typeOf.Name(), field.Name, field.Type.String()) typeScriptChunk, err := t.convertType(depth+1, field.Type.Elem(), customCode) From 203abce1a539e074deac4a0ae705ad8537a70fd0 Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Sat, 4 Jan 2025 02:01:22 -0500 Subject: [PATCH 14/15] lots o tests --- .../binding_test/binding_deepelements_test.go | 126 ++++++++++++++ .../binding_test/binding_generics_test.go | 154 ++++++++++++++++++ .../binding_test/binding_ignored_test.go | 47 ++++++ .../binding/binding_test/binding_test.go | 4 + v2/internal/binding/generate_test.go | 10 ++ 5 files changed, 341 insertions(+) create mode 100644 v2/internal/binding/binding_test/binding_deepelements_test.go create mode 100644 v2/internal/binding/binding_test/binding_generics_test.go create mode 100644 v2/internal/binding/binding_test/binding_ignored_test.go diff --git a/v2/internal/binding/binding_test/binding_deepelements_test.go b/v2/internal/binding/binding_test/binding_deepelements_test.go new file mode 100644 index 00000000000..488c58f6d13 --- /dev/null +++ b/v2/internal/binding/binding_test/binding_deepelements_test.go @@ -0,0 +1,126 @@ +package binding_test + +// Issues 2303, 3442, 3709 + +type DeepMessage struct { + Msg string +} + +type DeepElements struct { + Single []int + Double [][]string + FourDouble [4][]float64 + DoubleFour [][4]int64 + Triple [][][]int + + SingleMap map[string]int + SliceMap map[string][]int + DoubleSliceMap map[string][][]int + + ArrayMap map[string][4]int + DoubleArrayMap1 map[string][4][]int + DoubleArrayMap2 map[string][][4]int + DoubleArrayMap3 map[string][4][4]int + + OneStructs []*DeepMessage + TwoStructs [3][]*DeepMessage + ThreeStructs [][][]DeepMessage + MapStructs map[string][]*DeepMessage + MapTwoStructs map[string][4][]DeepMessage + MapThreeStructs map[string][][7][]*DeepMessage +} + +func (x DeepElements) Get() DeepElements { + return x +} + +var DeepElementsTest = BindingTest{ + name: "DeepElements", + structs: []interface{}{ + &DeepElements{}, + }, + exemptions: nil, + shouldError: false, + want: ` +export namespace binding_test { + + export class DeepMessage { + Msg: string; + + static createFrom(source: any = {}) { + return new DeepMessage(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.Msg = source["Msg"]; + } + } + export class DeepElements { + Single: number[]; + Double: string[][]; + FourDouble: number[][]; + DoubleFour: number[][]; + Triple: number[][][]; + SingleMap: Record; + SliceMap: Record; + DoubleSliceMap: Record; + ArrayMap: Record; + DoubleArrayMap1: Record; + DoubleArrayMap2: Record; + DoubleArrayMap3: Record; + OneStructs: DeepMessage[]; + TwoStructs: DeepMessage[][]; + ThreeStructs: DeepMessage[][][]; + MapStructs: Record; + MapTwoStructs: Record; + MapThreeStructs: Record; + + static createFrom(source: any = {}) { + return new DeepElements(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.Single = source["Single"]; + this.Double = source["Double"]; + this.FourDouble = source["FourDouble"]; + this.DoubleFour = source["DoubleFour"]; + this.Triple = source["Triple"]; + this.SingleMap = source["SingleMap"]; + this.SliceMap = source["SliceMap"]; + this.DoubleSliceMap = source["DoubleSliceMap"]; + this.ArrayMap = source["ArrayMap"]; + this.DoubleArrayMap1 = source["DoubleArrayMap1"]; + this.DoubleArrayMap2 = source["DoubleArrayMap2"]; + this.DoubleArrayMap3 = source["DoubleArrayMap3"]; + this.OneStructs = this.convertValues(source["OneStructs"], DeepMessage); + this.TwoStructs = this.convertValues(source["TwoStructs"], DeepMessage); + this.ThreeStructs = this.convertValues(source["ThreeStructs"], DeepMessage); + this.MapStructs = this.convertValues(source["MapStructs"], DeepMessage[], true); + this.MapTwoStructs = this.convertValues(source["MapTwoStructs"], DeepMessage[][], true); + this.MapThreeStructs = this.convertValues(source["MapThreeStructs"], DeepMessage[][][], true); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + + } +`, +} diff --git a/v2/internal/binding/binding_test/binding_generics_test.go b/v2/internal/binding/binding_test/binding_generics_test.go new file mode 100644 index 00000000000..920bd2a7acf --- /dev/null +++ b/v2/internal/binding/binding_test/binding_generics_test.go @@ -0,0 +1,154 @@ +package binding_test + +import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/float_package" + +// Issues 3900, 3371, 2323 (no TS generics though) + +type ListData[T interface{}] struct { + Total int64 `json:"Total"` + TotalPage int64 `json:"TotalPage"` + PageNum int `json:"PageNum"` + List []T `json:"List,omitempty"` +} + +func (x ListData[T]) Get() ListData[T] { + return x +} + +var Generics1Test = BindingTest{ + name: "Generics1", + structs: []interface{}{ + &ListData[string]{}, + }, + exemptions: nil, + shouldError: false, + want: ` +export namespace binding_test { + + export class ListData_string_ { + Total: number; + TotalPage: number; + PageNum: number; + List?: string[]; + + static createFrom(source: any = {}) { + return new ListData_string_(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.Total = source["Total"]; + this.TotalPage = source["TotalPage"]; + this.PageNum = source["PageNum"]; + this.List = source["List"]; + } + } + + } +`, +} + +var Generics2Test = BindingTest{ + name: "Generics2", + structs: []interface{}{ + &ListData[float_package.SomeStruct]{}, + &ListData[*float_package.SomeStruct]{}, + }, + exemptions: nil, + shouldError: false, + want: ` +export namespace binding_test { + + export class ListData__github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_ { + Total: number; + TotalPage: number; + PageNum: number; + List?: float_package.SomeStruct[]; + + static createFrom(source: any = {}) { + return new ListData__github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.Total = source["Total"]; + this.TotalPage = source["TotalPage"]; + this.PageNum = source["PageNum"]; + this.List = this.convertValues(source["List"], float_package.SomeStruct); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + export class ListData_github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_ { + Total: number; + TotalPage: number; + PageNum: number; + List?: float_package.SomeStruct[]; + + static createFrom(source: any = {}) { + return new ListData_github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.Total = source["Total"]; + this.TotalPage = source["TotalPage"]; + this.PageNum = source["PageNum"]; + this.List = this.convertValues(source["List"], float_package.SomeStruct); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + + } + + export namespace float_package { + + export class SomeStruct { + string: string; + + static createFrom(source: any = {}) { + return new SomeStruct(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.string = source["string"]; + } + } + + } +`, +} diff --git a/v2/internal/binding/binding_test/binding_ignored_test.go b/v2/internal/binding/binding_test/binding_ignored_test.go new file mode 100644 index 00000000000..aeb6a9c3f0b --- /dev/null +++ b/v2/internal/binding/binding_test/binding_ignored_test.go @@ -0,0 +1,47 @@ +package binding_test + +import ( + "unsafe" +) + +// Issues 3755, 3809 + +type Ignored struct { + Valid bool + Total func() int `json:"Total"` + UnsafeP unsafe.Pointer + Complex64 complex64 `json:"Complex"` + Complex128 complex128 + StringChan chan string +} + +func (x Ignored) Get() Ignored { + return x +} + +var IgnoredTest = BindingTest{ + name: "Ignored", + structs: []interface{}{ + &Ignored{}, + }, + exemptions: nil, + shouldError: false, + want: ` +export namespace binding_test { + + export class Ignored { + Valid: boolean; + + static createFrom(source: any = {}) { + return new Ignored(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.Valid = source["Valid"]; + } + } + + } +`, +} diff --git a/v2/internal/binding/binding_test/binding_test.go b/v2/internal/binding/binding_test/binding_test.go index 32ebbe05663..d358d8b0f38 100644 --- a/v2/internal/binding/binding_test/binding_test.go +++ b/v2/internal/binding/binding_test/binding_test.go @@ -51,6 +51,10 @@ func TestBindings_GenerateModels(t *testing.T) { SpecialCharacterFieldTest, WithoutFieldsTest, NoFieldTagsTest, + Generics1Test, + Generics2Test, + IgnoredTest, + DeepElementsTest, } testLogger := &logger.Logger{} diff --git a/v2/internal/binding/generate_test.go b/v2/internal/binding/generate_test.go index 6a1f2a7b572..26d7c70dfe2 100644 --- a/v2/internal/binding/generate_test.go +++ b/v2/internal/binding/generate_test.go @@ -128,6 +128,16 @@ func Test_goTypeToJSDocType(t *testing.T) { input: "main.SomeType", want: "main.SomeType", }, + { + name: "primitive_generic", + input: "main.ListData[string]", + want: "main.ListData_string_", + }, + { + name: "stdlib_generic", + input: "main.ListData[*net/http.Request]", + want: "main.ListData_net_http_Request_", + }, } var importNamespaces slicer.StringSlicer for _, tt := range tests { From 0248bd81477e4eed28ccf9d8ea3eec94964d6ddd Mon Sep 17 00:00:00 2001 From: Jeremy Jay Date: Sat, 11 Jan 2025 23:46:52 -0500 Subject: [PATCH 15/15] update changelog --- website/src/pages/changelog.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index 2e148afb4e4..9ba4eb43d44 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - chore: fix some comments in [PR](https://github.com/wailsapp/wails/pull/3932) by @lvyaoting - [windows] Fixed frameless window flickering when minimizing/restoring by preventing unnecessary redraws [#3951](https://github.com/wailsapp/wails/issues/3951) - Fixed failed models.ts build due to non-json-encodable Go types [PR](https://github.com/wailsapp/wails/pull/3975) by [@pbnjay](https://github.com/pbnjay) +- Fixed more binding and typescript export bugs [PR](https://github.com/wailsapp/wails/pull/3978) by [@pbnjay](https://github.com/pbnjay) ### Changed - Allow to specify macos-min-version externally. Implemented by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/3756)