From 96a5c4bcb57088fe19c729ceb0bf0b1f8e3d59d2 Mon Sep 17 00:00:00 2001 From: atterpac Date: Wed, 28 Jan 2026 10:14:35 -0700 Subject: [PATCH 1/6] add nullable to slices in structs --- v2/internal/typescriptify/typescriptify.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index e732c5976..805ef45dc 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -863,7 +863,7 @@ func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field ref t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) return nil } else if len(typeScriptType) > 0 { - t.addField(fieldName, fmt.Sprint(typeScriptType, strings.Repeat("[]", arrayDepth)), false) + t.addField(fieldName, fmt.Sprint(typeScriptType, strings.Repeat("[]", arrayDepth), " | null"), false) t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) return nil } @@ -936,7 +936,7 @@ func (t *typeScriptClassBuilder) AddArrayOfStructsField(fieldName string, field fieldType = field.Type.Elem().String() } strippedFieldName := strings.ReplaceAll(fieldName, "?", "") - t.addField(fieldName, fmt.Sprint(t.prefix+fieldType+t.suffix, strings.Repeat("[]", arrayDepth)), false) + t.addField(fieldName, fmt.Sprint(t.prefix+fieldType+t.suffix, strings.Repeat("[]", arrayDepth), " | null"), false) t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("this.convertValues(source[\"%s\"], %s)", strippedFieldName, t.prefix+fieldType+t.suffix)) } From 2f328d90b25772f04f3f587611a41c81088bc5ce Mon Sep 17 00:00:00 2001 From: atterpac Date: Wed, 28 Jan 2026 10:23:43 -0700 Subject: [PATCH 2/6] add nullable in function declaration for arrays --- v2/internal/binding/generate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go index 77edc983d..42933d4e3 100644 --- a/v2/internal/binding/generate.go +++ b/v2/internal/binding/generate.go @@ -187,7 +187,7 @@ func arrayifyValue(valueArray string, valueType string) string { return valueType } - return "Array<" + valueType + ">" + return "Array<" + valueType + "> | null" } func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) string { From 034f5c5584fb5ef9fced8ec5e52483892c71d51f Mon Sep 17 00:00:00 2001 From: atterpac Date: Wed, 28 Jan 2026 10:24:00 -0700 Subject: [PATCH 3/6] update test outputs to reflect nullable --- .../binding_test/binding_deepelements_test.go | 16 ++++++++-------- .../binding_test/binding_generics_test.go | 6 +++--- .../binding_test/binding_importedslice_test.go | 2 +- .../binding_returned_promises_test.go | 4 ++-- .../binding_test/binding_type_alias_test.go | 4 ++-- v2/internal/binding/generate_test.go | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/v2/internal/binding/binding_test/binding_deepelements_test.go b/v2/internal/binding/binding_test/binding_deepelements_test.go index 034687474..9d127946c 100644 --- a/v2/internal/binding/binding_test/binding_deepelements_test.go +++ b/v2/internal/binding/binding_test/binding_deepelements_test.go @@ -57,11 +57,11 @@ export namespace binding_test { } } export class DeepElements { - Single: number[]; - Double: string[][]; - FourDouble: number[][]; - DoubleFour: number[][]; - Triple: number[][][]; + Single: number[] | null; + Double: string[][] | null; + FourDouble: number[][] | null; + DoubleFour: number[][] | null; + Triple: number[][][] | null; SingleMap: Record; SliceMap: Record>; DoubleSliceMap: Record>>; @@ -69,9 +69,9 @@ export namespace binding_test { DoubleArrayMap1: Record>>; DoubleArrayMap2: Record>>; DoubleArrayMap3: Record>>; - OneStructs: DeepMessage[]; - TwoStructs: DeepMessage[][]; - ThreeStructs: DeepMessage[][][]; + OneStructs: DeepMessage[] | null; + TwoStructs: DeepMessage[][] | null; + ThreeStructs: DeepMessage[][][] | null; MapStructs: Record>; MapTwoStructs: Record>>; MapThreeStructs: Record>>>; diff --git a/v2/internal/binding/binding_test/binding_generics_test.go b/v2/internal/binding/binding_test/binding_generics_test.go index 920bd2a7a..12f1626ec 100644 --- a/v2/internal/binding/binding_test/binding_generics_test.go +++ b/v2/internal/binding/binding_test/binding_generics_test.go @@ -29,7 +29,7 @@ export namespace binding_test { Total: number; TotalPage: number; PageNum: number; - List?: string[]; + List?: string[] | null; static createFrom(source: any = {}) { return new ListData_string_(source); @@ -63,7 +63,7 @@ export namespace binding_test { Total: number; TotalPage: number; PageNum: number; - List?: float_package.SomeStruct[]; + List?: float_package.SomeStruct[] | null; static createFrom(source: any = {}) { return new ListData__github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_(source); @@ -99,7 +99,7 @@ export namespace binding_test { Total: number; TotalPage: number; PageNum: number; - List?: float_package.SomeStruct[]; + List?: float_package.SomeStruct[] | null; static createFrom(source: any = {}) { return new ListData_github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_(source); diff --git a/v2/internal/binding/binding_test/binding_importedslice_test.go b/v2/internal/binding/binding_test/binding_importedslice_test.go index 5abf55b43..c84904d41 100644 --- a/v2/internal/binding/binding_test/binding_importedslice_test.go +++ b/v2/internal/binding/binding_test/binding_importedslice_test.go @@ -50,7 +50,7 @@ export namespace binding_test { export namespace binding_test_import { export class ASliceWrapper { - ASlice: binding_test_nestedimport.A[]; + ASlice: binding_test_nestedimport.A[] | null; static createFrom(source: any = {}) { return new ASliceWrapper(source); } diff --git a/v2/internal/binding/binding_test/binding_returned_promises_test.go b/v2/internal/binding/binding_test/binding_returned_promises_test.go index 94941d0a3..da703273b 100644 --- a/v2/internal/binding/binding_test/binding_returned_promises_test.go +++ b/v2/internal/binding/binding_test/binding_returned_promises_test.go @@ -23,9 +23,9 @@ export function SingleReturnStruct(arg1:any):Promise; -export function SingleReturnStructPointerSlice(arg1:any):Promise>; +export function SingleReturnStructPointerSlice(arg1:any):Promise | null>; -export function SingleReturnStructSlice(arg1:any):Promise>; +export function SingleReturnStructSlice(arg1:any):Promise | null>; export function SingleReturnWithError(arg1:number):Promise; 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 90b009c5f..32fbccf6f 100644 --- a/v2/internal/binding/binding_test/binding_type_alias_test.go +++ b/v2/internal/binding/binding_test/binding_type_alias_test.go @@ -21,9 +21,9 @@ export function MapAlias():Promise; export function MapWithImportedStructValue():Promise>; -export function Slice():Promise>; +export function Slice():Promise | null>; -export function SliceImportedStruct():Promise>; +export function SliceImportedStruct():Promise | null>; ` type AliasTest struct{} diff --git a/v2/internal/binding/generate_test.go b/v2/internal/binding/generate_test.go index 26d7c70df..6450a4486 100644 --- a/v2/internal/binding/generate_test.go +++ b/v2/internal/binding/generate_test.go @@ -101,12 +101,12 @@ func Test_goTypeToJSDocType(t *testing.T) { { name: "[]int", input: "[]int", - want: "Array", + want: "Array | null", }, { name: "[]bool", input: "[]bool", - want: "Array", + want: "Array | null", }, { name: "anything else", From b6113d78aad52274853abbcc2b4689a84207d4b5 Mon Sep 17 00:00:00 2001 From: atterpac Date: Wed, 28 Jan 2026 10:36:52 -0700 Subject: [PATCH 4/6] useNullableSlices option --- v2/cmd/wails/generate.go | 11 ++++--- v2/internal/app/app_bindings.go | 11 +++++++ v2/internal/binding/binding.go | 16 +++++++--- v2/internal/binding/generate.go | 33 +++++++++----------- v2/internal/project/project.go | 7 +++-- v2/internal/typescriptify/typescriptify.go | 36 ++++++++++++++++------ v2/pkg/commands/bindings/bindings.go | 20 +++++++----- v2/pkg/commands/build/build.go | 13 ++++---- 8 files changed, 93 insertions(+), 54 deletions(-) diff --git a/v2/cmd/wails/generate.go b/v2/cmd/wails/generate.go index 15a6b33d8..2c022d5be 100644 --- a/v2/cmd/wails/generate.go +++ b/v2/cmd/wails/generate.go @@ -48,11 +48,12 @@ func generateModule(f *flags.GenerateModule) error { } _, err = bindings.GenerateBindings(bindings.Options{ - Compiler: f.Compiler, - Tags: buildTags, - TsPrefix: projectConfig.Bindings.TsGeneration.Prefix, - TsSuffix: projectConfig.Bindings.TsGeneration.Suffix, - TsOutputType: projectConfig.Bindings.TsGeneration.OutputType, + Compiler: f.Compiler, + Tags: buildTags, + TsPrefix: projectConfig.Bindings.TsGeneration.Prefix, + TsSuffix: projectConfig.Bindings.TsGeneration.Suffix, + TsOutputType: projectConfig.Bindings.TsGeneration.OutputType, + UseNullableSlices: projectConfig.Bindings.TsGeneration.UseNullableSlices, }) if err != nil { return err diff --git a/v2/internal/app/app_bindings.go b/v2/internal/app/app_bindings.go index be031819c..ab6e56e0a 100644 --- a/v2/internal/app/app_bindings.go +++ b/v2/internal/app/app_bindings.go @@ -32,6 +32,7 @@ func (a *App) Run() error { var tsPrefixFlag *string var tsPostfixFlag *string var tsOutputTypeFlag *string + var useNullableSlicesFlag *bool tsPrefix := os.Getenv("tsprefix") if tsPrefix == "" { @@ -48,6 +49,12 @@ func (a *App) Run() error { tsOutputTypeFlag = bindingFlags.String("tsoutputtype", "", "Output type for generated typescript entities (classes|interfaces)") } + useNullableSlicesEnv := os.Getenv("usenullableslices") + useNullableSlices := useNullableSlicesEnv == "true" + if useNullableSlicesEnv == "" { + useNullableSlicesFlag = bindingFlags.Bool("usenullableslices", false, "Generate nullable slice types (Type[] | null)") + } + _ = bindingFlags.Parse(os.Args[1:]) if tsPrefixFlag != nil { tsPrefix = *tsPrefixFlag @@ -58,12 +65,16 @@ func (a *App) Run() error { if tsOutputTypeFlag != nil { tsOutputType = *tsOutputTypeFlag } + if useNullableSlicesFlag != nil { + useNullableSlices = *useNullableSlicesFlag + } appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated(), a.options.EnumBind) appBindings.SetTsPrefix(tsPrefix) appBindings.SetTsSuffix(tsSuffix) appBindings.SetOutputType(tsOutputType) + appBindings.SetUseNullableSlices(useNullableSlices) err := generateBindings(appBindings) if err != nil { diff --git a/v2/internal/binding/binding.go b/v2/internal/binding/binding.go index b7bf07ae0..126b4d5b4 100644 --- a/v2/internal/binding/binding.go +++ b/v2/internal/binding/binding.go @@ -25,10 +25,11 @@ type Bindings struct { structsToGenerateTS map[string]map[string]interface{} enumsToGenerateTS map[string]map[string]interface{} - tsPrefix string - tsSuffix string - tsInterface bool - obfuscate bool + tsPrefix string + tsSuffix string + tsInterface bool + obfuscate bool + useNullableSlices bool } // NewBindings returns a new Bindings object @@ -101,6 +102,7 @@ func (b *Bindings) GenerateModels() ([]byte, error) { w.WithPrefix(b.tsPrefix) w.WithSuffix(b.tsSuffix) w.WithInterface(b.tsInterface) + w.WithUseNullableSlices(b.useNullableSlices) w.Namespace = packageName w.WithBackupDir("") w.KnownStructs = allStructNames @@ -161,6 +163,7 @@ func (b *Bindings) GenerateModels() ([]byte, error) { w.WithPrefix(b.tsPrefix) w.WithSuffix(b.tsSuffix) w.WithInterface(b.tsInterface) + w.WithUseNullableSlices(b.useNullableSlices) w.Namespace = packageName w.WithBackupDir("") @@ -328,6 +331,11 @@ func (b *Bindings) SetOutputType(outputType string) *Bindings { return b } +func (b *Bindings) SetUseNullableSlices(v bool) *Bindings { + b.useNullableSlices = v + return b +} + func (b *Bindings) getAllStructNames() *slicer.StringSlicer { var result slicer.StringSlicer for packageName, structsToGenerate := range b.structsToGenerateTS { diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go index 42933d4e3..d57149053 100644 --- a/v2/internal/binding/generate.go +++ b/v2/internal/binding/generate.go @@ -92,7 +92,7 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error { for count, input := range methodDetails.Inputs { arg := fmt.Sprintf("arg%d", count+1) entityName := entityFullReturnType(input.TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces) - args.Add(arg + ":" + goTypeToTypescriptType(entityName, &importNamespaces)) + args.Add(arg + ":" + b.goTypeToTypescriptType(entityName, &importNamespaces)) } tsBody.WriteString(args.Join(",") + "):") // now build Typescript return types @@ -108,11 +108,11 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error { returnType = "Promise" } else { outputTypeName := entityFullReturnType(methodDetails.Outputs[0].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces) - firstType := goTypeToTypescriptType(outputTypeName, &importNamespaces) + firstType := b.goTypeToTypescriptType(outputTypeName, &importNamespaces) returnType = "Promise<" + firstType if methodDetails.OutputCount() == 2 && methodDetails.Outputs[1].TypeName != "error" { outputTypeName = entityFullReturnType(methodDetails.Outputs[1].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces) - secondType := goTypeToTypescriptType(outputTypeName, &importNamespaces) + secondType := b.goTypeToTypescriptType(outputTypeName, &importNamespaces) returnType += "|" + secondType } returnType += ">" @@ -175,7 +175,7 @@ var ( jsVariableUnsafeChars = regexp.MustCompile(`[^A-Za-z0-9_]`) ) -func arrayifyValue(valueArray string, valueType string) string { +func arrayifyValue(valueArray string, valueType string, useNullableSlices bool) string { valueType = strings.ReplaceAll(valueType, "*", "") gidx := strings.IndexRune(valueType, '[') if gidx > 0 { // its a generic type @@ -187,23 +187,20 @@ func arrayifyValue(valueArray string, valueType string) string { return valueType } - return "Array<" + valueType + "> | null" + result := "Array<" + valueType + ">" + if useNullableSlices { + result += " | null" + } + return result } -func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) string { +func (b *Bindings) 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" { @@ -222,20 +219,20 @@ func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) stri key := fullyQualifiedName(keyPackage, keyType) var value string if strings.HasPrefix(valueType, "map") { - value = goTypeToJSDocType(valueType, importNamespaces) + value = b.goTypeToJSDocType(valueType, importNamespaces) } else { value = fullyQualifiedName(valuePackage, valueType) } if len(key) > 0 { - return fmt.Sprintf("Record<%s, %s>", key, arrayifyValue(valueArray, value)) + return fmt.Sprintf("Record<%s, %s>", key, arrayifyValue(valueArray, value, b.useNullableSlices)) } - return arrayifyValue(valueArray, value) + return arrayifyValue(valueArray, value, b.useNullableSlices) } -func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string { - return goTypeToJSDocType(input, importNamespaces) +func (b *Bindings) goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string { + return b.goTypeToJSDocType(input, importNamespaces) } func entityFullReturnType(input, prefix, suffix string, importNamespaces *slicer.StringSlicer) string { diff --git a/v2/internal/project/project.go b/v2/internal/project/project.go index 2df99bdfa..9c96ffafb 100644 --- a/v2/internal/project/project.go +++ b/v2/internal/project/project.go @@ -251,9 +251,10 @@ type Bindings struct { } type TsGeneration struct { - Prefix string `json:"prefix"` - Suffix string `json:"suffix"` - OutputType string `json:"outputType"` + Prefix string `json:"prefix"` + Suffix string `json:"suffix"` + OutputType string `json:"outputType"` + UseNullableSlices bool `json:"useNullableSlices"` } // Parse the given JSON data into a Project struct diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index 805ef45dc..a5c37314f 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -116,9 +116,10 @@ type TypeScriptify struct { // throwaway, used when converting alreadyConverted map[string]bool - Namespace string - KnownStructs *slicer.StringSlicer - KnownEnums *slicer.StringSlicer + Namespace string + KnownStructs *slicer.StringSlicer + KnownEnums *slicer.StringSlicer + UseNullableSlices bool } func New() *TypeScriptify { @@ -253,6 +254,11 @@ func (t *TypeScriptify) WithSuffix(s string) *TypeScriptify { return t } +func (t *TypeScriptify) WithUseNullableSlices(v bool) *TypeScriptify { + t.UseNullableSlices = v + return t +} + func (t *TypeScriptify) Add(obj interface{}) *TypeScriptify { switch ty := obj.(type) { case StructType: @@ -658,11 +664,12 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m result = "export " + result } builder := typeScriptClassBuilder{ - types: t.kinds, - indent: t.Indent, - prefix: t.Prefix, - suffix: t.Suffix, - namespace: t.Namespace, + types: t.kinds, + indent: t.Indent, + prefix: t.Prefix, + suffix: t.Suffix, + namespace: t.Namespace, + useNullableSlices: t.UseNullableSlices, } for _, field := range fields { @@ -846,6 +853,7 @@ type typeScriptClassBuilder struct { constructorBody []string prefix, suffix string namespace string + useNullableSlices bool } func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field reflect.StructField, arrayDepth int, opts TypeOptions) error { @@ -863,7 +871,11 @@ func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field ref t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) return nil } else if len(typeScriptType) > 0 { - t.addField(fieldName, fmt.Sprint(typeScriptType, strings.Repeat("[]", arrayDepth), " | null"), false) + nullableSuffix := "" + if t.useNullableSlices { + nullableSuffix = " | null" + } + t.addField(fieldName, fmt.Sprint(typeScriptType, strings.Repeat("[]", arrayDepth), nullableSuffix), false) t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) return nil } @@ -936,7 +948,11 @@ func (t *typeScriptClassBuilder) AddArrayOfStructsField(fieldName string, field fieldType = field.Type.Elem().String() } strippedFieldName := strings.ReplaceAll(fieldName, "?", "") - t.addField(fieldName, fmt.Sprint(t.prefix+fieldType+t.suffix, strings.Repeat("[]", arrayDepth), " | null"), false) + nullableSuffix := "" + if t.useNullableSlices { + nullableSuffix = " | null" + } + t.addField(fieldName, fmt.Sprint(t.prefix+fieldType+t.suffix, strings.Repeat("[]", arrayDepth), nullableSuffix), false) t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("this.convertValues(source[\"%s\"], %s)", strippedFieldName, t.prefix+fieldType+t.suffix)) } diff --git a/v2/pkg/commands/bindings/bindings.go b/v2/pkg/commands/bindings/bindings.go index 82ce0d58f..c7cf8ec28 100644 --- a/v2/pkg/commands/bindings/bindings.go +++ b/v2/pkg/commands/bindings/bindings.go @@ -15,14 +15,15 @@ import ( // Options for generating bindings type Options struct { - Filename string - Tags []string - ProjectDirectory string - Compiler string - GoModTidy bool - TsPrefix string - TsSuffix string - TsOutputType string + Filename string + Tags []string + ProjectDirectory string + Compiler string + GoModTidy bool + TsPrefix string + TsSuffix string + TsOutputType string + UseNullableSlices bool } // GenerateBindings generates bindings for the Wails project in the given ProjectDirectory. @@ -83,6 +84,9 @@ func GenerateBindings(options Options) (string, error) { env = shell.SetEnv(env, "tsprefix", options.TsPrefix) env = shell.SetEnv(env, "tssuffix", options.TsSuffix) env = shell.SetEnv(env, "tsoutputtype", options.TsOutputType) + if options.UseNullableSlices { + env = shell.SetEnv(env, "usenullableslices", "true") + } stdout, stderr, err = shell.RunCommandWithEnv(env, workingDirectory, filename) if err != nil { diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go index 7263f63ae..8ed134cbe 100644 --- a/v2/pkg/commands/build/build.go +++ b/v2/pkg/commands/build/build.go @@ -231,12 +231,13 @@ func GenerateBindings(buildOptions *Options) error { // Generate Bindings output, err := bindings.GenerateBindings(bindings.Options{ - Compiler: buildOptions.Compiler, - Tags: buildOptions.UserTags, - GoModTidy: !buildOptions.SkipModTidy, - TsPrefix: buildOptions.ProjectData.Bindings.TsGeneration.Prefix, - TsSuffix: buildOptions.ProjectData.Bindings.TsGeneration.Suffix, - TsOutputType: buildOptions.ProjectData.Bindings.TsGeneration.OutputType, + Compiler: buildOptions.Compiler, + Tags: buildOptions.UserTags, + GoModTidy: !buildOptions.SkipModTidy, + TsPrefix: buildOptions.ProjectData.Bindings.TsGeneration.Prefix, + TsSuffix: buildOptions.ProjectData.Bindings.TsGeneration.Suffix, + TsOutputType: buildOptions.ProjectData.Bindings.TsGeneration.OutputType, + UseNullableSlices: buildOptions.ProjectData.Bindings.TsGeneration.UseNullableSlices, }) if err != nil { return err From 9ebc00058229d378917103e9028888feb844bb7f Mon Sep 17 00:00:00 2001 From: atterpac Date: Wed, 28 Jan 2026 10:37:41 -0700 Subject: [PATCH 5/6] tests --- .../binding_test/binding_deepelements_test.go | 16 ++++---- .../binding_test/binding_generics_test.go | 6 +-- .../binding_importedslice_test.go | 2 +- .../binding_returned_promises_test.go | 4 +- .../binding_test/binding_type_alias_test.go | 4 +- v2/internal/binding/generate_test.go | 40 +++++++++++++++++-- 6 files changed, 53 insertions(+), 19 deletions(-) diff --git a/v2/internal/binding/binding_test/binding_deepelements_test.go b/v2/internal/binding/binding_test/binding_deepelements_test.go index 9d127946c..034687474 100644 --- a/v2/internal/binding/binding_test/binding_deepelements_test.go +++ b/v2/internal/binding/binding_test/binding_deepelements_test.go @@ -57,11 +57,11 @@ export namespace binding_test { } } export class DeepElements { - Single: number[] | null; - Double: string[][] | null; - FourDouble: number[][] | null; - DoubleFour: number[][] | null; - Triple: number[][][] | null; + Single: number[]; + Double: string[][]; + FourDouble: number[][]; + DoubleFour: number[][]; + Triple: number[][][]; SingleMap: Record; SliceMap: Record>; DoubleSliceMap: Record>>; @@ -69,9 +69,9 @@ export namespace binding_test { DoubleArrayMap1: Record>>; DoubleArrayMap2: Record>>; DoubleArrayMap3: Record>>; - OneStructs: DeepMessage[] | null; - TwoStructs: DeepMessage[][] | null; - ThreeStructs: DeepMessage[][][] | null; + OneStructs: DeepMessage[]; + TwoStructs: DeepMessage[][]; + ThreeStructs: DeepMessage[][][]; MapStructs: Record>; MapTwoStructs: Record>>; MapThreeStructs: Record>>>; diff --git a/v2/internal/binding/binding_test/binding_generics_test.go b/v2/internal/binding/binding_test/binding_generics_test.go index 12f1626ec..920bd2a7a 100644 --- a/v2/internal/binding/binding_test/binding_generics_test.go +++ b/v2/internal/binding/binding_test/binding_generics_test.go @@ -29,7 +29,7 @@ export namespace binding_test { Total: number; TotalPage: number; PageNum: number; - List?: string[] | null; + List?: string[]; static createFrom(source: any = {}) { return new ListData_string_(source); @@ -63,7 +63,7 @@ export namespace binding_test { Total: number; TotalPage: number; PageNum: number; - List?: float_package.SomeStruct[] | null; + 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); @@ -99,7 +99,7 @@ export namespace binding_test { Total: number; TotalPage: number; PageNum: number; - List?: float_package.SomeStruct[] | null; + 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); diff --git a/v2/internal/binding/binding_test/binding_importedslice_test.go b/v2/internal/binding/binding_test/binding_importedslice_test.go index c84904d41..5abf55b43 100644 --- a/v2/internal/binding/binding_test/binding_importedslice_test.go +++ b/v2/internal/binding/binding_test/binding_importedslice_test.go @@ -50,7 +50,7 @@ export namespace binding_test { export namespace binding_test_import { export class ASliceWrapper { - ASlice: binding_test_nestedimport.A[] | null; + ASlice: binding_test_nestedimport.A[]; static createFrom(source: any = {}) { return new ASliceWrapper(source); } diff --git a/v2/internal/binding/binding_test/binding_returned_promises_test.go b/v2/internal/binding/binding_test/binding_returned_promises_test.go index da703273b..94941d0a3 100644 --- a/v2/internal/binding/binding_test/binding_returned_promises_test.go +++ b/v2/internal/binding/binding_test/binding_returned_promises_test.go @@ -23,9 +23,9 @@ export function SingleReturnStruct(arg1:any):Promise; -export function SingleReturnStructPointerSlice(arg1:any):Promise | null>; +export function SingleReturnStructPointerSlice(arg1:any):Promise>; -export function SingleReturnStructSlice(arg1:any):Promise | null>; +export function SingleReturnStructSlice(arg1:any):Promise>; export function SingleReturnWithError(arg1:number):Promise; 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 32fbccf6f..90b009c5f 100644 --- a/v2/internal/binding/binding_test/binding_type_alias_test.go +++ b/v2/internal/binding/binding_test/binding_type_alias_test.go @@ -21,9 +21,9 @@ export function MapAlias():Promise; export function MapWithImportedStructValue():Promise>; -export function Slice():Promise | null>; +export function Slice():Promise>; -export function SliceImportedStruct():Promise | null>; +export function SliceImportedStruct():Promise>; ` type AliasTest struct{} diff --git a/v2/internal/binding/generate_test.go b/v2/internal/binding/generate_test.go index 6450a4486..5ceb7d0cd 100644 --- a/v2/internal/binding/generate_test.go +++ b/v2/internal/binding/generate_test.go @@ -101,12 +101,12 @@ func Test_goTypeToJSDocType(t *testing.T) { { name: "[]int", input: "[]int", - want: "Array | null", + want: "Array", }, { name: "[]bool", input: "[]bool", - want: "Array | null", + want: "Array", }, { name: "anything else", @@ -139,10 +139,44 @@ func Test_goTypeToJSDocType(t *testing.T) { want: "main.ListData_net_http_Request_", }, } + b := &Bindings{} var importNamespaces slicer.StringSlicer for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := goTypeToJSDocType(tt.input, &importNamespaces); got != tt.want { + if got := b.goTypeToJSDocType(tt.input, &importNamespaces); got != tt.want { + t.Errorf("goTypeToJSDocType() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_goTypeToJSDocType_nullable(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + { + name: "[]int nullable", + input: "[]int", + want: "Array | null", + }, + { + name: "[]bool nullable", + input: "[]bool", + want: "Array | null", + }, + { + name: "[]byte still string", + input: "[]byte", + want: "string", + }, + } + b := &Bindings{useNullableSlices: true} + var importNamespaces slicer.StringSlicer + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := b.goTypeToJSDocType(tt.input, &importNamespaces); got != tt.want { t.Errorf("goTypeToJSDocType() = %v, want %v", got, tt.want) } }) From faa87bc83072399cde61f385e79b063d49879b13 Mon Sep 17 00:00:00 2001 From: atterpac Date: Wed, 28 Jan 2026 10:59:19 -0700 Subject: [PATCH 6/6] 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 01fa47a69..9d1a28d0c 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added `nullableSlices` opt-in to allow nullable array type generation in [#4920](https://github.com/wailsapp/wails/pull/4920) - Add origin verification for bindings by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4480) - Configure Vite timeout by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4374) - Added `ContentProtection` option to allow hiding the application window from screen sharing software [#4241](https://github.com/wailsapp/wails/pull/4241) by [@Taiterbase](https://github.com/Taiterbase)