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