initial commit
This commit is contained in:
242
internal/tsconvert/convert.go
Normal file
242
internal/tsconvert/convert.go
Normal file
@@ -0,0 +1,242 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user