goscript 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,618 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/types"
7
+ "strings"
8
+ )
9
+
10
+ // GoTypeContext specifies the context in which a Go type is being translated to TypeScript.
11
+ // This affects how certain types (especially pointers) are handled.
12
+ type GoTypeContext int
13
+
14
+ const (
15
+ // GoTypeContextGeneral is used for general type translation
16
+ GoTypeContextGeneral GoTypeContext = iota
17
+ // GoTypeContextFunctionReturn is used when translating types for function return values.
18
+ // In this context, pointer-to-struct types become `ClassName | null` instead of
19
+ // `$.Box<ClassName> | null` because function return values cannot be addressed.
20
+ GoTypeContextFunctionReturn
21
+ )
22
+
23
+ // WriteGoType is the main dispatcher for translating Go types to their TypeScript
24
+ // equivalents. It examines the type and delegates to more specialized type writer
25
+ // functions based on the specific Go type encountered.
26
+ //
27
+ // The context parameter controls how certain types (especially pointers) are handled:
28
+ // - GoTypeContextGeneral: Standard type translation
29
+ // - GoTypeContextFunctionReturn: Special handling for function return types where
30
+ // pointer-to-struct types become `ClassName | null` instead of `$.Box<ClassName> | null`
31
+ //
32
+ // It handles nil types as 'any' with a comment, and dispatches to appropriate
33
+ // type-specific writers for all other recognized Go types.
34
+ func (c *GoToTSCompiler) WriteGoType(typ types.Type, context GoTypeContext) {
35
+ if typ == nil {
36
+ c.tsw.WriteLiterally("any")
37
+ c.tsw.WriteCommentInline("nil type")
38
+ return
39
+ }
40
+
41
+ switch t := typ.(type) {
42
+ case *types.Basic:
43
+ c.WriteBasicType(t)
44
+ case *types.Named:
45
+ c.WriteNamedType(t)
46
+ case *types.Pointer:
47
+ if context == GoTypeContextFunctionReturn {
48
+ c.writePointerTypeForFunctionReturn(t)
49
+ } else {
50
+ c.WritePointerType(t)
51
+ }
52
+ case *types.Slice:
53
+ c.WriteSliceType(t)
54
+ case *types.Array:
55
+ c.WriteArrayType(t)
56
+ case *types.Map:
57
+ c.WriteMapType(t)
58
+ case *types.Chan:
59
+ c.WriteChannelType(t)
60
+ case *types.Interface:
61
+ c.WriteInterfaceType(t, nil) // No ast.InterfaceType available here
62
+ case *types.Signature:
63
+ c.WriteSignatureType(t)
64
+ case *types.Struct:
65
+ c.WriteStructType(t)
66
+ case *types.Alias:
67
+ c.WriteGoType(t.Underlying(), context)
68
+ case *types.TypeParam:
69
+ // For type parameters, write the type parameter name (e.g., "T", "K", etc.)
70
+ c.tsw.WriteLiterally(t.Obj().Name())
71
+ default:
72
+ // For other types, just write "any" and add a comment
73
+ c.tsw.WriteLiterally("any")
74
+ c.tsw.WriteCommentInlinef("unhandled type: %T", typ)
75
+ }
76
+ }
77
+
78
+ // writePointerTypeForFunctionReturn translates a Go pointer type (*T) to its TypeScript
79
+ // equivalent for function return types. Unlike WritePointerType, this function
80
+ // handles pointer-to-struct types specially: they become `ClassName | null` instead
81
+ // of `$.Box<ClassName> | null` because function return values cannot be addressed.
82
+ func (c *GoToTSCompiler) writePointerTypeForFunctionReturn(t *types.Pointer) {
83
+ elemType := t.Elem()
84
+
85
+ // Check if the element type is a struct (directly or via a named type)
86
+ isStructType := false
87
+ if _, ok := elemType.Underlying().(*types.Struct); ok {
88
+ isStructType = true
89
+ }
90
+
91
+ if isStructType {
92
+ // For pointer-to-struct in function returns, generate ClassName | null
93
+ c.WriteGoType(elemType, GoTypeContextFunctionReturn)
94
+ c.tsw.WriteLiterally(" | null")
95
+ } else {
96
+ // For pointer-to-primitive in function returns, still use boxing
97
+ c.tsw.WriteLiterally("$.Box<")
98
+ c.WriteGoType(elemType, GoTypeContextFunctionReturn)
99
+ c.tsw.WriteLiterally("> | null")
100
+ }
101
+ }
102
+
103
+ // WriteZeroValueForType writes the TypeScript representation of the zero value
104
+ // for a given Go type.
105
+ // It handles `types.Array` by recursively writing zero values for each element
106
+ // to form a TypeScript array literal (e.g., `[0, 0, 0]`).
107
+ // For `types.Basic` (like `bool`, `string`, numeric types), it writes the
108
+ // corresponding TypeScript zero value (`false`, `""`, `0`).
109
+ // For `[]byte`, it writes `new Uint8Array(0)`.
110
+ // Other types default to `null`. This function is primarily used for initializing
111
+ // arrays and variables where an explicit initializer is absent.
112
+ func (c *GoToTSCompiler) WriteZeroValueForType(typ any) {
113
+ switch t := typ.(type) {
114
+ case *types.Array:
115
+ c.tsw.WriteLiterally("[")
116
+ for i := 0; i < int(t.Len()); i++ {
117
+ if i > 0 {
118
+ c.tsw.WriteLiterally(", ")
119
+ }
120
+ c.WriteZeroValueForType(t.Elem())
121
+ }
122
+ c.tsw.WriteLiterally("]")
123
+ case *ast.Expr:
124
+ // For AST expressions, get the type and handle that instead
125
+ if expr := *t; expr != nil {
126
+ if typ := c.pkg.TypesInfo.TypeOf(expr); typ != nil {
127
+ c.WriteZeroValueForType(typ)
128
+ return
129
+ }
130
+ }
131
+ c.tsw.WriteLiterally("null")
132
+ case *types.Basic:
133
+ switch t.Kind() {
134
+ case types.Bool:
135
+ c.tsw.WriteLiterally("false")
136
+ case types.String:
137
+ c.tsw.WriteLiterally(`""`)
138
+ default:
139
+ c.tsw.WriteLiterally("0")
140
+ }
141
+ case *types.Named:
142
+ // Handle named types, especially struct types
143
+ if _, isStruct := t.Underlying().(*types.Struct); isStruct {
144
+ // Initialize struct types with a new instance
145
+ c.tsw.WriteLiterallyf("new %s()", t.Obj().Name())
146
+ return
147
+ }
148
+ // For other named types, use the zero value of the underlying type
149
+ c.WriteZeroValueForType(t.Underlying())
150
+ case *types.Slice:
151
+ // Check if it's a []byte slice
152
+ if elem, ok := t.Elem().(*types.Basic); ok && elem.Kind() == types.Uint8 {
153
+ c.tsw.WriteLiterally("new Uint8Array(0)")
154
+ return
155
+ }
156
+ // For other slice types, default to null
157
+ c.tsw.WriteLiterally("null")
158
+ case *types.Struct:
159
+ // For anonymous struct types, initialize with {}
160
+ c.tsw.WriteLiterally("{}")
161
+ case *types.TypeParam:
162
+ // For type parameters, use null! (non-null assertion) to avoid TypeScript
163
+ // casting errors with union types like string | Uint8Array
164
+ c.tsw.WriteLiterally("null!")
165
+ default:
166
+ c.tsw.WriteLiterally("null")
167
+ }
168
+ }
169
+
170
+ // WriteBasicType translates a Go basic type (primitives like int, string, bool)
171
+ // to its TypeScript equivalent.
172
+ // It handles untyped constants by mapping them to appropriate TypeScript types
173
+ // (boolean, number, string, null) and uses GoBuiltinToTypescript for typed primitives.
174
+ func (c *GoToTSCompiler) WriteBasicType(t *types.Basic) {
175
+ name := t.Name()
176
+
177
+ // Handle untyped constants by mapping them to appropriate TypeScript types
178
+ if t.Info()&types.IsUntyped != 0 {
179
+ switch t.Kind() {
180
+ case types.UntypedBool:
181
+ c.tsw.WriteLiterally("boolean")
182
+ return
183
+ case types.UntypedInt, types.UntypedFloat, types.UntypedComplex, types.UntypedRune:
184
+ c.tsw.WriteLiterally("number")
185
+ return
186
+ case types.UntypedString:
187
+ c.tsw.WriteLiterally("string")
188
+ return
189
+ case types.UntypedNil:
190
+ c.tsw.WriteLiterally("null")
191
+ return
192
+ }
193
+ }
194
+
195
+ // For typed basic types, use the existing mapping
196
+ if tsType, ok := GoBuiltinToTypescript(name); ok {
197
+ c.tsw.WriteLiterally(tsType)
198
+ } else {
199
+ c.tsw.WriteLiterally(name)
200
+ }
201
+ }
202
+
203
+ // WriteNamedType translates a Go named type to its TypeScript equivalent.
204
+ // It specially handles the error interface as $.GoError, and uses the original
205
+ // type name for other named types. For generic types, it includes type arguments.
206
+ func (c *GoToTSCompiler) WriteNamedType(t *types.Named) {
207
+ // Check if the named type is the error interface
208
+ if iface, ok := t.Underlying().(*types.Interface); ok && iface.String() == "interface{Error() string}" {
209
+ c.tsw.WriteLiterally("$.GoError")
210
+ return
211
+ }
212
+
213
+ // Use Obj().Name() for the original defined name
214
+ c.tsw.WriteLiterally(t.Obj().Name())
215
+
216
+ // For generic types, include type arguments
217
+ if t.TypeArgs() != nil && t.TypeArgs().Len() > 0 {
218
+ c.tsw.WriteLiterally("<")
219
+ for i := 0; i < t.TypeArgs().Len(); i++ {
220
+ if i > 0 {
221
+ c.tsw.WriteLiterally(", ")
222
+ }
223
+ c.WriteGoType(t.TypeArgs().At(i), GoTypeContextGeneral)
224
+ }
225
+ c.tsw.WriteLiterally(">")
226
+ }
227
+ }
228
+
229
+ // WritePointerType translates a Go pointer type (*T) to its TypeScript equivalent.
230
+ // It generates $.Box<T_ts> | null, where T_ts is the translated element type.
231
+ func (c *GoToTSCompiler) WritePointerType(t *types.Pointer) {
232
+ c.tsw.WriteLiterally("$.Box<")
233
+ c.WriteGoType(t.Elem(), GoTypeContextGeneral)
234
+ c.tsw.WriteLiterally("> | null") // Pointers are always nullable
235
+ }
236
+
237
+ // WriteSliceType translates a Go slice type ([]T) to its TypeScript equivalent.
238
+ // It generates $.Slice<T_ts>, where T_ts is the translated element type.
239
+ // For []byte, it generates Uint8Array.
240
+ func (c *GoToTSCompiler) WriteSliceType(t *types.Slice) {
241
+ // Check if it's a []byte slice
242
+ if elem, ok := t.Elem().(*types.Basic); ok && elem.Kind() == types.Uint8 {
243
+ c.tsw.WriteLiterally("Uint8Array")
244
+ return
245
+ }
246
+ c.tsw.WriteLiterally("$.Slice<")
247
+ c.WriteGoType(t.Elem(), GoTypeContextGeneral)
248
+ c.tsw.WriteLiterally(">")
249
+ }
250
+
251
+ // WriteArrayType translates a Go array type ([N]T) to its TypeScript equivalent.
252
+ // It generates T_ts[], where T_ts is the translated element type.
253
+ func (c *GoToTSCompiler) WriteArrayType(t *types.Array) {
254
+ c.WriteGoType(t.Elem(), GoTypeContextGeneral)
255
+ c.tsw.WriteLiterally("[]") // Arrays cannot be nil
256
+ }
257
+
258
+ // WriteMapType translates a Go map type (map[K]V) to its TypeScript equivalent.
259
+ // It generates Map<K_ts, V_ts>, where K_ts and V_ts are the translated key
260
+ // and element types respectively.
261
+ func (c *GoToTSCompiler) WriteMapType(t *types.Map) {
262
+ c.tsw.WriteLiterally("Map<")
263
+ c.WriteGoType(t.Key(), GoTypeContextGeneral)
264
+ c.tsw.WriteLiterally(", ")
265
+ c.WriteGoType(t.Elem(), GoTypeContextGeneral)
266
+ c.tsw.WriteLiterally(">")
267
+ }
268
+
269
+ // WriteChannelType translates a Go channel type (chan T) to its TypeScript equivalent.
270
+ // It generates $.Channel<T_ts> | null, where T_ts is the translated element type.
271
+ // Channels are nilable in Go, so they are represented as nullable types in TypeScript.
272
+ func (c *GoToTSCompiler) WriteChannelType(t *types.Chan) {
273
+ c.tsw.WriteLiterally("$.Channel<")
274
+ c.WriteGoType(t.Elem(), GoTypeContextGeneral)
275
+ c.tsw.WriteLiterally("> | null")
276
+ }
277
+
278
+ // WriteFuncType translates a Go function type (`ast.FuncType`) into a TypeScript
279
+ // function signature.
280
+ // The signature is of the form `(param1: type1, param2: type2) => returnType`.
281
+ // - Parameters are written using `WriteFieldList`.
282
+ // - Return types:
283
+ // - If there are no results, the return type is `void`.
284
+ // - If there's a single, unnamed result, it's `resultType`.
285
+ // - If there are multiple or named results, it's a tuple type `[typeA, typeB]`.
286
+ // - If `isAsync` is true (indicating the function is known to perform async
287
+ // operations like channel interactions or contains `go` or `defer` with async calls),
288
+ // the return type is wrapped in `Promise<>` (e.g., `Promise<void>`, `Promise<number>`).
289
+ func (c *GoToTSCompiler) WriteFuncType(exp *ast.FuncType, isAsync bool) {
290
+ c.tsw.WriteLiterally("(")
291
+ c.WriteFieldList(exp.Params, true) // true = arguments
292
+ c.tsw.WriteLiterally(")")
293
+ if exp.Results != nil && len(exp.Results.List) > 0 {
294
+ // Use colon for return type annotation
295
+ c.tsw.WriteLiterally(": ")
296
+ if isAsync {
297
+ c.tsw.WriteLiterally("Promise<")
298
+ }
299
+ if len(exp.Results.List) == 1 && len(exp.Results.List[0].Names) == 0 {
300
+ // Single unnamed return type
301
+ typ := c.pkg.TypesInfo.TypeOf(exp.Results.List[0].Type)
302
+ c.WriteGoType(typ, GoTypeContextFunctionReturn)
303
+ } else {
304
+ // Multiple or named return types -> tuple
305
+ c.tsw.WriteLiterally("[")
306
+ for i, field := range exp.Results.List {
307
+ if i > 0 {
308
+ c.tsw.WriteLiterally(", ")
309
+ }
310
+ typ := c.pkg.TypesInfo.TypeOf(field.Type)
311
+ c.WriteGoType(typ, GoTypeContextFunctionReturn)
312
+ }
313
+ c.tsw.WriteLiterally("]")
314
+ }
315
+ if isAsync {
316
+ c.tsw.WriteLiterally(">")
317
+ }
318
+ } else {
319
+ // No return value -> void
320
+ if isAsync {
321
+ c.tsw.WriteLiterally(": Promise<void>")
322
+ } else {
323
+ c.tsw.WriteLiterally(": void")
324
+ }
325
+ }
326
+ }
327
+
328
+ // WriteInterfaceType translates a Go interface type to its TypeScript equivalent.
329
+ // It specially handles the error interface as $.GoError, and delegates to
330
+ // writeInterfaceStructure for other interface types, prepending "null | ".
331
+ // If astNode is provided (e.g., from a type spec), comments for methods will be included.
332
+ func (c *GoToTSCompiler) WriteInterfaceType(t *types.Interface, astNode *ast.InterfaceType) {
333
+ // Handle the built-in error interface specifically
334
+ if t.String() == "interface{Error() string}" {
335
+ c.tsw.WriteLiterally("$.GoError")
336
+ return
337
+ }
338
+
339
+ // Prepend "null | " for all other interfaces.
340
+ // writeInterfaceStructure will handle the actual structure like "{...}" or "any".
341
+ c.tsw.WriteLiterally("null | ")
342
+ c.writeInterfaceStructure(t, astNode)
343
+ }
344
+
345
+ // WriteSignatureType translates a Go function signature to its TypeScript equivalent.
346
+ // It generates (param1: type1, param2: type2, ...): returnType for function types.
347
+ func (c *GoToTSCompiler) WriteSignatureType(t *types.Signature) {
348
+ c.tsw.WriteLiterally("(")
349
+ c.tsw.WriteLiterally("(")
350
+ params := t.Params()
351
+ for i := 0; i < params.Len(); i++ {
352
+ if i > 0 {
353
+ c.tsw.WriteLiterally(", ")
354
+ }
355
+
356
+ param := params.At(i)
357
+ paramSlice, paramIsSlice := param.Type().(*types.Slice)
358
+
359
+ paramVariadic := i == params.Len()-1 && t.Variadic()
360
+ if paramVariadic {
361
+ c.tsw.WriteLiterally("...")
362
+ }
363
+
364
+ // Use parameter name if available, otherwise use p0, p1, etc.
365
+ if param.Name() != "" {
366
+ c.tsw.WriteLiterally(param.Name())
367
+ } else {
368
+ c.tsw.WriteLiterallyf("p%d", i)
369
+ }
370
+ c.tsw.WriteLiterally(": ")
371
+
372
+ if paramVariadic && paramIsSlice {
373
+ c.WriteGoType(paramSlice.Elem(), GoTypeContextGeneral)
374
+ c.tsw.WriteLiterally("[]")
375
+ } else {
376
+ c.WriteGoType(param.Type(), GoTypeContextGeneral)
377
+ }
378
+ }
379
+ c.tsw.WriteLiterally(")")
380
+
381
+ // Handle return types
382
+ c.tsw.WriteLiterally(" => ")
383
+ results := t.Results()
384
+ if results.Len() == 0 {
385
+ c.tsw.WriteLiterally("void")
386
+ } else if results.Len() == 1 {
387
+ c.WriteGoType(results.At(0).Type(), GoTypeContextFunctionReturn)
388
+ } else {
389
+ // Multiple return values -> tuple
390
+ c.tsw.WriteLiterally("[")
391
+ for i := 0; i < results.Len(); i++ {
392
+ if i > 0 {
393
+ c.tsw.WriteLiterally(", ")
394
+ }
395
+ c.WriteGoType(results.At(i).Type(), GoTypeContextFunctionReturn)
396
+ }
397
+ c.tsw.WriteLiterally("]")
398
+ }
399
+ c.tsw.WriteLiterally(") | null")
400
+ }
401
+
402
+ // writeInterfaceStructure translates a Go `types.Interface` into its TypeScript structural representation.
403
+ // If astNode is provided, it's used to fetch comments for methods.
404
+ // For example, an interface `interface { MethodA(x int) string; EmbeddedB }` might become
405
+ // `{ MethodA(_p0: number): string; } & B_ts`.
406
+ func (c *GoToTSCompiler) writeInterfaceStructure(iface *types.Interface, astNode *ast.InterfaceType) {
407
+ // Handle empty interface interface{}
408
+ if iface.NumExplicitMethods() == 0 && iface.NumEmbeddeds() == 0 {
409
+ c.tsw.WriteLiterally("any") // Matches current behavior for interface{}
410
+ return
411
+ }
412
+
413
+ // Keep track if we've written any part (methods or first embedded type)
414
+ // to correctly place " & " separators.
415
+ firstPartWritten := false
416
+
417
+ // Handle explicit methods
418
+ if iface.NumExplicitMethods() > 0 {
419
+ c.tsw.WriteLiterally("{") // Opening brace for the object type
420
+ c.tsw.Indent(1)
421
+ c.tsw.WriteLine("") // Newline after opening brace, before the first method
422
+
423
+ for i := 0; i < iface.NumExplicitMethods(); i++ {
424
+ method := iface.ExplicitMethod(i)
425
+ sig := method.Type().(*types.Signature)
426
+
427
+ // Find corresponding ast.Field for comments if astNode is available
428
+ var astField *ast.Field
429
+ if astNode != nil && astNode.Methods != nil {
430
+ for _, f := range astNode.Methods.List {
431
+ // Ensure the field is a named method (not an embedded interface)
432
+ if len(f.Names) > 0 && f.Names[0].Name == method.Name() {
433
+ astField = f
434
+ break
435
+ }
436
+ }
437
+ }
438
+
439
+ // Write comments if astField is found
440
+ if astField != nil {
441
+ if astField.Doc != nil {
442
+ c.WriteDoc(astField.Doc) // WriteDoc handles newlines
443
+ }
444
+ if astField.Comment != nil { // For trailing comments on the same line in Go AST
445
+ c.WriteDoc(astField.Comment)
446
+ }
447
+ }
448
+
449
+ c.tsw.WriteLiterally(method.Name())
450
+ c.tsw.WriteLiterally("(") // Start params
451
+ params := sig.Params()
452
+ for j := 0; j < params.Len(); j++ {
453
+ if j > 0 {
454
+ c.tsw.WriteLiterally(", ")
455
+ }
456
+ paramVar := params.At(j)
457
+ paramName := paramVar.Name()
458
+ if paramName == "" || paramName == "_" {
459
+ paramName = fmt.Sprintf("_p%d", j)
460
+ }
461
+ c.tsw.WriteLiterally(paramName)
462
+ c.tsw.WriteLiterally(": ")
463
+ c.WriteGoType(paramVar.Type(), GoTypeContextGeneral) // Recursive call for param type
464
+ }
465
+ c.tsw.WriteLiterally(")") // End params
466
+
467
+ // Return type
468
+ c.tsw.WriteLiterally(": ")
469
+ results := sig.Results()
470
+ if results.Len() == 0 {
471
+ c.tsw.WriteLiterally("void")
472
+ } else if results.Len() == 1 {
473
+ c.WriteGoType(results.At(0).Type(), GoTypeContextFunctionReturn) // Recursive call for result type
474
+ } else {
475
+ c.tsw.WriteLiterally("[")
476
+ for j := 0; j < results.Len(); j++ {
477
+ if j > 0 {
478
+ c.tsw.WriteLiterally(", ")
479
+ }
480
+ c.WriteGoType(results.At(j).Type(), GoTypeContextFunctionReturn) // Recursive call for result type
481
+ }
482
+ c.tsw.WriteLiterally("]")
483
+ }
484
+ c.tsw.WriteLine("") // newline for each method
485
+ }
486
+ c.tsw.Indent(-1)
487
+ c.tsw.WriteLiterally("}") // Closing brace for the object type
488
+ firstPartWritten = true
489
+ }
490
+
491
+ // Handle embedded types
492
+ if iface.NumEmbeddeds() > 0 {
493
+ for i := 0; i < iface.NumEmbeddeds(); i++ {
494
+ if firstPartWritten {
495
+ c.tsw.WriteLiterally(" & ")
496
+ } else {
497
+ // This is the first part being written (no explicit methods, only embedded)
498
+ firstPartWritten = true
499
+ }
500
+ embeddedType := iface.EmbeddedType(i)
501
+ // When WriteGoType encounters an interface, it will call WriteInterfaceType
502
+ // which will pass nil for astNode, so comments for deeply embedded interface literals
503
+ // might not be available unless they are named types.
504
+ c.WriteGoType(embeddedType, GoTypeContextGeneral)
505
+ }
506
+ }
507
+ }
508
+
509
+ // getTypeString is a utility function that converts a Go `types.Type` into its
510
+ // TypeScript type string representation. It achieves this by creating a temporary
511
+ // `GoToTSCompiler` and `TSCodeWriter` (writing to a `strings.Builder`) and then
512
+ // calling `WriteGoType` on the provided Go type. This allows reusing the main
513
+ // type translation logic to get a string representation of the TypeScript type.
514
+ func (c *GoToTSCompiler) getTypeString(goType types.Type) string {
515
+ var typeStr strings.Builder
516
+ writer := NewTSCodeWriter(&typeStr)
517
+ tempCompiler := NewGoToTSCompiler(writer, c.pkg, c.analysis)
518
+ tempCompiler.WriteGoType(goType, GoTypeContextGeneral)
519
+ return typeStr.String()
520
+ }
521
+
522
+ // WriteStructType translates a Go struct type definition (`ast.StructType`)
523
+ // into a TypeScript anonymous object type (e.g., `{ Field1: Type1; Field2: Type2 }`).
524
+ // If the struct has no fields, it writes `{}`. Otherwise, it delegates to
525
+ // `WriteFieldList` to generate the list of field definitions.
526
+ // Note: This is for anonymous struct type literals. Named struct types are usually
527
+ // handled as classes via `WriteTypeSpec`.
528
+ func (c *GoToTSCompiler) WriteStructType(t *types.Struct) {
529
+ // Generate an interface with the struct's fields
530
+ c.tsw.WriteLiterally("{ ")
531
+ // Add field properties to the interface
532
+ for i := range t.NumFields() {
533
+ field := t.Field(i)
534
+ if i > 0 {
535
+ c.tsw.WriteLiterally("; ")
536
+ }
537
+ c.tsw.WriteLiterally(field.Name() + "?: ")
538
+ c.WriteGoType(field.Type(), GoTypeContextGeneral)
539
+ }
540
+ c.tsw.WriteLiterally(" }")
541
+ }
542
+
543
+ // WriteTypeParameters translates Go type parameters to TypeScript generic parameters.
544
+ // It handles the TypeParams field of ast.FuncDecl and ast.TypeSpec to generate
545
+ // TypeScript generic parameter lists like <T extends SomeConstraint, U extends OtherConstraint>.
546
+ func (c *GoToTSCompiler) WriteTypeParameters(typeParams *ast.FieldList) {
547
+ if typeParams == nil || len(typeParams.List) == 0 {
548
+ return
549
+ }
550
+
551
+ c.tsw.WriteLiterally("<")
552
+ for i, field := range typeParams.List {
553
+ if i > 0 {
554
+ c.tsw.WriteLiterally(", ")
555
+ }
556
+ // Write each type parameter name and constraint
557
+ for j, name := range field.Names {
558
+ if j > 0 {
559
+ c.tsw.WriteLiterally(", ")
560
+ }
561
+ c.tsw.WriteLiterally(name.Name)
562
+
563
+ // Write constraint if present
564
+ if field.Type != nil {
565
+ c.tsw.WriteLiterally(" extends ")
566
+ c.WriteTypeConstraint(field.Type)
567
+ }
568
+ }
569
+ }
570
+ c.tsw.WriteLiterally(">")
571
+ }
572
+
573
+ // WriteTypeConstraint translates Go type constraints to TypeScript constraint expressions.
574
+ // It handles different constraint types including:
575
+ // - Union types: []byte | string -> string | Uint8Array
576
+ // - Interface types: interface{Method()} -> {Method(): void}
577
+ // - Basic types: any -> any, comparable -> $.Comparable
578
+ func (c *GoToTSCompiler) WriteTypeConstraint(constraint ast.Expr) {
579
+ switch t := constraint.(type) {
580
+ case *ast.Ident:
581
+ // Handle predeclared constraints
582
+ switch t.Name {
583
+ case "any":
584
+ c.tsw.WriteLiterally("any")
585
+ case "comparable":
586
+ c.tsw.WriteLiterally("$.Comparable")
587
+ default:
588
+ // Use the type directly
589
+ c.WriteTypeExpr(t)
590
+ }
591
+ case *ast.BinaryExpr:
592
+ // Handle union types like []byte | string
593
+ if t.Op.String() == "|" {
594
+ c.WriteTypeConstraint(t.X)
595
+ c.tsw.WriteLiterally(" | ")
596
+ c.WriteTypeConstraint(t.Y)
597
+ } else {
598
+ // Fallback for other binary expressions
599
+ c.WriteTypeExpr(constraint)
600
+ }
601
+ case *ast.InterfaceType:
602
+ // Handle interface constraints
603
+ c.WriteTypeExpr(constraint)
604
+ case *ast.ArrayType:
605
+ // Handle []byte specifically
606
+ if ident, ok := t.Elt.(*ast.Ident); ok && ident.Name == "byte" {
607
+ c.tsw.WriteLiterally("Uint8Array")
608
+ } else {
609
+ c.WriteTypeExpr(constraint)
610
+ }
611
+ case *ast.SliceExpr:
612
+ // Handle slice types in constraints
613
+ c.WriteTypeExpr(constraint)
614
+ default:
615
+ // Fallback: use the standard type expression writer
616
+ c.WriteTypeExpr(constraint)
617
+ }
618
+ }
package/go.mod CHANGED
@@ -4,13 +4,14 @@ go 1.24.3
4
4
 
