github.com/secoba/wails/v2@v2.6.4/internal/typescriptify/typescriptify.go (about) 1 package typescriptify 2 3 import ( 4 "bufio" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "os" 9 "path" 10 "reflect" 11 "regexp" 12 "strings" 13 "time" 14 15 "github.com/leaanthony/slicer" 16 17 "github.com/tkrajina/go-reflector/reflector" 18 ) 19 20 const ( 21 tsTransformTag = "ts_transform" 22 tsType = "ts_type" 23 tsConvertValuesFunc = `convertValues(a: any, classs: any, asMap: boolean = false): any { 24 if (!a) { 25 return a; 26 } 27 if (a.slice) { 28 return (a as any[]).map(elem => this.convertValues(elem, classs)); 29 } else if ("object" === typeof a) { 30 if (asMap) { 31 for (const key of Object.keys(a)) { 32 a[key] = new classs(a[key]); 33 } 34 return a; 35 } 36 return new classs(a); 37 } 38 return a; 39 }` 40 jsVariableNameRegex = `^([A-Z]|[a-z]|\$|_)([A-Z]|[a-z]|[0-9]|\$|_)*$` 41 ) 42 43 // TypeOptions overrides options set by `ts_*` tags. 44 type TypeOptions struct { 45 TSType string 46 TSTransform string 47 } 48 49 // StructType stores settings for transforming one Golang struct. 50 type StructType struct { 51 Type reflect.Type 52 FieldOptions map[reflect.Type]TypeOptions 53 } 54 55 func NewStruct(i interface{}) *StructType { 56 return &StructType{ 57 Type: reflect.TypeOf(i), 58 } 59 } 60 61 func (st *StructType) WithFieldOpts(i interface{}, opts TypeOptions) *StructType { 62 if st.FieldOptions == nil { 63 st.FieldOptions = map[reflect.Type]TypeOptions{} 64 } 65 var typ reflect.Type 66 if ty, is := i.(reflect.Type); is { 67 typ = ty 68 } else { 69 typ = reflect.TypeOf(i) 70 } 71 st.FieldOptions[typ] = opts 72 return st 73 } 74 75 type EnumType struct { 76 Type reflect.Type 77 } 78 79 type enumElement struct { 80 value interface{} 81 name string 82 } 83 84 type TypeScriptify struct { 85 Prefix string 86 Suffix string 87 Indent string 88 CreateFromMethod bool 89 CreateConstructor bool 90 BackupDir string // If empty no backup 91 DontExport bool 92 CreateInterface bool 93 customImports []string 94 95 structTypes []StructType 96 enumTypes []EnumType 97 enums map[reflect.Type][]enumElement 98 kinds map[reflect.Kind]string 99 100 fieldTypeOptions map[reflect.Type]TypeOptions 101 102 // throwaway, used when converting 103 alreadyConverted map[string]bool 104 105 Namespace string 106 KnownStructs *slicer.StringSlicer 107 } 108 109 func New() *TypeScriptify { 110 result := new(TypeScriptify) 111 result.Indent = "\t" 112 result.BackupDir = "." 113 114 kinds := make(map[reflect.Kind]string) 115 116 kinds[reflect.Bool] = "boolean" 117 kinds[reflect.Interface] = "any" 118 119 kinds[reflect.Int] = "number" 120 kinds[reflect.Int8] = "number" 121 kinds[reflect.Int16] = "number" 122 kinds[reflect.Int32] = "number" 123 kinds[reflect.Int64] = "number" 124 kinds[reflect.Uint] = "number" 125 kinds[reflect.Uint8] = "number" 126 kinds[reflect.Uint16] = "number" 127 kinds[reflect.Uint32] = "number" 128 kinds[reflect.Uint64] = "number" 129 kinds[reflect.Float32] = "number" 130 kinds[reflect.Float64] = "number" 131 132 kinds[reflect.String] = "string" 133 134 result.kinds = kinds 135 136 result.Indent = " " 137 result.CreateFromMethod = true 138 result.CreateConstructor = true 139 140 return result 141 } 142 143 func (t *TypeScriptify) deepFields(typeOf reflect.Type) []reflect.StructField { 144 fields := make([]reflect.StructField, 0) 145 146 if typeOf.Kind() == reflect.Ptr { 147 typeOf = typeOf.Elem() 148 } 149 150 if typeOf.Kind() != reflect.Struct { 151 return fields 152 } 153 154 for i := 0; i < typeOf.NumField(); i++ { 155 f := typeOf.Field(i) 156 kind := f.Type.Kind() 157 isPointer := kind == reflect.Ptr && f.Type.Elem().Kind() == reflect.Struct 158 if f.Anonymous && kind == reflect.Struct { 159 // fmt.Println(v.Interface()) 160 fields = append(fields, t.deepFields(f.Type)...) 161 } else if f.Anonymous && isPointer { 162 // fmt.Println(v.Interface()) 163 fields = append(fields, t.deepFields(f.Type.Elem())...) 164 } else { 165 // Check we have a json tag 166 jsonTag := t.getJSONFieldName(f, isPointer) 167 if jsonTag != "" { 168 fields = append(fields, f) 169 } 170 } 171 } 172 173 return fields 174 } 175 176 func (ts TypeScriptify) logf(depth int, s string, args ...interface{}) { 177 fmt.Printf(strings.Repeat(" ", depth)+s+"\n", args...) 178 } 179 180 // ManageType can define custom options for fields of a specified type. 181 // 182 // This can be used instead of setting ts_type and ts_transform for all fields of a certain type. 183 func (t *TypeScriptify) ManageType(fld interface{}, opts TypeOptions) *TypeScriptify { 184 var typ reflect.Type 185 switch t := fld.(type) { 186 case reflect.Type: 187 typ = t 188 default: 189 typ = reflect.TypeOf(fld) 190 } 191 if t.fieldTypeOptions == nil { 192 t.fieldTypeOptions = map[reflect.Type]TypeOptions{} 193 } 194 t.fieldTypeOptions[typ] = opts 195 return t 196 } 197 198 func (t *TypeScriptify) GetGeneratedStructs() []string { 199 var result []string 200 for key := range t.alreadyConverted { 201 result = append(result, key) 202 } 203 return result 204 } 205 206 func (t *TypeScriptify) WithCreateFromMethod(b bool) *TypeScriptify { 207 t.CreateFromMethod = b 208 return t 209 } 210 211 func (t *TypeScriptify) WithInterface(b bool) *TypeScriptify { 212 t.CreateInterface = b 213 return t 214 } 215 216 func (t *TypeScriptify) WithConstructor(b bool) *TypeScriptify { 217 t.CreateConstructor = b 218 return t 219 } 220 221 func (t *TypeScriptify) WithIndent(i string) *TypeScriptify { 222 t.Indent = i 223 return t 224 } 225 226 func (t *TypeScriptify) WithBackupDir(b string) *TypeScriptify { 227 t.BackupDir = b 228 return t 229 } 230 231 func (t *TypeScriptify) WithPrefix(p string) *TypeScriptify { 232 t.Prefix = p 233 return t 234 } 235 236 func (t *TypeScriptify) WithSuffix(s string) *TypeScriptify { 237 t.Suffix = s 238 return t 239 } 240 241 func (t *TypeScriptify) Add(obj interface{}) *TypeScriptify { 242 switch ty := obj.(type) { 243 case StructType: 244 t.structTypes = append(t.structTypes, ty) 245 case *StructType: 246 t.structTypes = append(t.structTypes, *ty) 247 case reflect.Type: 248 t.AddType(ty) 249 default: 250 t.AddType(reflect.TypeOf(obj)) 251 } 252 return t 253 } 254 255 func (t *TypeScriptify) AddType(typeOf reflect.Type) *TypeScriptify { 256 t.structTypes = append(t.structTypes, StructType{Type: typeOf}) 257 return t 258 } 259 260 func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.StructField) { 261 keyType := field.Type.Key() 262 valueType := field.Type.Elem() 263 valueTypeName := valueType.Name() 264 if name, ok := t.types[valueType.Kind()]; ok { 265 valueTypeName = name 266 } 267 if valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Slice { 268 valueTypeName = valueType.Elem().Name() + "[]" 269 } 270 if valueType.Kind() == reflect.Ptr { 271 valueTypeName = valueType.Elem().Name() 272 } 273 if valueType.Kind() == reflect.Struct && differentNamespaces(t.namespace, valueType) { 274 valueTypeName = valueType.String() 275 } 276 strippedFieldName := strings.ReplaceAll(fieldName, "?", "") 277 isOptional := strings.HasSuffix(fieldName, "?") 278 279 keyTypeStr := "" 280 // Key should always be a JS primitive. JS will read it as a string either way. 281 if typeStr, isSimple := t.types[keyType.Kind()]; isSimple { 282 keyTypeStr = typeStr 283 } else { 284 keyTypeStr = t.types[reflect.String] 285 } 286 287 var dotField string 288 if regexp.MustCompile(jsVariableNameRegex).Match([]byte(strippedFieldName)) { 289 dotField = fmt.Sprintf(".%s", strippedFieldName) 290 } else { 291 dotField = fmt.Sprintf(`["%s"]`, strippedFieldName) 292 if isOptional { 293 fieldName = fmt.Sprintf(`"%s"?`, strippedFieldName) 294 } 295 } 296 t.fields = append(t.fields, fmt.Sprintf("%s%s: {[key: %s]: %s};", t.indent, fieldName, keyTypeStr, valueTypeName)) 297 if valueType.Kind() == reflect.Struct { 298 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)) 299 } else { 300 t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = source[\"%s\"];", t.indent, t.indent, dotField, strippedFieldName)) 301 } 302 } 303 304 func (t *TypeScriptify) AddEnum(values interface{}) *TypeScriptify { 305 if t.enums == nil { 306 t.enums = map[reflect.Type][]enumElement{} 307 } 308 items := reflect.ValueOf(values) 309 if items.Kind() != reflect.Slice { 310 panic(fmt.Sprintf("Values for %T isn't a slice", values)) 311 } 312 313 var elements []enumElement 314 for i := 0; i < items.Len(); i++ { 315 item := items.Index(i) 316 317 var el enumElement 318 if item.Kind() == reflect.Struct { 319 r := reflector.New(item.Interface()) 320 val, err := r.Field("Value").Get() 321 if err != nil { 322 panic(fmt.Sprint("missing Type field in ", item.Type().String())) 323 } 324 name, err := r.Field("TSName").Get() 325 if err != nil { 326 panic(fmt.Sprint("missing TSName field in ", item.Type().String())) 327 } 328 el.value = val 329 el.name = name.(string) 330 } else { 331 el.value = item.Interface() 332 if tsNamer, is := item.Interface().(TSNamer); is { 333 el.name = tsNamer.TSName() 334 } else { 335 panic(fmt.Sprint(item.Type().String(), " has no TSName method")) 336 } 337 } 338 339 elements = append(elements, el) 340 } 341 ty := reflect.TypeOf(elements[0].value) 342 t.enums[ty] = elements 343 t.enumTypes = append(t.enumTypes, EnumType{Type: ty}) 344 345 return t 346 } 347 348 // AddEnumValues is deprecated, use `AddEnum()` 349 func (t *TypeScriptify) AddEnumValues(typeOf reflect.Type, values interface{}) *TypeScriptify { 350 t.AddEnum(values) 351 return t 352 } 353 354 func (t *TypeScriptify) Convert(customCode map[string]string) (string, error) { 355 t.alreadyConverted = make(map[string]bool) 356 depth := 0 357 358 result := "" 359 if len(t.customImports) > 0 { 360 // Put the custom imports, i.e.: `import Decimal from 'decimal.js'` 361 for _, cimport := range t.customImports { 362 result += cimport + "\n" 363 } 364 } 365 366 for _, enumTyp := range t.enumTypes { 367 elements := t.enums[enumTyp.Type] 368 typeScriptCode, err := t.convertEnum(depth, enumTyp.Type, elements) 369 if err != nil { 370 return "", err 371 } 372 result += "\n" + strings.Trim(typeScriptCode, " "+t.Indent+"\r\n") 373 } 374 375 for _, strctTyp := range t.structTypes { 376 typeScriptCode, err := t.convertType(depth, strctTyp.Type, customCode) 377 if err != nil { 378 return "", err 379 } 380 result += "\n" + strings.Trim(typeScriptCode, " "+t.Indent+"\r\n") 381 } 382 return result, nil 383 } 384 385 func loadCustomCode(fileName string) (map[string]string, error) { 386 result := make(map[string]string) 387 f, err := os.Open(fileName) 388 if err != nil { 389 if os.IsNotExist(err) { 390 return result, nil 391 } 392 return result, err 393 } 394 defer f.Close() 395 396 bytes, err := ioutil.ReadAll(f) 397 if err != nil { 398 return result, err 399 } 400 401 var currentName string 402 var currentValue string 403 lines := strings.Split(string(bytes), "\n") 404 for _, line := range lines { 405 trimmedLine := strings.TrimSpace(line) 406 if strings.HasPrefix(trimmedLine, "//[") && strings.HasSuffix(trimmedLine, ":]") { 407 currentName = strings.Replace(strings.Replace(trimmedLine, "//[", "", -1), ":]", "", -1) 408 currentValue = "" 409 } else if trimmedLine == "//[end]" { 410 result[currentName] = strings.TrimRight(currentValue, " \t\r\n") 411 currentName = "" 412 currentValue = "" 413 } else if len(currentName) > 0 { 414 currentValue += line + "\n" 415 } 416 } 417 418 return result, nil 419 } 420 421 func (t TypeScriptify) backup(fileName string) error { 422 fileIn, err := os.Open(fileName) 423 if err != nil { 424 if !os.IsNotExist(err) { 425 return err 426 } 427 // No neet to backup, just return: 428 return nil 429 } 430 defer fileIn.Close() 431 432 bytes, err := ioutil.ReadAll(fileIn) 433 if err != nil { 434 return err 435 } 436 437 _, backupFn := path.Split(fmt.Sprintf("%s-%s.backup", fileName, time.Now().Format("2006-01-02T15_04_05.99"))) 438 if t.BackupDir != "" { 439 backupFn = path.Join(t.BackupDir, backupFn) 440 } 441 442 return ioutil.WriteFile(backupFn, bytes, os.FileMode(0o700)) 443 } 444 445 func (t TypeScriptify) ConvertToFile(fileName string, packageName string) error { 446 if len(t.BackupDir) > 0 { 447 err := t.backup(fileName) 448 if err != nil { 449 return err 450 } 451 } 452 453 customCode, err := loadCustomCode(fileName) 454 if err != nil { 455 return err 456 } 457 458 f, err := os.Create(fileName) 459 if err != nil { 460 return err 461 } 462 defer f.Close() 463 464 converted, err := t.Convert(customCode) 465 if err != nil { 466 return err 467 } 468 469 var lines []string 470 sc := bufio.NewScanner(strings.NewReader(converted)) 471 for sc.Scan() { 472 lines = append(lines, "\t"+sc.Text()) 473 } 474 475 converted = "export namespace " + packageName + " {\n" 476 converted += strings.Join(lines, "\n") 477 converted += "\n}\n" 478 479 if _, err := f.WriteString("/* Do not change, this code is generated from Golang structs */\n\n"); err != nil { 480 return err 481 } 482 if _, err := f.WriteString(converted); err != nil { 483 return err 484 } 485 if err != nil { 486 return err 487 } 488 489 return nil 490 } 491 492 type TSNamer interface { 493 TSName() string 494 } 495 496 func (t *TypeScriptify) convertEnum(depth int, typeOf reflect.Type, elements []enumElement) (string, error) { 497 t.logf(depth, "Converting enum %s", typeOf.String()) 498 if _, found := t.alreadyConverted[typeOf.String()]; found { // Already converted 499 return "", nil 500 } 501 t.alreadyConverted[typeOf.String()] = true 502 503 entityName := t.Prefix + typeOf.Name() + t.Suffix 504 result := "enum " + entityName + " {\n" 505 506 for _, val := range elements { 507 result += fmt.Sprintf("%s%s = %#v,\n", t.Indent, val.name, val.value) 508 } 509 510 result += "}" 511 512 if !t.DontExport { 513 result = "export " + result 514 } 515 516 return result, nil 517 } 518 519 func (t *TypeScriptify) getFieldOptions(structType reflect.Type, field reflect.StructField) TypeOptions { 520 // By default use options defined by tags: 521 opts := TypeOptions{TSTransform: field.Tag.Get(tsTransformTag), TSType: field.Tag.Get(tsType)} 522 523 overrides := []TypeOptions{} 524 525 // But there is maybe an struct-specific override: 526 for _, strct := range t.structTypes { 527 if strct.FieldOptions == nil { 528 continue 529 } 530 if strct.Type == structType { 531 if fldOpts, found := strct.FieldOptions[field.Type]; found { 532 overrides = append(overrides, fldOpts) 533 } 534 } 535 } 536 537 if fldOpts, found := t.fieldTypeOptions[field.Type]; found { 538 overrides = append(overrides, fldOpts) 539 } 540 541 for _, o := range overrides { 542 if o.TSTransform != "" { 543 opts.TSTransform = o.TSTransform 544 } 545 if o.TSType != "" { 546 opts.TSType = o.TSType 547 } 548 } 549 550 return opts 551 } 552 553 func (t *TypeScriptify) getJSONFieldName(field reflect.StructField, isPtr bool) string { 554 jsonFieldName := "" 555 jsonTag := field.Tag.Get("json") 556 if len(jsonTag) > 0 { 557 jsonTagParts := strings.Split(jsonTag, ",") 558 if len(jsonTagParts) > 0 { 559 jsonFieldName = strings.Trim(jsonTagParts[0], t.Indent) 560 } 561 hasOmitEmpty := false 562 ignored := false 563 for _, t := range jsonTagParts { 564 if t == "" { 565 break 566 } 567 if t == "omitempty" { 568 hasOmitEmpty = true 569 break 570 } 571 if t == "-" { 572 ignored = true 573 break 574 } 575 } 576 if !ignored && isPtr || hasOmitEmpty { 577 jsonFieldName = fmt.Sprintf("%s?", jsonFieldName) 578 } 579 } 580 return jsonFieldName 581 } 582 583 func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode map[string]string) (string, error) { 584 if _, found := t.alreadyConverted[typeOf.String()]; found { // Already converted 585 return "", nil 586 } 587 fields := t.deepFields(typeOf) 588 if len(fields) == 0 { 589 return "", nil 590 } 591 t.logf(depth, "Converting type %s", typeOf.String()) 592 if differentNamespaces(t.Namespace, typeOf) { 593 return "", nil 594 } 595 596 t.alreadyConverted[typeOf.String()] = true 597 598 entityName := t.Prefix + typeOf.Name() + t.Suffix 599 600 if typeClashWithReservedKeyword(entityName) { 601 warnAboutTypesClash(entityName) 602 } 603 604 result := "" 605 if t.CreateInterface { 606 result += fmt.Sprintf("interface %s {\n", entityName) 607 } else { 608 result += fmt.Sprintf("class %s {\n", entityName) 609 } 610 if !t.DontExport { 611 result = "export " + result 612 } 613 builder := typeScriptClassBuilder{ 614 types: t.kinds, 615 indent: t.Indent, 616 prefix: t.Prefix, 617 suffix: t.Suffix, 618 namespace: t.Namespace, 619 } 620 621 for _, field := range fields { 622 isPtr := field.Type.Kind() == reflect.Ptr 623 if isPtr { 624 field.Type = field.Type.Elem() 625 } 626 jsonFieldName := t.getJSONFieldName(field, isPtr) 627 if len(jsonFieldName) == 0 || jsonFieldName == "-" { 628 continue 629 } 630 631 var err error 632 fldOpts := t.getFieldOptions(typeOf, field) 633 if fldOpts.TSTransform != "" { 634 t.logf(depth, "- simple field %s.%s", typeOf.Name(), field.Name) 635 err = builder.AddSimpleField(jsonFieldName, field, fldOpts) 636 } else if _, isEnum := t.enums[field.Type]; isEnum { 637 t.logf(depth, "- enum field %s.%s", typeOf.Name(), field.Name) 638 builder.AddEnumField(jsonFieldName, field) 639 } else if fldOpts.TSType != "" { // Struct: 640 t.logf(depth, "- simple field %s.%s", typeOf.Name(), field.Name) 641 err = builder.AddSimpleField(jsonFieldName, field, fldOpts) 642 } else if field.Type.Kind() == reflect.Struct { // Struct: 643 t.logf(depth, "- struct %s.%s (%s)", typeOf.Name(), field.Name, field.Type.String()) 644 645 // Anonymous structures is ignored 646 // It is possible to generate them but hard to generate correct name 647 if field.Type.Name() != "" { 648 typeScriptChunk, err := t.convertType(depth+1, field.Type, customCode) 649 if err != nil { 650 return "", err 651 } 652 if typeScriptChunk != "" { 653 result = typeScriptChunk + "\n" + result 654 } 655 } 656 657 isKnownType := t.KnownStructs.Contains(getStructFQN(field.Type.String())) 658 println("KnownStructs:", t.KnownStructs.Join("\t")) 659 println(getStructFQN(field.Type.String())) 660 builder.AddStructField(jsonFieldName, field, !isKnownType) 661 } else if field.Type.Kind() == reflect.Map { 662 t.logf(depth, "- map field %s.%s", typeOf.Name(), field.Name) 663 // Also convert map key types if needed 664 var keyTypeToConvert reflect.Type 665 switch field.Type.Key().Kind() { 666 case reflect.Struct: 667 keyTypeToConvert = field.Type.Key() 668 case reflect.Ptr: 669 keyTypeToConvert = field.Type.Key().Elem() 670 } 671 if keyTypeToConvert != nil { 672 typeScriptChunk, err := t.convertType(depth+1, keyTypeToConvert, customCode) 673 if err != nil { 674 return "", err 675 } 676 if typeScriptChunk != "" { 677 result = typeScriptChunk + "\n" + result 678 } 679 } 680 // Also convert map value types if needed 681 var valueTypeToConvert reflect.Type 682 switch field.Type.Elem().Kind() { 683 case reflect.Struct: 684 valueTypeToConvert = field.Type.Elem() 685 case reflect.Ptr: 686 valueTypeToConvert = field.Type.Elem().Elem() 687 } 688 if valueTypeToConvert != nil { 689 typeScriptChunk, err := t.convertType(depth+1, valueTypeToConvert, customCode) 690 if err != nil { 691 return "", err 692 } 693 if typeScriptChunk != "" { 694 result = typeScriptChunk + "\n" + result 695 } 696 } 697 698 builder.AddMapField(jsonFieldName, field) 699 } else if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array { // Slice: 700 if field.Type.Elem().Kind() == reflect.Ptr { // extract ptr type 701 field.Type = field.Type.Elem() 702 } 703 704 arrayDepth := 1 705 for field.Type.Elem().Kind() == reflect.Slice { // Slice of slices: 706 field.Type = field.Type.Elem() 707 arrayDepth++ 708 } 709 710 if field.Type.Elem().Kind() == reflect.Struct { // Slice of structs: 711 t.logf(depth, "- struct slice %s.%s (%s)", typeOf.Name(), field.Name, field.Type.String()) 712 typeScriptChunk, err := t.convertType(depth+1, field.Type.Elem(), customCode) 713 if err != nil { 714 return "", err 715 } 716 if typeScriptChunk != "" { 717 result = typeScriptChunk + "\n" + result 718 } 719 builder.AddArrayOfStructsField(jsonFieldName, field, arrayDepth) 720 } else { // Slice of simple fields: 721 t.logf(depth, "- slice field %s.%s", typeOf.Name(), field.Name) 722 err = builder.AddSimpleArrayField(jsonFieldName, field, arrayDepth, fldOpts) 723 } 724 } else { // Simple field: 725 t.logf(depth, "- simple field %s.%s", typeOf.Name(), field.Name) 726 err = builder.AddSimpleField(jsonFieldName, field, fldOpts) 727 } 728 if err != nil { 729 return "", err 730 } 731 } 732 733 if t.CreateFromMethod { 734 t.CreateConstructor = true 735 } 736 737 result += strings.Join(builder.fields, "\n") + "\n" 738 if !t.CreateInterface { 739 constructorBody := strings.Join(builder.constructorBody, "\n") 740 needsConvertValue := strings.Contains(constructorBody, "this.convertValues") 741 if t.CreateFromMethod { 742 result += fmt.Sprintf("\n%sstatic createFrom(source: any = {}) {\n", t.Indent) 743 result += fmt.Sprintf("%s%sreturn new %s(source);\n", t.Indent, t.Indent, entityName) 744 result += fmt.Sprintf("%s}\n", t.Indent) 745 } 746 if t.CreateConstructor { 747 result += fmt.Sprintf("\n%sconstructor(source: any = {}) {\n", t.Indent) 748 result += t.Indent + t.Indent + "if ('string' === typeof source) source = JSON.parse(source);\n" 749 result += constructorBody + "\n" 750 result += fmt.Sprintf("%s}\n", t.Indent) 751 } 752 if needsConvertValue && (t.CreateConstructor || t.CreateFromMethod) { 753 result += "\n" + indentLines(strings.ReplaceAll(tsConvertValuesFunc, "\t", t.Indent), 1) + "\n" 754 } 755 } 756 757 if customCode != nil { 758 code := customCode[entityName] 759 if len(code) != 0 { 760 result += t.Indent + "//[" + entityName + ":]\n" + code + "\n\n" + t.Indent + "//[end]\n" 761 } 762 } 763 764 result += "}" 765 766 return result, nil 767 } 768 769 func (t *TypeScriptify) AddImport(i string) { 770 for _, cimport := range t.customImports { 771 if cimport == i { 772 return 773 } 774 } 775 776 t.customImports = append(t.customImports, i) 777 } 778 779 type typeScriptClassBuilder struct { 780 types map[reflect.Kind]string 781 indent string 782 fields []string 783 createFromMethodBody []string 784 constructorBody []string 785 prefix, suffix string 786 namespace string 787 } 788 789 func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field reflect.StructField, arrayDepth int, opts TypeOptions) error { 790 fieldType, kind := field.Type.Elem().Name(), field.Type.Elem().Kind() 791 typeScriptType := t.types[kind] 792 793 if len(fieldName) > 0 { 794 strippedFieldName := strings.ReplaceAll(fieldName, "?", "") 795 if len(opts.TSType) > 0 { 796 t.addField(fieldName, opts.TSType, false) 797 t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) 798 return nil 799 } else if len(typeScriptType) > 0 { 800 t.addField(fieldName, fmt.Sprint(typeScriptType, strings.Repeat("[]", arrayDepth)), false) 801 t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) 802 return nil 803 } 804 } 805 806 return fmt.Errorf("cannot find type for %s (%s/%s)", kind.String(), fieldName, fieldType) 807 } 808 809 func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect.StructField, opts TypeOptions) error { 810 fieldType, kind := field.Type.Name(), field.Type.Kind() 811 812 typeScriptType := t.types[kind] 813 if len(opts.TSType) > 0 { 814 typeScriptType = opts.TSType 815 } 816 817 if len(typeScriptType) > 0 && len(fieldName) > 0 { 818 strippedFieldName := strings.ReplaceAll(fieldName, "?", "") 819 t.addField(fieldName, typeScriptType, false) 820 if opts.TSTransform == "" { 821 t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) 822 } else { 823 val := fmt.Sprintf(`source["%s"]`, strippedFieldName) 824 expression := strings.Replace(opts.TSTransform, "__VALUE__", val, -1) 825 t.addInitializerFieldLine(strippedFieldName, expression) 826 } 827 return nil 828 } 829 830 return fmt.Errorf("cannot find type for %s (%s/%s)", kind.String(), fieldName, fieldType) 831 } 832 833 func (t *typeScriptClassBuilder) AddEnumField(fieldName string, field reflect.StructField) { 834 fieldType := field.Type.Name() 835 t.addField(fieldName, t.prefix+fieldType+t.suffix, false) 836 strippedFieldName := strings.ReplaceAll(fieldName, "?", "") 837 t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) 838 } 839 840 func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect.StructField, isAnyType bool) { 841 strippedFieldName := strings.ReplaceAll(fieldName, "?", "") 842 classname := "null" 843 namespace := strings.Split(field.Type.String(), ".")[0] 844 fqname := t.prefix + field.Type.Name() + t.suffix 845 if namespace != t.namespace { 846 fqname = namespace + "." + fqname 847 } 848 849 if !isAnyType { 850 classname = fqname 851 } 852 853 // Anonymous struct 854 if field.Type.Name() == "" { 855 classname = "Object" 856 } 857 858 t.addField(fieldName, fqname, isAnyType) 859 t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("this.convertValues(source[\"%s\"], %s)", strippedFieldName, classname)) 860 } 861 862 func (t *typeScriptClassBuilder) AddArrayOfStructsField(fieldName string, field reflect.StructField, arrayDepth int) { 863 fieldType := field.Type.Elem().Name() 864 if differentNamespaces(t.namespace, field.Type.Elem()) { 865 fieldType = field.Type.Elem().String() 866 } 867 strippedFieldName := strings.ReplaceAll(fieldName, "?", "") 868 t.addField(fieldName, fmt.Sprint(t.prefix+fieldType+t.suffix, strings.Repeat("[]", arrayDepth)), false) 869 t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("this.convertValues(source[\"%s\"], %s)", strippedFieldName, t.prefix+fieldType+t.suffix)) 870 } 871 872 func (t *typeScriptClassBuilder) addInitializerFieldLine(fld, initializer string) { 873 var dotField string 874 if regexp.MustCompile(jsVariableNameRegex).Match([]byte(fld)) { 875 dotField = fmt.Sprintf(".%s", fld) 876 } else { 877 dotField = fmt.Sprintf(`["%s"]`, fld) 878 } 879 t.createFromMethodBody = append(t.createFromMethodBody, fmt.Sprint(t.indent, t.indent, "result", dotField, " = ", initializer, ";")) 880 t.constructorBody = append(t.constructorBody, fmt.Sprint(t.indent, t.indent, "this", dotField, " = ", initializer, ";")) 881 } 882 883 func (t *typeScriptClassBuilder) addField(fld, fldType string, isAnyType bool) { 884 isOptional := strings.HasSuffix(fld, "?") 885 strippedFieldName := strings.ReplaceAll(fld, "?", "") 886 if !regexp.MustCompile(jsVariableNameRegex).Match([]byte(strippedFieldName)) { 887 fld = fmt.Sprintf(`"%s"`, fld) 888 if isOptional { 889 fld += "?" 890 } 891 } 892 if isAnyType { 893 fldType = strings.Split(fldType, ".")[0] 894 t.fields = append(t.fields, fmt.Sprint(t.indent, "// Go type: ", fldType, "\n", t.indent, fld, ": any;")) 895 } else { 896 t.fields = append(t.fields, fmt.Sprint(t.indent, fld, ": ", fldType, ";")) 897 } 898 } 899 900 func indentLines(str string, i int) string { 901 lines := strings.Split(str, "\n") 902 for n := range lines { 903 lines[n] = strings.Repeat("\t", i) + lines[n] 904 } 905 return strings.Join(lines, "\n") 906 } 907 908 func getStructFQN(in string) string { 909 result := strings.ReplaceAll(in, "[]", "") 910 result = strings.ReplaceAll(result, "*", "") 911 return result 912 } 913 914 func differentNamespaces(namespace string, typeOf reflect.Type) bool { 915 if strings.ContainsRune(typeOf.String(), '.') { 916 typeNamespace := strings.Split(typeOf.String(), ".")[0] 917 if namespace != typeNamespace { 918 return true 919 } 920 } 921 return false 922 } 923 924 func typeClashWithReservedKeyword(input string) bool { 925 in := strings.ToLower(strings.TrimSpace(input)) 926 for _, v := range jsReservedKeywords { 927 if in == v { 928 return true 929 } 930 } 931 932 return false 933 } 934 935 func warnAboutTypesClash(entity string) { 936 // TODO: Refactor logging 937 l := log.New(os.Stderr, "", 0) 938 l.Printf("Usage of reserved keyword found and not supported: %s", entity) 939 log.Println("Please rename returned type or consider adding bindings config to your wails.json") 940 }