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)
|
||||
}
|
||||
333
internal/tsconvert/convert_test.go
Normal file
333
internal/tsconvert/convert_test.go
Normal file
@@ -0,0 +1,333 @@
|
||||
package tsconvert
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Test types for conversion
|
||||
type SimpleStruct struct {
|
||||
Name string `json:"name"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type NestedStruct struct {
|
||||
ID int `json:"id"`
|
||||
Simple SimpleStruct `json:"simple"`
|
||||
Pointer *SimpleStruct `json:"pointer,omitempty"`
|
||||
}
|
||||
|
||||
type OptionalFields struct {
|
||||
Required string `json:"required"`
|
||||
Optional *string `json:"optional,omitempty"`
|
||||
Number int `json:"number,omitempty"`
|
||||
}
|
||||
|
||||
type ComplexTypes struct {
|
||||
Strings []string `json:"strings"`
|
||||
Numbers []int `json:"numbers"`
|
||||
Mapping map[string]any `json:"mapping"`
|
||||
Nested []SimpleStruct `json:"nested"`
|
||||
}
|
||||
|
||||
func TestConvertType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input any
|
||||
expected string
|
||||
}{
|
||||
{"string", "", "string"},
|
||||
{"int", int(0), "number"},
|
||||
{"int8", int8(0), "number"},
|
||||
{"int16", int16(0), "number"},
|
||||
{"int32", int32(0), "number"},
|
||||
{"int64", int64(0), "number"},
|
||||
{"uint", uint(0), "number"},
|
||||
{"float32", float32(0), "number"},
|
||||
{"float64", float64(0), "number"},
|
||||
{"bool", true, "boolean"},
|
||||
{"interface", (*any)(nil), "any | null"},
|
||||
{"slice of strings", []string{}, "string[]"},
|
||||
{"slice of ints", []int{}, "number[]"},
|
||||
{"map", map[string]any{}, "Record<string, any>"},
|
||||
{"struct", SimpleStruct{}, "SimpleStruct"},
|
||||
{"pointer to string", (*string)(nil), "string | null"},
|
||||
{"pointer to struct", (*SimpleStruct)(nil), "SimpleStruct | null"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Get Input Type
|
||||
var inputType reflect.Type
|
||||
if tt.input == nil {
|
||||
inputType = reflect.TypeOf((*any)(nil)).Elem()
|
||||
} else {
|
||||
inputType = reflect.TypeOf(tt.input)
|
||||
}
|
||||
|
||||
// Convert Type
|
||||
result := ConvertType(inputType)
|
||||
|
||||
// Verify Result
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectTypes(t *testing.T) {
|
||||
t.Run("simple struct", func(t *testing.T) {
|
||||
// Collect Types
|
||||
argsType := reflect.TypeOf(SimpleStruct{})
|
||||
fnType := reflect.TypeOf(func() (SimpleStruct, error) { return SimpleStruct{}, nil })
|
||||
ts := CollectTypes(argsType, fnType)
|
||||
|
||||
// Verify TypeSet
|
||||
require.NotNil(t, ts)
|
||||
assert.Len(t, ts.All(), 1)
|
||||
|
||||
// Verify SimpleStruct Definition
|
||||
def, ok := ts.Get("SimpleStruct")
|
||||
assert.True(t, ok)
|
||||
assert.Contains(t, def, "interface SimpleStruct")
|
||||
assert.Contains(t, def, "name: string")
|
||||
assert.Contains(t, def, "count: number")
|
||||
})
|
||||
|
||||
t.Run("nested struct", func(t *testing.T) {
|
||||
// Collect Types
|
||||
argsType := reflect.TypeOf(NestedStruct{})
|
||||
fnType := reflect.TypeOf(func() (NestedStruct, error) { return NestedStruct{}, nil })
|
||||
ts := CollectTypes(argsType, fnType)
|
||||
|
||||
// Verify TypeSet
|
||||
require.NotNil(t, ts)
|
||||
all := ts.All()
|
||||
assert.Len(t, all, 2) // NestedStruct and SimpleStruct
|
||||
|
||||
// Verify NestedStruct Definition
|
||||
def, ok := ts.Get("NestedStruct")
|
||||
assert.True(t, ok)
|
||||
assert.Contains(t, def, "interface NestedStruct")
|
||||
assert.Contains(t, def, "id: number")
|
||||
assert.Contains(t, def, "simple: SimpleStruct")
|
||||
assert.Contains(t, def, "pointer?: SimpleStruct | null")
|
||||
|
||||
// Verify SimpleStruct is Also Included
|
||||
_, ok = ts.Get("SimpleStruct")
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("optional fields", func(t *testing.T) {
|
||||
// Collect Types
|
||||
argsType := reflect.TypeOf(OptionalFields{})
|
||||
fnType := reflect.TypeOf(func() (OptionalFields, error) { return OptionalFields{}, nil })
|
||||
ts := CollectTypes(argsType, fnType)
|
||||
|
||||
// Verify TypeSet
|
||||
require.NotNil(t, ts)
|
||||
|
||||
// Verify OptionalFields Definition
|
||||
def, ok := ts.Get("OptionalFields")
|
||||
assert.True(t, ok)
|
||||
assert.Contains(t, def, "required: string")
|
||||
assert.Contains(t, def, "optional?: string | null")
|
||||
assert.Contains(t, def, "number?: number")
|
||||
})
|
||||
|
||||
t.Run("complex types", func(t *testing.T) {
|
||||
// Collect Types
|
||||
argsType := reflect.TypeOf(ComplexTypes{})
|
||||
fnType := reflect.TypeOf(func() (ComplexTypes, error) { return ComplexTypes{}, nil })
|
||||
ts := CollectTypes(argsType, fnType)
|
||||
|
||||
// Verify TypeSet
|
||||
require.NotNil(t, ts)
|
||||
|
||||
// Verify ComplexTypes Definition
|
||||
def, ok := ts.Get("ComplexTypes")
|
||||
assert.True(t, ok)
|
||||
assert.Contains(t, def, "strings: string[]")
|
||||
assert.Contains(t, def, "numbers: number[]")
|
||||
assert.Contains(t, def, "mapping: Record<string, any>")
|
||||
assert.Contains(t, def, "nested: SimpleStruct[]")
|
||||
})
|
||||
|
||||
t.Run("no return type", func(t *testing.T) {
|
||||
// Collect Types
|
||||
argsType := reflect.TypeOf(SimpleStruct{})
|
||||
fnType := reflect.TypeOf(func() error { return nil })
|
||||
ts := CollectTypes(argsType, fnType)
|
||||
|
||||
// Verify TypeSet - Only SimpleStruct from args
|
||||
require.NotNil(t, ts)
|
||||
assert.Len(t, ts.All(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTypeSet(t *testing.T) {
|
||||
t.Run("add and get", func(t *testing.T) {
|
||||
// Create TypeSet
|
||||
ts := NewTypeSet()
|
||||
|
||||
// Add Type
|
||||
err := ts.Add("User", "interface User { name: string }")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify Type
|
||||
def, ok := ts.Get("User")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "interface User { name: string }", def)
|
||||
})
|
||||
|
||||
t.Run("duplicate same definition", func(t *testing.T) {
|
||||
// Create TypeSet
|
||||
ts := NewTypeSet()
|
||||
|
||||
// Add Type
|
||||
err := ts.Add("User", "interface User { name: string }")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add Same Type with Same Definition - Should Not Error
|
||||
err = ts.Add("User", "interface User { name: string }")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify Count
|
||||
assert.Len(t, ts.All(), 1)
|
||||
})
|
||||
|
||||
t.Run("conflicting definitions", func(t *testing.T) {
|
||||
// Create TypeSet
|
||||
ts := NewTypeSet()
|
||||
|
||||
// Add Type
|
||||
err := ts.Add("User", "interface User { name: string }")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add Same Type with Different Definition - Should Error
|
||||
err = ts.Add("User", "interface User { id: number }")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "type conflict")
|
||||
assert.Contains(t, err.Error(), "User")
|
||||
})
|
||||
|
||||
t.Run("merge type sets", func(t *testing.T) {
|
||||
// Create First TypeSet
|
||||
ts1 := NewTypeSet()
|
||||
err := ts1.Add("User", "interface User { name: string }")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create Second TypeSet
|
||||
ts2 := NewTypeSet()
|
||||
err = ts2.Add("Post", "interface Post { title: string }")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Merge TypeSets
|
||||
err = ts1.Merge(ts2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify Merged Types
|
||||
assert.Len(t, ts1.All(), 2)
|
||||
_, ok := ts1.Get("User")
|
||||
assert.True(t, ok)
|
||||
_, ok = ts1.Get("Post")
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("merge with conflict", func(t *testing.T) {
|
||||
// Create First TypeSet
|
||||
ts1 := NewTypeSet()
|
||||
err := ts1.Add("User", "interface User { name: string }")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create Second TypeSet with Conflicting Type
|
||||
ts2 := NewTypeSet()
|
||||
err = ts2.Add("User", "interface User { id: number }")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Merge Should Fail Due to Conflict
|
||||
err = ts1.Merge(ts2)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "type conflict")
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtractName(t *testing.T) {
|
||||
tests := []struct {
|
||||
definition string
|
||||
expected string
|
||||
}{
|
||||
{"interface User { name: string }", "User"},
|
||||
{"interface MyType { }", "MyType"},
|
||||
{"type MyAlias = string", "MyAlias"},
|
||||
{"type ComplexType = { a: number }", "ComplexType"},
|
||||
{"invalid syntax here", ""},
|
||||
{"", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.definition, func(t *testing.T) {
|
||||
// Extract Name
|
||||
result := ExtractName(tt.definition)
|
||||
|
||||
// Verify Result
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateFunctionDecl(t *testing.T) {
|
||||
t.Run("simple function", func(t *testing.T) {
|
||||
// Generate Declaration
|
||||
argsType := reflect.TypeOf(SimpleStruct{})
|
||||
fnType := reflect.TypeOf(func() (SimpleStruct, error) { return SimpleStruct{}, nil })
|
||||
decl := GenerateFunctionDecl("myFunc", argsType, fnType, false)
|
||||
|
||||
// Verify Declaration
|
||||
assert.Equal(t, "declare function myFunc(name: string, count: number): SimpleStruct;", decl)
|
||||
})
|
||||
|
||||
t.Run("async function", func(t *testing.T) {
|
||||
// Generate Declaration
|
||||
argsType := reflect.TypeOf(SimpleStruct{})
|
||||
fnType := reflect.TypeOf(func() (SimpleStruct, error) { return SimpleStruct{}, nil })
|
||||
decl := GenerateFunctionDecl("myAsyncFunc", argsType, fnType, true)
|
||||
|
||||
// Verify Declaration
|
||||
assert.Equal(t, "declare function myAsyncFunc(name: string, count: number): Promise<SimpleStruct>;", decl)
|
||||
})
|
||||
|
||||
t.Run("function with optional fields", func(t *testing.T) {
|
||||
// Generate Declaration
|
||||
argsType := reflect.TypeOf(OptionalFields{})
|
||||
fnType := reflect.TypeOf(func() (OptionalFields, error) { return OptionalFields{}, nil })
|
||||
decl := GenerateFunctionDecl("optionalFunc", argsType, fnType, false)
|
||||
|
||||
// Verify Declaration
|
||||
assert.Contains(t, decl, "required: string")
|
||||
assert.Contains(t, decl, "optional?: string | null")
|
||||
assert.Contains(t, decl, "number?: number")
|
||||
})
|
||||
|
||||
t.Run("function with no return", func(t *testing.T) {
|
||||
// Generate Declaration
|
||||
argsType := reflect.TypeOf(SimpleStruct{})
|
||||
fnType := reflect.TypeOf(func() error { return nil })
|
||||
decl := GenerateFunctionDecl("noReturn", argsType, fnType, false)
|
||||
|
||||
// Verify Declaration
|
||||
assert.Equal(t, "declare function noReturn(name: string, count: number): any;", decl)
|
||||
})
|
||||
|
||||
t.Run("non-struct args returns empty", func(t *testing.T) {
|
||||
// Generate Declaration
|
||||
argsType := reflect.TypeOf("")
|
||||
fnType := reflect.TypeOf(func() error { return nil })
|
||||
decl := GenerateFunctionDecl("invalid", argsType, fnType, false)
|
||||
|
||||
// Verify Declaration
|
||||
assert.Equal(t, "", decl)
|
||||
})
|
||||
}
|
||||
92
internal/tsconvert/types.go
Normal file
92
internal/tsconvert/types.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// Package tsconvert provides utilities for converting Go types to TypeScript definitions.
|
||||
package tsconvert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// TypeDecl represents a TypeScript type declaration.
|
||||
type TypeDecl struct {
|
||||
Name string // e.g., "UserConfig"
|
||||
Definition string // e.g., "interface UserConfig { name: string }"
|
||||
}
|
||||
|
||||
// TypeSet manages a collection of type declarations with deduplication support.
|
||||
type TypeSet struct {
|
||||
types map[string]string // name -> definition
|
||||
}
|
||||
|
||||
// NewTypeSet creates a new empty TypeSet.
|
||||
func NewTypeSet() *TypeSet {
|
||||
return &TypeSet{
|
||||
types: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a type declaration to the set.
|
||||
// Returns an error if a type with the same name but different definition already exists.
|
||||
func (ts *TypeSet) Add(name, definition string) error {
|
||||
if existing, ok := ts.types[name]; ok {
|
||||
if existing != definition {
|
||||
return fmt.Errorf("type conflict: %s has conflicting definitions", name)
|
||||
}
|
||||
// Same name and definition, no conflict
|
||||
return nil
|
||||
}
|
||||
ts.types[name] = definition
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves a type definition by name.
|
||||
func (ts *TypeSet) Get(name string) (string, bool) {
|
||||
def, ok := ts.types[name]
|
||||
return def, ok
|
||||
}
|
||||
|
||||
// All returns all type declarations as a map.
|
||||
func (ts *TypeSet) All() map[string]string {
|
||||
result := make(map[string]string, len(ts.types))
|
||||
for k, v := range ts.types {
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Names returns all type names in the set.
|
||||
func (ts *TypeSet) Names() []string {
|
||||
names := make([]string, 0, len(ts.types))
|
||||
for name := range ts.types {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// Merge merges another TypeSet into this one.
|
||||
// Returns an error if there are conflicting definitions.
|
||||
func (ts *TypeSet) Merge(other *TypeSet) error {
|
||||
for name, def := range other.types {
|
||||
if err := ts.Add(name, def); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractName extracts the type name from a TypeScript declaration.
|
||||
// Supports "interface Name {...}" and "type Name = ..." patterns.
|
||||
func ExtractName(definition string) string {
|
||||
// Try interface pattern: "interface Name { ... }"
|
||||
interfaceRe := regexp.MustCompile(`^interface\s+(\w+)`)
|
||||
if matches := interfaceRe.FindStringSubmatch(definition); len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
// Try type alias pattern: "type Name = ..."
|
||||
typeRe := regexp.MustCompile(`^type\s+(\w+)`)
|
||||
if matches := typeRe.FindStringSubmatch(definition); len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user