5
5
  require (
6
6
  github.com/aperturerobotics/cli v1.0.0
7
+ github.com/aperturerobotics/util v1.30.0
7
8
  github.com/pkg/errors v0.9.1
8
9
  github.com/sirupsen/logrus v1.9.3
9
10
  golang.org/x/tools v0.33.0
10
11
  )
11
12
 
12
13
  require (
13
- github.com/aperturerobotics/common v0.21.2 // indirect
14
+ github.com/aperturerobotics/common v0.22.1 // indirect
14
15
  github.com/stretchr/testify v1.10.0 // indirect
15
16
  github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
16
17
  golang.org/x/mod v0.24.0 // indirect
package/go.sum CHANGED
@@ -1,7 +1,9 @@
1
1
  github.com/aperturerobotics/cli v1.0.0 h1:s3xT2h7eBih4/4yZKTn/HQ6P+qpk6ygWZl2416xAI1M=
2
2
  github.com/aperturerobotics/cli v1.0.0/go.mod h1:wtlINjMcKuwyV1x4ftReuA6hHZcPB8kPMXHyQqGFCSc=
3
- github.com/aperturerobotics/common v0.21.2 h1:fqnPL5Oovpd8nDaNBYGiD1UpZhcH/JfpsS8gt5iBDyA=
4
- github.com/aperturerobotics/common v0.21.2/go.mod h1:FrecdNcsYvVS8RcWCR8FUkKFh+XmouFOYKHpBdMqqBA=
3
+ github.com/aperturerobotics/common v0.22.1 h1:wxTV9wSgfAM9jYUuSzNFzUeC28DQMBgDO3iGahlHeaY=
4
+ github.com/aperturerobotics/common v0.22.1/go.mod h1:wsPfDVCTNpGHddg/MSfm84rKoO4GAvb+TQtATXz+pKY=
5
+ github.com/aperturerobotics/util v1.30.0 h1:OKhFVPnAfR8/dfVNV27EtMr27C0kzwPiStoCwKiint0=
6
+ github.com/aperturerobotics/util v1.30.0/go.mod h1:T97YTP+FVLegYo5rylOVaPuTLyZyiDqYxD5zVLI9YAc=
5
7
  github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6
8
  github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7
9
  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "goscript",
3
3
  "description": "Go to TypeScript transpiler",
4
- "version": "0.0.21",
4
+ "version": "0.0.23",
5
5
  "author": {
6
6
  "name": "Aperture Robotics LLC.",
7
7
  "email": "support@aperture.us",
@@ -34,10 +34,10 @@
34
34
  "default": "./dist/compiler/index.js"
35
35
  }
36
36
  },
