mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 23:51:44 +08:00
Fix binding generation special cases (#1902)
* Make binding.go easier to test * Fix non-deterministic namespace order for bindings * Add binding tests * Fix nested import structs, non-string map keys, and escape invalid variable names Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
parent
de49b1f125
commit
40e326a708
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/typescriptify"
|
||||
@ -83,7 +84,7 @@ func (b *Bindings) ToJSON() (string, error) {
|
||||
return b.db.ToJSON()
|
||||
}
|
||||
|
||||
func (b *Bindings) WriteModels(modelsDir string) error {
|
||||
func (b *Bindings) GenerateModels() ([]byte, error) {
|
||||
models := map[string]string{}
|
||||
var seen slicer.StringSlicer
|
||||
allStructNames := b.getAllStructNames()
|
||||
@ -102,15 +103,23 @@ func (b *Bindings) WriteModels(modelsDir string) error {
|
||||
}
|
||||
str, err := w.Convert(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
thisPackageCode += str
|
||||
seen.AddSlice(w.GetGeneratedStructs())
|
||||
models[packageName] = thisPackageCode
|
||||
}
|
||||
|
||||
// Sort the package names first to make the output deterministic
|
||||
sortedPackageNames := make([]string, 0)
|
||||
for packageName := range models {
|
||||
sortedPackageNames = append(sortedPackageNames, packageName)
|
||||
}
|
||||
sort.Strings(sortedPackageNames)
|
||||
|
||||
var modelsData bytes.Buffer
|
||||
for packageName, modelData := range models {
|
||||
for _, packageName := range sortedPackageNames {
|
||||
modelData := models[packageName]
|
||||
if strings.TrimSpace(modelData) == "" {
|
||||
continue
|
||||
}
|
||||
@ -121,14 +130,22 @@ func (b *Bindings) WriteModels(modelsDir string) error {
|
||||
}
|
||||
modelsData.WriteString("\n}\n\n")
|
||||
}
|
||||
return modelsData.Bytes(), nil
|
||||
}
|
||||
|
||||
func (b *Bindings) WriteModels(modelsDir string) error {
|
||||
|
||||
modelsData, err := b.GenerateModels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Don't write if we don't have anything
|
||||
if len(modelsData.Bytes()) == 0 {
|
||||
if len(modelsData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
filename := filepath.Join(modelsDir, "models.ts")
|
||||
err := os.WriteFile(filename, modelsData.Bytes(), 0755)
|
||||
err = os.WriteFile(filename, modelsData, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -147,7 +164,7 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string,
|
||||
|
||||
// Iterate this struct and add any struct field references
|
||||
structType := reflect.TypeOf(s)
|
||||
if structType.Kind() == reflect.Ptr {
|
||||
if hasElements(structType) {
|
||||
structType = structType.Elem()
|
||||
}
|
||||
|
||||
@ -169,11 +186,11 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string,
|
||||
s := reflect.Indirect(a).Interface()
|
||||
b.AddStructToGenerateTS(pName, sName, s)
|
||||
}
|
||||
} else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
|
||||
} else if hasElements(field.Type) && field.Type.Elem().Kind() == reflect.Struct {
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
fqname := field.Type.String()
|
||||
fqname := field.Type.Elem().String()
|
||||
sName := strings.Split(fqname, ".")[1]
|
||||
pName := getPackageName(fqname)
|
||||
typ := field.Type.Elem()
|
||||
|
32
v2/internal/binding/binding_test/binding_escapedname_test.go
Normal file
32
v2/internal/binding/binding_test/binding_escapedname_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package binding_test
|
||||
|
||||
type EscapedName struct {
|
||||
Name string `json:"n.a.m.e"`
|
||||
}
|
||||
|
||||
func (s EscapedName) Get() EscapedName {
|
||||
return s
|
||||
}
|
||||
|
||||
var EscapedNameTest = BindingTest{
|
||||
name: "EscapedName",
|
||||
structs: []interface{}{
|
||||
&EscapedName{},
|
||||
},
|
||||
exemptions: nil,
|
||||
shouldError: false,
|
||||
want: `
|
||||
export namespace binding_test {
|
||||
export class EscapedName {
|
||||
"n.a.m.e": string;
|
||||
static createFrom(source: any = {}) {
|
||||
return new EscapedName(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this["n.a.m.e"] = source["n.a.m.e"];
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
94
v2/internal/binding/binding_test/binding_importedmap_test.go
Normal file
94
v2/internal/binding/binding_test/binding_importedmap_test.go
Normal file
@ -0,0 +1,94 @@
|
||||
package binding_test
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import"
|
||||
|
||||
type ImportedMap struct {
|
||||
AMapWrapperContainer binding_test_import.AMapWrapper `json:"AMapWrapperContainer"`
|
||||
}
|
||||
|
||||
func (s ImportedMap) Get() ImportedMap {
|
||||
return s
|
||||
}
|
||||
|
||||
var ImportedMapTest = BindingTest{
|
||||
name: "ImportedMap",
|
||||
structs: []interface{}{
|
||||
&ImportedMap{},
|
||||
},
|
||||
exemptions: nil,
|
||||
shouldError: false,
|
||||
want: `
|
||||
export namespace binding_test {
|
||||
export class ImportedMap {
|
||||
AMapWrapperContainer: binding_test_import.AMapWrapper;
|
||||
static createFrom(source: any = {}) {
|
||||
return new ImportedMap(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.AMapWrapperContainer = this.convertValues(source["AMapWrapperContainer"], binding_test_import.AMapWrapper);
|
||||
}
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice) {
|
||||
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 binding_test_import {
|
||||
export class AMapWrapper {
|
||||
AMap: {[key: string]: binding_test_nestedimport.A};
|
||||
static createFrom(source: any = {}) {
|
||||
return new AMapWrapper(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.AMap = this.convertValues(source["AMap"], binding_test_nestedimport.A, true);
|
||||
}
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice) {
|
||||
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 binding_test_nestedimport {
|
||||
export class A {
|
||||
A: string;
|
||||
static createFrom(source: any = {}) {
|
||||
return new A(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.A = source["A"];
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package binding_test
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import"
|
||||
|
||||
type ImportedSlice struct {
|
||||
ASliceWrapperContainer binding_test_import.ASliceWrapper `json:"ASliceWrapperContainer"`
|
||||
}
|
||||
|
||||
func (s ImportedSlice) Get() ImportedSlice {
|
||||
return s
|
||||
}
|
||||
|
||||
var ImportedSliceTest = BindingTest{
|
||||
name: "ImportedSlice",
|
||||
structs: []interface{}{
|
||||
&ImportedSlice{},
|
||||
},
|
||||
exemptions: nil,
|
||||
shouldError: false,
|
||||
want: `
|
||||
export namespace binding_test {
|
||||
export class ImportedSlice {
|
||||
ASliceWrapperContainer: binding_test_import.ASliceWrapper;
|
||||
static createFrom(source: any = {}) {
|
||||
return new ImportedSlice(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.ASliceWrapperContainer = this.convertValues(source["ASliceWrapperContainer"], binding_test_import.ASliceWrapper);
|
||||
}
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice) {
|
||||
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 binding_test_import {
|
||||
export class ASliceWrapper {
|
||||
ASlice: binding_test_nestedimport.A[];
|
||||
static createFrom(source: any = {}) {
|
||||
return new ASliceWrapper(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.ASlice = this.convertValues(source["ASlice"], binding_test_nestedimport.A);
|
||||
}
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice) {
|
||||
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 binding_test_nestedimport {
|
||||
export class A {
|
||||
A: string;
|
||||
static createFrom(source: any = {}) {
|
||||
return new A(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.A = source["A"];
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package binding_test
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import"
|
||||
|
||||
type ImportedStruct struct {
|
||||
AWrapperContainer binding_test_import.AWrapper `json:"AWrapperContainer"`
|
||||
}
|
||||
|
||||
func (s ImportedStruct) Get() ImportedStruct {
|
||||
return s
|
||||
}
|
||||
|
||||
var ImportedStructTest = BindingTest{
|
||||
name: "ImportedStruct",
|
||||
structs: []interface{}{
|
||||
&ImportedStruct{},
|
||||
},
|
||||
exemptions: nil,
|
||||
shouldError: false,
|
||||
want: `
|
||||
export namespace binding_test {
|
||||
export class ImportedStruct {
|
||||
AWrapperContainer: binding_test_import.AWrapper;
|
||||
static createFrom(source: any = {}) {
|
||||
return new ImportedStruct(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.AWrapperContainer = this.convertValues(source["AWrapperContainer"], binding_test_import.AWrapper);
|
||||
}
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
|
||||
if (a.slice) {
|
||||
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 binding_test_import {
|
||||
export class AWrapper {
|
||||
AWrapper: binding_test_nestedimport.A;
|
||||
static createFrom(source: any = {}) {
|
||||
return new AWrapper(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.AWrapper = this.convertValues(source["AWrapper"], binding_test_nestedimport.A);
|
||||
}
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice) {
|
||||
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 binding_test_nestedimport {
|
||||
export class A {
|
||||
A: string;
|
||||
static createFrom(source: any = {}) {
|
||||
return new A(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.A = source["A"];
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
63
v2/internal/binding/binding_test/binding_nestedfield_test.go
Normal file
63
v2/internal/binding/binding_test/binding_nestedfield_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package binding_test
|
||||
|
||||
type As struct {
|
||||
B Bs `json:"b"`
|
||||
}
|
||||
|
||||
type Bs struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (a As) Get() As {
|
||||
return a
|
||||
}
|
||||
|
||||
var NestedFieldTest = BindingTest{
|
||||
name: "NestedField",
|
||||
structs: []interface{}{
|
||||
&As{},
|
||||
},
|
||||
exemptions: nil,
|
||||
shouldError: false,
|
||||
want: `
|
||||
export namespace binding_test {
|
||||
export class Bs {
|
||||
name: string;
|
||||
static createFrom(source: any = {}) {
|
||||
return new Bs(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
}
|
||||
}
|
||||
export class As {
|
||||
b: Bs;
|
||||
static createFrom(source: any = {}) {
|
||||
return new As(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.b = this.convertValues(source["b"], Bs);
|
||||
}
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package binding_test
|
||||
|
||||
type NonStringMapKey struct {
|
||||
NumberMap map[uint]any `json:"numberMap"`
|
||||
}
|
||||
|
||||
func (s NonStringMapKey) Get() NonStringMapKey {
|
||||
return s
|
||||
}
|
||||
|
||||
var NonStringMapKeyTest = BindingTest{
|
||||
name: "NonStringMapKey",
|
||||
structs: []interface{}{
|
||||
&NonStringMapKey{},
|
||||
},
|
||||
exemptions: nil,
|
||||
shouldError: false,
|
||||
want: `
|
||||
export namespace binding_test {
|
||||
export class NonStringMapKey {
|
||||
numberMap: {[key: number]: any};
|
||||
static createFrom(source: any = {}) {
|
||||
return new NonStringMapKey(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.numberMap = source["numberMap"];
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
32
v2/internal/binding/binding_test/binding_singlefield_test.go
Normal file
32
v2/internal/binding/binding_test/binding_singlefield_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package binding_test
|
||||
|
||||
type SingleField struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (s SingleField) Get() SingleField {
|
||||
return s
|
||||
}
|
||||
|
||||
var SingleFieldTest = BindingTest{
|
||||
name: "SingleField",
|
||||
structs: []interface{}{
|
||||
&SingleField{},
|
||||
},
|
||||
exemptions: nil,
|
||||
shouldError: false,
|
||||
want: `
|
||||
export namespace binding_test {
|
||||
export class SingleField {
|
||||
name: string;
|
||||
static createFrom(source: any = {}) {
|
||||
return new SingleField(source);
|
||||
}
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
51
v2/internal/binding/binding_test/binding_test.go
Normal file
51
v2/internal/binding/binding_test/binding_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
package binding_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
type BindingTest struct {
|
||||
name string
|
||||
structs []interface{}
|
||||
exemptions []interface{}
|
||||
want string
|
||||
shouldError bool
|
||||
}
|
||||
|
||||
func TestBindings_GenerateModels(t *testing.T) {
|
||||
|
||||
tests := []BindingTest{
|
||||
EscapedNameTest,
|
||||
ImportedStructTest,
|
||||
ImportedSliceTest,
|
||||
ImportedMapTest,
|
||||
NestedFieldTest,
|
||||
NonStringMapKeyTest,
|
||||
SingleFieldTest,
|
||||
}
|
||||
|
||||
testLogger := &logger.Logger{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b := binding.NewBindings(testLogger, tt.structs, tt.exemptions, false)
|
||||
for _, s := range tt.structs {
|
||||
err := b.Add(s)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := b.GenerateModels()
|
||||
if (err != nil) != tt.shouldError {
|
||||
t.Errorf("GenerateModels() error = %v, shouldError %v", err, tt.shouldError)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(strings.Fields(string(got)), strings.Fields(tt.want)) {
|
||||
t.Errorf("GenerateModels() got = %v, want %v", string(got), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package binding_test_import
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/binding_test_nestedimport"
|
||||
|
||||
type AWrapper struct {
|
||||
AWrapper binding_test_nestedimport.A `json:"AWrapper"`
|
||||
}
|
||||
|
||||
type ASliceWrapper struct {
|
||||
ASlice []binding_test_nestedimport.A `json:"ASlice"`
|
||||
}
|
||||
|
||||
type AMapWrapper struct {
|
||||
AMap map[string]binding_test_nestedimport.A `json:"AMap"`
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package binding_test_nestedimport
|
||||
|
||||
type A struct {
|
||||
A string `json:"A"`
|
||||
}
|
@ -165,3 +165,8 @@ func getPackageName(in string) string {
|
||||
result = strings.ReplaceAll(result, "*", "")
|
||||
return result
|
||||
}
|
||||
|
||||
func hasElements(typ reflect.Type) bool {
|
||||
kind := typ.Kind()
|
||||
return kind == reflect.Ptr || kind == reflect.Array || kind == reflect.Slice || kind == reflect.Map
|
||||
}
|
||||
|
@ -3,14 +3,16 @@ package typescriptify
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/leaanthony/slicer"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
|
||||
"github.com/tkrajina/go-reflector/reflector"
|
||||
)
|
||||
|
||||
@ -34,6 +36,7 @@ const (
|
||||
}
|
||||
return a;
|
||||
}`
|
||||
jsVariableNameRegex = `^([A-Z]|[a-z]|\$|_)([A-Z]|[a-z]|[0-9]|\$|_)*$`
|
||||
)
|
||||
|
||||
// TypeOptions overrides options set by `ts_*` tags.
|
||||
@ -266,20 +269,34 @@ func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.Str
|
||||
if valueType.Kind() == reflect.Ptr {
|
||||
valueTypeName = valueType.Elem().Name()
|
||||
}
|
||||
if valueType.Kind() == reflect.Struct && differentNamespaces(t.namespace, valueType) {
|
||||
valueTypeName = valueType.String()
|
||||
}
|
||||
strippedFieldName := strings.ReplaceAll(fieldName, "?", "")
|
||||
isOptional := strings.HasSuffix(fieldName, "?")
|
||||
|
||||
keyTypeStr := keyType.Name()
|
||||
// Key should always be string, no need for this:
|
||||
// _, isSimple := t.types[keyType.Kind()]
|
||||
// if !isSimple {
|
||||
// keyTypeStr = t.prefix + keyType.Name() + t.suffix
|
||||
// }
|
||||
keyTypeStr := ""
|
||||
// Key should always be a JS primitive. JS will read it as a string either way.
|
||||
if typeStr, isSimple := t.types[keyType.Kind()]; isSimple {
|
||||
keyTypeStr = typeStr
|
||||
} else {
|
||||
keyTypeStr = t.types[reflect.String]
|
||||
}
|
||||
|
||||
var dotField string
|
||||
if regexp.MustCompile(jsVariableNameRegex).Match([]byte(strippedFieldName)) {
|
||||
dotField = fmt.Sprintf(".%s", strippedFieldName)
|
||||
} else {
|
||||
dotField = fmt.Sprintf(`["%s"]`, strippedFieldName)
|
||||
if isOptional {
|
||||
fieldName = fmt.Sprintf(`"%s"?`, strippedFieldName)
|
||||
}
|
||||
}
|
||||
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, strippedFieldName, 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, strippedFieldName, strippedFieldName))
|
||||
t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = source[\"%s\"];", t.indent, t.indent, dotField, strippedFieldName))
|
||||
}
|
||||
}
|
||||
|
||||
@ -571,11 +588,8 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m
|
||||
return "", nil
|
||||
}
|
||||
t.logf(depth, "Converting type %s", typeOf.String())
|
||||
if strings.ContainsRune(typeOf.String(), '.') {
|
||||
namespace := strings.Split(typeOf.String(), ".")[0]
|
||||
if namespace != t.Namespace {
|
||||
return "", nil
|
||||
}
|
||||
if differentNamespaces(t.Namespace, typeOf) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
t.alreadyConverted[typeOf.String()] = true
|
||||
@ -829,17 +843,34 @@ func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect.
|
||||
|
||||
func (t *typeScriptClassBuilder) AddArrayOfStructsField(fieldName string, field reflect.StructField, arrayDepth int) {
|
||||
fieldType := field.Type.Elem().Name()
|
||||
if differentNamespaces(t.namespace, field.Type.Elem()) {
|
||||
fieldType = field.Type.Elem().String()
|
||||
}
|
||||
strippedFieldName := strings.ReplaceAll(fieldName, "?", "")
|
||||
t.addField(fieldName, fmt.Sprint(t.prefix+fieldType+t.suffix, strings.Repeat("[]", arrayDepth)), false)
|
||||
t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("this.convertValues(source[\"%s\"], %s)", strippedFieldName, t.prefix+fieldType+t.suffix))
|
||||
}
|
||||
|
||||
func (t *typeScriptClassBuilder) addInitializerFieldLine(fld, initializer string) {
|
||||
t.createFromMethodBody = append(t.createFromMethodBody, fmt.Sprint(t.indent, t.indent, "result.", fld, " = ", initializer, ";"))
|
||||
t.constructorBody = append(t.constructorBody, fmt.Sprint(t.indent, t.indent, "this.", fld, " = ", initializer, ";"))
|
||||
var dotField string
|
||||
if regexp.MustCompile(jsVariableNameRegex).Match([]byte(fld)) {
|
||||
dotField = fmt.Sprintf(".%s", fld)
|
||||
} else {
|
||||
dotField = fmt.Sprintf(`["%s"]`, fld)
|
||||
}
|
||||
t.createFromMethodBody = append(t.createFromMethodBody, fmt.Sprint(t.indent, t.indent, "result", dotField, " = ", initializer, ";"))
|
||||
t.constructorBody = append(t.constructorBody, fmt.Sprint(t.indent, t.indent, "this", dotField, " = ", initializer, ";"))
|
||||
}
|
||||
|
||||
func (t *typeScriptClassBuilder) addField(fld, fldType string, isAnyType bool) {
|
||||
isOptional := strings.HasSuffix(fld, "?")
|
||||
strippedFieldName := strings.ReplaceAll(fld, "?", "")
|
||||
if !regexp.MustCompile(jsVariableNameRegex).Match([]byte(strippedFieldName)) {
|
||||
fld = fmt.Sprintf(`"%s"`, fld)
|
||||
if isOptional {
|
||||
fld += "?"
|
||||
}
|
||||
}
|
||||
if isAnyType {
|
||||
t.fields = append(t.fields, fmt.Sprint(t.indent, "// Go type: ", fldType, "\n", t.indent, fld, ": any;"))
|
||||
} else {
|
||||
@ -860,3 +891,13 @@ func getStructFQN(in string) string {
|
||||
result = strings.ReplaceAll(result, "*", "")
|
||||
return result
|
||||
}
|
||||
|
||||
func differentNamespaces(namespace string, typeOf reflect.Type) bool {
|
||||
if strings.ContainsRune(typeOf.String(), '.') {
|
||||
typeNamespace := strings.Split(typeOf.String(), ".")[0]
|
||||
if namespace != typeNamespace {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user