mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 07:21:32 +08:00
Fix miscellaneous bindings and typescript export bugs (#3978)
* Do not attempt to export fields that cannot be json-encoded * update changelog w/ PR * also skip UnsafePointers * WIP to allow conversion from Go generic types to typescript * support for non-primitive generics also :) * fix generic types in parameters / return args * fixes a namespacing bug when mapping to pointer to struct * fixing invalid knownstructs * found a place it mattered, pushing the star replacement to the generate side * descend as much as necessary to find structs caught these examples in http.Request.TLS: PeerCertificates []*x509.Certificate VerifiedChains [][]*x509.Certificate * accidently reverted other fix * 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 * better handle edge cases for nested arrays and slices * lots o tests * update changelog --------- Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
parent
d9b99a990d
commit
c4fdfd6415
@ -262,22 +262,19 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string,
|
|||||||
|
|
||||||
// Iterate this struct and add any struct field references
|
// Iterate this struct and add any struct field references
|
||||||
structType := reflect.TypeOf(s)
|
structType := reflect.TypeOf(s)
|
||||||
if hasElements(structType) {
|
for hasElements(structType) {
|
||||||
structType = structType.Elem()
|
structType = structType.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < structType.NumField(); i++ {
|
for i := 0; i < structType.NumField(); i++ {
|
||||||
field := structType.Field(i)
|
field := structType.Field(i)
|
||||||
if field.Anonymous {
|
if field.Anonymous || !field.IsExported() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
kind := field.Type.Kind()
|
kind := field.Type.Kind()
|
||||||
if kind == reflect.Struct {
|
if kind == reflect.Struct {
|
||||||
if !field.IsExported() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fqname := field.Type.String()
|
fqname := field.Type.String()
|
||||||
sNameSplit := strings.Split(fqname, ".")
|
sNameSplit := strings.SplitN(fqname, ".", 2)
|
||||||
if len(sNameSplit) < 2 {
|
if len(sNameSplit) < 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -288,22 +285,24 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string,
|
|||||||
s := reflect.Indirect(a).Interface()
|
s := reflect.Indirect(a).Interface()
|
||||||
b.AddStructToGenerateTS(pName, sName, s)
|
b.AddStructToGenerateTS(pName, sName, s)
|
||||||
}
|
}
|
||||||
} else if hasElements(field.Type) && field.Type.Elem().Kind() == reflect.Struct {
|
} else {
|
||||||
if !field.IsExported() {
|
fType := field.Type
|
||||||
continue
|
for hasElements(fType) {
|
||||||
|
fType = fType.Elem()
|
||||||
}
|
}
|
||||||
fqname := field.Type.Elem().String()
|
if fType.Kind() == reflect.Struct {
|
||||||
sNameSplit := strings.Split(fqname, ".")
|
fqname := fType.String()
|
||||||
if len(sNameSplit) < 2 {
|
sNameSplit := strings.SplitN(fqname, ".", 2)
|
||||||
continue
|
if len(sNameSplit) < 2 {
|
||||||
}
|
continue
|
||||||
sName := sNameSplit[1]
|
}
|
||||||
pName := getPackageName(fqname)
|
sName := sNameSplit[1]
|
||||||
typ := field.Type.Elem()
|
pName := getPackageName(fqname)
|
||||||
a := reflect.New(typ)
|
a := reflect.New(fType)
|
||||||
if b.hasExportedJSONFields(typ) {
|
if b.hasExportedJSONFields(fType) {
|
||||||
s := reflect.Indirect(a).Interface()
|
s := reflect.Indirect(a).Interface()
|
||||||
b.AddStructToGenerateTS(pName, sName, s)
|
b.AddStructToGenerateTS(pName, sName, s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
126
v2/internal/binding/binding_test/binding_deepelements_test.go
Normal file
126
v2/internal/binding/binding_test/binding_deepelements_test.go
Normal file
@ -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<string, number>;
|
||||||
|
SliceMap: Record<string, number[]>;
|
||||||
|
DoubleSliceMap: Record<string, number[][]>;
|
||||||
|
ArrayMap: Record<string, number[]>;
|
||||||
|
DoubleArrayMap1: Record<string, number[][]>;
|
||||||
|
DoubleArrayMap2: Record<string, number[][]>;
|
||||||
|
DoubleArrayMap3: Record<string, number[][]>;
|
||||||
|
OneStructs: DeepMessage[];
|
||||||
|
TwoStructs: DeepMessage[][];
|
||||||
|
ThreeStructs: DeepMessage[][][];
|
||||||
|
MapStructs: Record<string, DeepMessage[]>;
|
||||||
|
MapTwoStructs: Record<string, DeepMessage[][]>;
|
||||||
|
MapThreeStructs: Record<string, DeepMessage[][][]>;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
154
v2/internal/binding/binding_test/binding_generics_test.go
Normal file
154
v2/internal/binding/binding_test/binding_generics_test.go
Normal file
@ -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"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
47
v2/internal/binding/binding_test/binding_ignored_test.go
Normal file
47
v2/internal/binding/binding_test/binding_ignored_test.go
Normal file
@ -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"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
@ -50,7 +50,7 @@ export namespace binding_test {
|
|||||||
|
|
||||||
export namespace binding_test_import {
|
export namespace binding_test_import {
|
||||||
export class AMapWrapper {
|
export class AMapWrapper {
|
||||||
AMap: {[key: string]: binding_test_nestedimport.A};
|
AMap: Record<string, binding_test_nestedimport.A>;
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new AMapWrapper(source);
|
return new AMapWrapper(source);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ var NonStringMapKeyTest = BindingTest{
|
|||||||
want: `
|
want: `
|
||||||
export namespace binding_test {
|
export namespace binding_test {
|
||||||
export class NonStringMapKey {
|
export class NonStringMapKey {
|
||||||
numberMap: {[key: number]: any};
|
numberMap: Record<number, any>;
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new NonStringMapKey(source);
|
return new NonStringMapKey(source);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,10 @@ func TestBindings_GenerateModels(t *testing.T) {
|
|||||||
SpecialCharacterFieldTest,
|
SpecialCharacterFieldTest,
|
||||||
WithoutFieldsTest,
|
WithoutFieldsTest,
|
||||||
NoFieldTagsTest,
|
NoFieldTagsTest,
|
||||||
|
Generics1Test,
|
||||||
|
Generics2Test,
|
||||||
|
IgnoredTest,
|
||||||
|
DeepElementsTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
testLogger := &logger.Logger{}
|
testLogger := &logger.Logger{}
|
||||||
|
@ -15,11 +15,11 @@ const expectedTypeAliasBindings = `// Cynhyrchwyd y ffeil hon yn awtomatig. PEID
|
|||||||
import {binding_test} from '../models';
|
import {binding_test} from '../models';
|
||||||
import {int_package} from '../models';
|
import {int_package} from '../models';
|
||||||
|
|
||||||
export function Map():Promise<{[key: string]: string}>;
|
export function Map():Promise<Record<string, string>>;
|
||||||
|
|
||||||
export function MapAlias():Promise<binding_test.MapAlias>;
|
export function MapAlias():Promise<binding_test.MapAlias>;
|
||||||
|
|
||||||
export function MapWithImportedStructValue():Promise<{[key: string]: int_package.SomeStruct}>;
|
export function MapWithImportedStructValue():Promise<Record<string, int_package.SomeStruct>>;
|
||||||
|
|
||||||
export function Slice():Promise<Array<string>>;
|
export function Slice():Promise<Array<string>>;
|
||||||
|
|
||||||
|
@ -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 {
|
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 {
|
if len(valueArray) == 0 {
|
||||||
return valueType
|
return valueType
|
||||||
}
|
}
|
||||||
@ -217,7 +228,7 @@ func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(key) > 0 {
|
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)
|
return arrayifyValue(valueArray, value)
|
||||||
|
@ -116,18 +116,28 @@ func Test_goTypeToJSDocType(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "map",
|
name: "map",
|
||||||
input: "map[string]float64",
|
input: "map[string]float64",
|
||||||
want: "{[key: string]: number}",
|
want: "Record<string, number>",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "map",
|
name: "map",
|
||||||
input: "map[string]map[string]float64",
|
input: "map[string]map[string]float64",
|
||||||
want: "{[key: string]: {[key: string]: number}}",
|
want: "Record<string, Record<string, number>>",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "types",
|
name: "types",
|
||||||
input: "main.SomeType",
|
input: "main.SomeType",
|
||||||
want: "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
|
var importNamespaces slicer.StringSlicer
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -166,7 +166,7 @@ func getPackageName(in string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getSplitReturn(in string) (string, string) {
|
func getSplitReturn(in string) (string, string) {
|
||||||
result := strings.Split(in, ".")
|
result := strings.SplitN(in, ".", 2)
|
||||||
return result[0], result[1]
|
return result[0], result[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,20 @@ const (
|
|||||||
jsVariableNameRegex = `^([A-Z]|[a-z]|\$|_)([A-Z]|[a-z]|[0-9]|\$|_)*$`
|
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.
|
// TypeOptions overrides options set by `ts_*` tags.
|
||||||
type TypeOptions struct {
|
type TypeOptions struct {
|
||||||
TSType string
|
TSType string
|
||||||
@ -261,15 +275,32 @@ func (t *TypeScriptify) AddType(typeOf reflect.Type) *TypeScriptify {
|
|||||||
func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.StructField) {
|
func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.StructField) {
|
||||||
keyType := field.Type.Key()
|
keyType := field.Type.Key()
|
||||||
valueType := field.Type.Elem()
|
valueType := field.Type.Elem()
|
||||||
valueTypeName := valueType.Name()
|
valueTypeName := nameTypeOf(valueType)
|
||||||
|
valueTypeSuffix := ""
|
||||||
|
if valueType.Kind() == reflect.Ptr {
|
||||||
|
valueType = valueType.Elem()
|
||||||
|
valueTypeName = nameTypeOf(valueType)
|
||||||
|
}
|
||||||
|
if valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Slice {
|
||||||
|
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 {
|
||||||
|
valueType = valueType.Elem()
|
||||||
|
valueTypeName = nameTypeOf(valueType)
|
||||||
|
}
|
||||||
if name, ok := t.types[valueType.Kind()]; ok {
|
if name, ok := t.types[valueType.Kind()]; ok {
|
||||||
valueTypeName = name
|
valueTypeName = name
|
||||||
}
|
}
|
||||||
if valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Slice {
|
if valueType.Kind() == reflect.Map {
|
||||||
valueTypeName = valueType.Elem().Name() + "[]"
|
// TODO: support nested maps
|
||||||
}
|
valueTypeName = "any" // valueType.Elem().Name()
|
||||||
if valueType.Kind() == reflect.Ptr {
|
|
||||||
valueTypeName = valueType.Elem().Name()
|
|
||||||
}
|
}
|
||||||
if valueType.Kind() == reflect.Struct && differentNamespaces(t.namespace, valueType) {
|
if valueType.Kind() == reflect.Struct && differentNamespaces(t.namespace, valueType) {
|
||||||
valueTypeName = valueType.String()
|
valueTypeName = valueType.String()
|
||||||
@ -294,11 +325,13 @@ func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.Str
|
|||||||
fieldName = fmt.Sprintf(`"%s"?`, strippedFieldName)
|
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+valueTypeSuffix))
|
||||||
if valueType.Kind() == reflect.Struct {
|
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+valueTypeSuffix+t.suffix))
|
||||||
} else {
|
} 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 +534,7 @@ func (t *TypeScriptify) convertEnum(depth int, typeOf reflect.Type, elements []e
|
|||||||
}
|
}
|
||||||
t.alreadyConverted[typeOf.String()] = true
|
t.alreadyConverted[typeOf.String()] = true
|
||||||
|
|
||||||
entityName := t.Prefix + typeOf.Name() + t.Suffix
|
entityName := t.Prefix + nameTypeOf(typeOf) + t.Suffix
|
||||||
result := "enum " + entityName + " {\n"
|
result := "enum " + entityName + " {\n"
|
||||||
|
|
||||||
for _, val := range elements {
|
for _, val := range elements {
|
||||||
@ -607,7 +640,7 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m
|
|||||||
|
|
||||||
t.alreadyConverted[typeOf.String()] = true
|
t.alreadyConverted[typeOf.String()] = true
|
||||||
|
|
||||||
entityName := t.Prefix + typeOf.Name() + t.Suffix
|
entityName := t.Prefix + nameTypeOf(typeOf) + t.Suffix
|
||||||
|
|
||||||
if typeClashWithReservedKeyword(entityName) {
|
if typeClashWithReservedKeyword(entityName) {
|
||||||
warnAboutTypesClash(entityName)
|
warnAboutTypesClash(entityName)
|
||||||
@ -667,8 +700,10 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m
|
|||||||
}
|
}
|
||||||
|
|
||||||
isKnownType := t.KnownStructs.Contains(getStructFQN(field.Type.String()))
|
isKnownType := t.KnownStructs.Contains(getStructFQN(field.Type.String()))
|
||||||
println("KnownStructs:", t.KnownStructs.Join("\t"))
|
if !isKnownType {
|
||||||
println(getStructFQN(field.Type.String()))
|
println("KnownStructs:", t.KnownStructs.Join("\t"))
|
||||||
|
println("Not found:", getStructFQN(field.Type.String()))
|
||||||
|
}
|
||||||
builder.AddStructField(jsonFieldName, field, !isKnownType)
|
builder.AddStructField(jsonFieldName, field, !isKnownType)
|
||||||
} else if field.Type.Kind() == reflect.Map {
|
} else if field.Type.Kind() == reflect.Map {
|
||||||
t.logf(depth, "- map field %s.%s", typeOf.Name(), field.Name)
|
t.logf(depth, "- map field %s.%s", typeOf.Name(), field.Name)
|
||||||
@ -714,11 +749,15 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m
|
|||||||
}
|
}
|
||||||
|
|
||||||
arrayDepth := 1
|
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()
|
field.Type = field.Type.Elem()
|
||||||
arrayDepth++
|
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:
|
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())
|
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)
|
typeScriptChunk, err := t.convertType(depth+1, field.Type.Elem(), customCode)
|
||||||
@ -808,8 +847,12 @@ type typeScriptClassBuilder struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field reflect.StructField, arrayDepth int, opts TypeOptions) error {
|
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())
|
||||||
typeScriptType := t.types[kind]
|
kind := field.Type.Elem().Kind()
|
||||||
|
typeScriptType, ok := t.types[kind]
|
||||||
|
if !ok {
|
||||||
|
typeScriptType = "any"
|
||||||
|
}
|
||||||
|
|
||||||
if len(fieldName) > 0 {
|
if len(fieldName) > 0 {
|
||||||
strippedFieldName := strings.ReplaceAll(fieldName, "?", "")
|
strippedFieldName := strings.ReplaceAll(fieldName, "?", "")
|
||||||
@ -828,9 +871,14 @@ func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field ref
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect.StructField, opts TypeOptions) error {
|
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, ok := t.types[kind]
|
||||||
|
if !ok {
|
||||||
|
typeScriptType = "any"
|
||||||
|
}
|
||||||
|
|
||||||
typeScriptType := t.types[kind]
|
|
||||||
if len(opts.TSType) > 0 {
|
if len(opts.TSType) > 0 {
|
||||||
typeScriptType = opts.TSType
|
typeScriptType = opts.TSType
|
||||||
}
|
}
|
||||||
@ -852,7 +900,7 @@ func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *typeScriptClassBuilder) AddEnumField(fieldName string, field reflect.StructField) {
|
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)
|
t.addField(fieldName, t.prefix+fieldType+t.suffix, false)
|
||||||
strippedFieldName := strings.ReplaceAll(fieldName, "?", "")
|
strippedFieldName := strings.ReplaceAll(fieldName, "?", "")
|
||||||
t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName))
|
t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName))
|
||||||
@ -862,7 +910,7 @@ func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect.
|
|||||||
strippedFieldName := strings.ReplaceAll(fieldName, "?", "")
|
strippedFieldName := strings.ReplaceAll(fieldName, "?", "")
|
||||||
classname := "null"
|
classname := "null"
|
||||||
namespace := strings.Split(field.Type.String(), ".")[0]
|
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 {
|
if namespace != t.namespace {
|
||||||
fqname = namespace + "." + fqname
|
fqname = namespace + "." + fqname
|
||||||
}
|
}
|
||||||
@ -881,7 +929,7 @@ func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *typeScriptClassBuilder) AddArrayOfStructsField(fieldName string, field reflect.StructField, arrayDepth int) {
|
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()) {
|
if differentNamespaces(t.namespace, field.Type.Elem()) {
|
||||||
fieldType = field.Type.Elem().String()
|
fieldType = field.Type.Elem().String()
|
||||||
}
|
}
|
||||||
|
@ -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
|
- 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)
|
- [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 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
|
### Changed
|
||||||
- Allow to specify macos-min-version externally. Implemented by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/3756)
|
- Allow to specify macos-min-version externally. Implemented by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/3756)
|
||||||
|
Loading…
Reference in New Issue
Block a user