Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix miscellaneous bindings and typescript export bugs #3978

Merged
merged 19 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions v2/internal/binding/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line 284 throws away the rest, we want to keep them

if len(sNameSplit) < 2 {
continue
}
Expand All @@ -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
}
Expand Down Expand Up @@ -350,6 +350,14 @@ 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.UnsafePointer ||
f.Type.Kind() == reflect.Complex128 ||
f.Type.Kind() == reflect.Complex64 {
continue
}
jsonTag, hasTag := f.Tag.Lookup("json")
if !hasTag && f.IsExported() {
return true
Expand Down
1 change: 1 addition & 0 deletions v2/internal/binding/binding_test/binding_notags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type NoFieldTags struct {
Address string
Zip *string
Spouse *NoFieldTags
NoFunc func() string
}

func (n NoFieldTags) Get() NoFieldTags {
Expand Down
13 changes: 12 additions & 1 deletion v2/internal/binding/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,19 @@ 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)
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
}

func entityFullReturnType(input, prefix, suffix string, importNamespaces *slicer.StringSlicer) string {
Expand Down
2 changes: 1 addition & 1 deletion v2/internal/binding/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
Comment on lines +169 to 171
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation to prevent panic

The current implementation will panic if the input string doesn't contain a "." character. Consider adding validation:

func getSplitReturn(in string) (string, string) {
	result := strings.SplitN(in, ".", 2)
+	if len(result) != 2 {
+		return in, ""
+	}
	return result[0], result[1]
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result := strings.SplitN(in, ".", 2)
return result[0], result[1]
}
func getSplitReturn(in string) (string, string) {
result := strings.SplitN(in, ".", 2)
if len(result) != 2 {
return in, ""
}
return result[0], result[1]
}


Expand Down
63 changes: 49 additions & 14 deletions v2/internal/typescriptify/typescriptify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -261,15 +275,22 @@ 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 := nameTypeOf(valueType)
if valueType.Kind() == reflect.Ptr {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

matches Ptr unwrapping everywhere else in this file

without this, a map value that is a pointer to a struct looses its namespace #3709

valueType = valueType.Elem()
}
if name, ok := t.types[valueType.Kind()]; ok {
valueTypeName = name
}
if valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Slice {
valueTypeName = valueType.Elem().Name() + "[]"
valueTypeName = nameTypeOf(valueType.Elem()) + "[]"
}
if valueType.Kind() == reflect.Ptr {
valueTypeName = valueType.Elem().Name()
valueTypeName = nameTypeOf(valueType.Elem())
}
if valueType.Kind() == reflect.Map {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previously there was no reflect.Map here, so valueTypeName defaulted to "" and caused invalid typescript with e.g. nested map[string]map[string]string.

any here gives valid code at least, future work to recursively generate?

// TODO: support nested maps
valueTypeName = "any" // valueType.Elem().Name()
}
if valueType.Kind() == reflect.Struct && differentNamespaces(t.namespace, valueType) {
valueTypeName = valueType.String()
Expand All @@ -296,9 +317,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))
}
}

Expand Down Expand Up @@ -501,7 +524,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 + nameTypeOf(typeOf) + t.Suffix
result := "enum " + entityName + " {\n"

for _, val := range elements {
Expand Down Expand Up @@ -553,6 +576,14 @@ 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.UnsafePointer ||
field.Type.Kind() == reflect.Complex128 ||
field.Type.Kind() == reflect.Complex64 {
return ""
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

LGTM! Added essential JSON encoding safety checks.

The changes prevent JSON serialization of types that cannot be properly encoded (channels, functions, complex numbers, etc.), improving type safety and preventing runtime errors.

jsonTag, hasTag := field.Tag.Lookup("json")
if !hasTag && field.IsExported() {
jsonFieldName = field.Name
Expand Down Expand Up @@ -599,7 +630,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 + nameTypeOf(typeOf) + t.Suffix

if typeClashWithReservedKeyword(entityName) {
warnAboutTypesClash(entityName)
Expand Down Expand Up @@ -659,8 +690,10 @@ 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()))
if !isKnownType {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very loud with a lot of structs. Quieting it unless it misses something

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)
Expand Down Expand Up @@ -800,7 +833,8 @@ type typeScriptClassBuilder struct {
}

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 := nameTypeOf(field.Type.Elem())
kind := field.Type.Elem().Kind()
typeScriptType := t.types[kind]

if len(fieldName) > 0 {
Expand All @@ -820,7 +854,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 := nameTypeOf(field.Type)
kind := field.Type.Kind()

typeScriptType := t.types[kind]
if len(opts.TSType) > 0 {
Expand All @@ -844,7 +879,7 @@ func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect.
}

func (t *typeScriptClassBuilder) AddEnumField(fieldName string, field reflect.StructField) {
fieldType := field.Type.Name()
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))
Expand All @@ -854,7 +889,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 + nameTypeOf(field.Type) + t.suffix
if namespace != t.namespace {
fqname = namespace + "." + fqname
}
Expand All @@ -873,7 +908,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 := nameTypeOf(field.Type.Elem())
if differentNamespaces(t.namespace, field.Type.Elem()) {
fieldType = field.Type.Elem().String()
}
Expand Down
2 changes: 1 addition & 1 deletion website/src/pages/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down