This commit is contained in:
2026-01-27 16:20:46 -05:00
parent 7bf4f115b1
commit c3a16c9e92
21 changed files with 1192 additions and 577 deletions

View File

@@ -0,0 +1,170 @@
package builtin
import (
"fmt"
"reflect"
"strings"
"sync"
)
type typeCollector struct {
mu sync.Mutex
types map[string]string
paramTypes map[string]bool
}
func newTypeCollector() *typeCollector {
return &typeCollector{
types: make(map[string]string),
paramTypes: make(map[string]bool),
}
}
func (tc *typeCollector) collectTypes(argsType reflect.Type, fnType reflect.Type) []string {
tc.mu.Lock()
defer tc.mu.Unlock()
tc.types = make(map[string]string)
tc.paramTypes = make(map[string]bool)
var result []string
tc.collectStruct(argsType, argsType.Name())
for i := 0; i < argsType.NumField(); i++ {
field := argsType.Field(i)
if field.Type.Kind() == reflect.Pointer || strings.Contains(field.Tag.Get("json"), ",omitempty") {
tc.collectParamType(field.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 {
tc.collectType(fnType.Out(0))
}
} else {
tc.collectType(lastType)
}
}
for _, t := range tc.types {
result = append(result, t)
}
return result
}
func (tc *typeCollector) collectParamType(t reflect.Type) {
if t.Kind() == reflect.Pointer {
tc.collectParamType(t.Elem())
return
}
if t.Kind() == reflect.Struct && t.Name() != "" {
tc.paramTypes[t.Name()+" | null"] = true
}
}
func (tc *typeCollector) getParamTypes() map[string]bool {
return tc.paramTypes
}
func (tc *typeCollector) collectType(t reflect.Type) {
if t.Kind() == reflect.Pointer {
tc.collectType(t.Elem())
return
}
if t.Kind() == reflect.Struct {
name := t.Name()
if _, exists := tc.types[name]; !exists {
tc.collectStruct(t, name)
}
}
}
func (tc *typeCollector) collectStruct(t reflect.Type, name string) {
if t.Kind() != reflect.Struct {
return
}
var fields []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Anonymous || !field.IsExported() {
continue
}
fieldName := getFieldName(field)
var tsType string
var isOptional bool
isPointer := field.Type.Kind() == reflect.Pointer
if isPointer {
isOptional = true
tsType = goTypeToTSType(field.Type, false)
} else {
isOptional = strings.Contains(field.Tag.Get("json"), ",omitempty")
tsType = goTypeToTSType(field.Type, false)
}
if isOptional {
fieldName += "?"
}
fields = append(fields, fmt.Sprintf("%s: %s", fieldName, tsType))
tc.collectType(field.Type)
}
tc.types[name] = fmt.Sprintf("interface %s {%s}", name, strings.Join(fields, "; "))
}
func goTypeToTSType(t reflect.Type, isPointer bool) string {
if t.Kind() == reflect.Pointer {
return goTypeToTSType(t.Elem(), true)
}
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"
}
if isPointer {
baseType += " | null"
}
return baseType
}