243 lines
5.8 KiB
Go
243 lines
5.8 KiB
Go
package tsconvert
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
// ConvertType converts a Go reflect.Type to a TypeScript type string.
|
|
func ConvertType(t reflect.Type) string {
|
|
return goTypeToTSType(t, false)
|
|
}
|
|
|
|
// goTypeToTSType converts a Go type to TypeScript type string.
|
|
// isPointer tracks if we're inside a pointer chain.
|
|
func goTypeToTSType(t reflect.Type, isPointer bool) string {
|
|
// Handle Pointer Types
|
|
if t.Kind() == reflect.Pointer {
|
|
return goTypeToTSType(t.Elem(), true)
|
|
}
|
|
|
|
// Determine Base Type
|
|
baseType := ""
|
|
switch t.Kind() {
|
|
case reflect.String:
|
|
baseType = "string"
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
baseType = "number"
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
baseType = "number"
|
|
case reflect.Float32, reflect.Float64:
|
|
baseType = "number"
|
|
case reflect.Bool:
|
|
baseType = "boolean"
|
|
case reflect.Interface:
|
|
baseType = "any"
|
|
case reflect.Slice:
|
|
baseType = fmt.Sprintf("%s[]", goTypeToTSType(t.Elem(), false))
|
|
case reflect.Map:
|
|
if t.Key().Kind() == reflect.String && t.Elem().Kind() == reflect.Interface {
|
|
baseType = "Record<string, any>"
|
|
} else {
|
|
baseType = "Record<string, any>"
|
|
}
|
|
case reflect.Struct:
|
|
name := t.Name()
|
|
if name == "" {
|
|
baseType = "{}"
|
|
} else {
|
|
baseType = name
|
|
}
|
|
default:
|
|
baseType = "any"
|
|
}
|
|
|
|
// Add Null for Pointer Types
|
|
if isPointer {
|
|
baseType += " | null"
|
|
}
|
|
return baseType
|
|
}
|
|
|
|
// CollectTypes extracts all type declarations from a function signature.
|
|
// It analyzes the args type and return type to find all struct types.
|
|
func CollectTypes(argsType, fnType reflect.Type) *TypeSet {
|
|
// Create TypeSet
|
|
ts := NewTypeSet()
|
|
|
|
// Collect Types from Args Struct
|
|
collectStructTypes(argsType, ts)
|
|
|
|
// Collect Types from Return Type
|
|
if fnType.Kind() == reflect.Func && fnType.NumOut() > 0 {
|
|
lastIndex := fnType.NumOut() - 1
|
|
lastType := fnType.Out(lastIndex)
|
|
|
|
if lastType.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
|
|
if fnType.NumOut() > 1 {
|
|
collectType(fnType.Out(0), ts)
|
|
}
|
|
} else {
|
|
collectType(lastType, ts)
|
|
}
|
|
}
|
|
|
|
return ts
|
|
}
|
|
|
|
// collectType recursively collects struct types.
|
|
func collectType(t reflect.Type, ts *TypeSet) {
|
|
// Handle Pointer Types
|
|
if t.Kind() == reflect.Pointer {
|
|
collectType(t.Elem(), ts)
|
|
return
|
|
}
|
|
|
|
// Collect Struct Types
|
|
if t.Kind() == reflect.Struct {
|
|
name := t.Name()
|
|
if name != "" {
|
|
// Only Process if Not Already Processed
|
|
if _, exists := ts.Get(name); !exists {
|
|
collectStructTypes(t, ts)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// collectStructTypes converts a Go struct to TypeScript interface and adds to TypeSet.
|
|
func collectStructTypes(t reflect.Type, ts *TypeSet) {
|
|
// Validate Struct Type
|
|
if t.Kind() != reflect.Struct {
|
|
return
|
|
}
|
|
|
|
// Get Struct Name
|
|
name := t.Name()
|
|
if name == "" {
|
|
return // Skip Anonymous Structs
|
|
}
|
|
|
|
// Check if Already Processed
|
|
if _, exists := ts.Get(name); exists {
|
|
return
|
|
}
|
|
|
|
// Collect Fields
|
|
var fields []string
|
|
for i := 0; i < t.NumField(); i++ {
|
|
field := t.Field(i)
|
|
|
|
// Skip Anonymous and Unexported Fields
|
|
if field.Anonymous || !field.IsExported() {
|
|
continue
|
|
}
|
|
|
|
// Get Field Name
|
|
fieldName := getFieldName(field)
|
|
|
|
// Determine Type and Optionality
|
|
var tsType string
|
|
var isOptional bool
|
|
isPointer := field.Type.Kind() == reflect.Pointer
|
|
|
|
if isPointer {
|
|
isOptional = true
|
|
tsType = goTypeToTSType(field.Type, true)
|
|
} else {
|
|
isOptional = strings.Contains(field.Tag.Get("json"), ",omitempty")
|
|
tsType = goTypeToTSType(field.Type, false)
|
|
}
|
|
|
|
// Mark Optional Fields
|
|
if isOptional {
|
|
fieldName += "?"
|
|
}
|
|
|
|
fields = append(fields, fmt.Sprintf("%s: %s", fieldName, tsType))
|
|
|
|
// Recursively Collect Nested Types
|
|
collectType(field.Type, ts)
|
|
}
|
|
|
|
// Add Type Definition
|
|
definition := fmt.Sprintf("interface %s {%s}", name, strings.Join(fields, "; "))
|
|
_ = ts.Add(name, definition)
|
|
}
|
|
|
|
// getFieldName extracts the field name from json tag or uses the Go field name.
|
|
func getFieldName(field reflect.StructField) string {
|
|
// Get JSON Tag
|
|
jsonTag := field.Tag.Get("json")
|
|
if jsonTag != "" && jsonTag != "-" {
|
|
name, _, _ := strings.Cut(jsonTag, ",")
|
|
return name
|
|
}
|
|
|
|
// Use Go Field Name
|
|
return field.Name
|
|
}
|
|
|
|
// GenerateFunctionDecl creates a TypeScript function declaration.
|
|
func GenerateFunctionDecl(name string, argsType, fnType reflect.Type, isAsync bool) string {
|
|
// Validate Args Type
|
|
if argsType.Kind() != reflect.Struct {
|
|
return ""
|
|
}
|
|
|
|
// Collect Parameters
|
|
var params []string
|
|
for i := 0; i < argsType.NumField(); i++ {
|
|
field := argsType.Field(i)
|
|
fieldName := getFieldName(field)
|
|
goType := field.Type
|
|
|
|
// Determine Type and Optionality
|
|
var tsType string
|
|
var isOptional bool
|
|
isPointer := goType.Kind() == reflect.Pointer
|
|
|
|
if isPointer {
|
|
isOptional = true
|
|
tsType = goTypeToTSType(goType, true)
|
|
if !strings.Contains(tsType, " | null") {
|
|
tsType += " | null"
|
|
}
|
|
} else {
|
|
isOptional = strings.Contains(field.Tag.Get("json"), ",omitempty")
|
|
tsType = goTypeToTSType(goType, false)
|
|
}
|
|
|
|
// Mark Optional Fields
|
|
if isOptional {
|
|
fieldName += "?"
|
|
}
|
|
params = append(params, fmt.Sprintf("%s: %s", fieldName, tsType))
|
|
}
|
|
|
|
// Determine Return Type
|
|
returnSignature := "any"
|
|
if fnType.Kind() == reflect.Func && fnType.NumOut() > 0 {
|
|
lastIndex := fnType.NumOut() - 1
|
|
lastType := fnType.Out(lastIndex)
|
|
|
|
if lastType.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
|
|
if fnType.NumOut() > 1 {
|
|
returnType := fnType.Out(0)
|
|
returnSignature = goTypeToTSType(returnType, returnType.Kind() == reflect.Pointer)
|
|
}
|
|
} else {
|
|
returnSignature = goTypeToTSType(lastType, lastType.Kind() == reflect.Pointer)
|
|
}
|
|
}
|
|
|
|
// Wrap in Promise for Async Functions
|
|
if isAsync {
|
|
returnSignature = fmt.Sprintf("Promise<%s>", returnSignature)
|
|
}
|
|
|
|
// Generate Declaration
|
|
return fmt.Sprintf("declare function %s(%s): %s;", name, strings.Join(params, ", "), returnSignature)
|
|
}
|