initial commit

This commit is contained in:
2026-01-27 09:55:09 -05:00
commit ccbe9cd7bf
18 changed files with 2210 additions and 0 deletions

View 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)
}