37
- "./builtin": {
37
+ "./gs/builtin": {
38
38
  "import": {
39
- "types": "./dist/builtin/builtin.d.ts",
40
- "default": "./dist/builtin/builtin.js"
39
+ "types": "./dist/gs/builtin/builtin.d.ts",
40
+ "default": "./dist/gs/builtin/builtin.js"
41
41
  }
42
42
  }
43
43
  },
@@ -52,7 +52,7 @@
52
52
  "format": "npm run format:go && npm run format:js && npm run format:config",
53
53
  "format:config": "prettier --write tsconfig.json package.json",
54
54
  "format:go": "gofumpt -w .",
55
- "format:js": "prettier --write './{src,builtin,example}/**/(*.ts|*.tsx|*.html|*.css|*.scss)'",
55
+ "format:js": "prettier --write './{src,gs,example}/**/(*.ts|*.tsx|*.html|*.css|*.scss)'",
56
56
  "release": "npm run release:version && npm run release:commit",
57
57
  "release:minor": "npm run release:version:minor && npm run release:commit",
58
58
  "release:version": "npm version patch -m \"release: v%s\" --no-git-tag-version",
@@ -83,7 +83,7 @@
83
83
  },
84
84
  "devDependencies": {
85
85
  "@eslint/js": "^9.25.1",
86
- "@types/node": "^22.15.2",
86
+ "@types/node": "^22.15.18",
87
87
  "@typescript-eslint/eslint-plugin": "^8.31.0",
88
88
  "@typescript-eslint/parser": "^8.31.0",
89
89
  "eslint": "^9.25.1",