goscript 0.0.8 → 0.0.11

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.
Files changed (78) hide show
  1. package/builtin/builtin.ts +277 -1
  2. package/cmd/goscript/cmd_compile.go +8 -6
  3. package/compiler/compile.go +28 -11
  4. package/compiler/compile_decls.go +12 -0
  5. package/compiler/compile_expr.go +178 -27
  6. package/compiler/compile_spec.go +285 -5
  7. package/compiler/compile_stmt.go +160 -49
  8. package/compiler/writer.go +2 -2
  9. package/dist/builtin/builtin.d.ts +112 -1
  10. package/dist/builtin/builtin.js +199 -0
  11. package/dist/builtin/builtin.js.map +1 -1
  12. package/dist/compliance/tests/array_literal/array_literal.gs.js +1 -0
  13. package/dist/compliance/tests/array_literal/array_literal.gs.js.map +1 -1
  14. package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.d.ts +1 -0
  15. package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js +82 -0
  16. package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js.map +1 -0
  17. package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js +4 -0
  18. package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js.map +1 -1
  19. package/dist/compliance/tests/copy_independence/copy_independence.gs.js +3 -1
  20. package/dist/compliance/tests/copy_independence/copy_independence.gs.js.map +1 -1
  21. package/dist/compliance/tests/defer_statement/defer_statement.gs.d.ts +1 -0
  22. package/dist/compliance/tests/defer_statement/defer_statement.gs.js +75 -0
  23. package/dist/compliance/tests/defer_statement/defer_statement.gs.js.map +1 -0
  24. package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.d.ts +1 -0
  25. package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js +37 -0
  26. package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js.map +1 -0
  27. package/dist/compliance/tests/float64/float64.gs.js +1 -0
  28. package/dist/compliance/tests/float64/float64.gs.js.map +1 -1
  29. package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.d.ts +1 -0
  30. package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js +14 -0
  31. package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js.map +1 -0
  32. package/dist/compliance/tests/func_literal/func_literal.gs.js.map +1 -1
  33. package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.d.ts +2 -0
  34. package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js +4 -0
  35. package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js.map +1 -1
  36. package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.d.ts +1 -0
  37. package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js +34 -0
  38. package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js.map +1 -0
  39. package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js +8 -2
  40. package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js.map +1 -1
  41. package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js +16 -2
  42. package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js.map +1 -1
  43. package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.d.ts +1 -0
  44. package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js +51 -0
  45. package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js.map +1 -0
  46. package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js +3 -1
  47. package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js.map +1 -1
  48. package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js +3 -0
  49. package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js.map +1 -1
  50. package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js +3 -0
  51. package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js.map +1 -1
  52. package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js +3 -0
  53. package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js.map +1 -1
  54. package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js +3 -0
  55. package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js.map +1 -1
  56. package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js +3 -0
  57. package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js.map +1 -1
  58. package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js +3 -0
  59. package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js.map +1 -1
  60. package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js +3 -1
  61. package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js.map +1 -1
  62. package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js +1 -0
  63. package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js.map +1 -1
  64. package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js +3 -1
  65. package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js.map +1 -1
  66. package/dist/compliance/tests/slices/slices.gs.js +280 -7
  67. package/dist/compliance/tests/slices/slices.gs.js.map +1 -1
  68. package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.js.map +1 -1
  69. package/dist/compliance/tests/struct_embedding/struct_embedding.gs.d.ts +1 -0
  70. package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js +48 -0
  71. package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js.map +1 -0
  72. package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js +3 -0
  73. package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js.map +1 -1
  74. package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js +3 -0
  75. package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js.map +1 -1
  76. package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js +3 -1
  77. package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js.map +1 -1
  78. package/package.json +4 -3
@@ -3,6 +3,9 @@ package compiler
3
3
  import (
4
4
  "fmt"
5
5
  "go/ast"
6
+ "go/types"
7
+ "sort"
8
+ "strings"
6
9
  )
7
10
 
8
11
  // WriteSpec writes a specification to the output.
@@ -24,6 +27,102 @@ func (c *GoToTSCompiler) WriteSpec(a ast.Spec) error {
24
27
  return nil
25
28
  }
26
29
 
30
+ // collectMethodNames returns a comma-separated string of method names for a struct
31
+ func (c *GoToTSCompiler) collectMethodNames(structName string) string {
32
+ var methodNames []string
33
+
34
+ for _, fileSyntax := range c.pkg.Syntax {
35
+ for _, decl := range fileSyntax.Decls {
36
+ funcDecl, isFunc := decl.(*ast.FuncDecl)
37
+ if !isFunc || funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 {
38
+ continue // Skip non-functions or functions without receivers
39
+ }
40
+
41
+ // Check if the receiver type matches the struct name
42
+ recvField := funcDecl.Recv.List[0]
43
+ recvType := recvField.Type
44
+ // Handle pointer receivers (*MyStruct) and value receivers (MyStruct)
45
+ if starExpr, ok := recvType.(*ast.StarExpr); ok {
46
+ recvType = starExpr.X // Get the type being pointed to
47
+ }
48
+
49
+ // Check if the receiver identifier name matches the struct name
50
+ if ident, ok := recvType.(*ast.Ident); ok && ident.Name == structName {
51
+ // Found a method for this struct
52
+ methodNames = append(methodNames, fmt.Sprintf("'%s'", funcDecl.Name.Name))
53
+ }
54
+ }
55
+ }
56
+
57
+ return strings.Join(methodNames, ", ")
58
+ }
59
+
60
+ // getTypeExprName returns a string representation of a type expression.
61
+ func (c *GoToTSCompiler) getTypeExprName(expr ast.Expr) string {
62
+ switch t := expr.(type) {
63
+ case *ast.Ident:
64
+ return t.Name
65
+ case *ast.SelectorExpr:
66
+ return fmt.Sprintf("%s.%s", c.getTypeExprName(t.X), t.Sel.Name)
67
+ case *ast.StarExpr:
68
+ return c.getTypeExprName(t.X) // Unwrap pointer type
69
+ default:
70
+ return "embedded" // Fallback for complex type expressions
71
+ }
72
+ }
73
+
74
+ // collectInterfaceMethods returns a comma-separated string of method names for an interface
75
+ func (c *GoToTSCompiler) collectInterfaceMethods(interfaceType *ast.InterfaceType) string {
76
+ // Use a map to ensure uniqueness of method names
77
+ methodNamesMap := make(map[string]struct{})
78
+
79
+ if interfaceType.Methods != nil {
80
+ for _, method := range interfaceType.Methods.List {
81
+ if len(method.Names) > 0 {
82
+ // Named method
83
+ methodNamesMap[method.Names[0].Name] = struct{}{}
84
+ } else {
85
+ // Embedded interface - resolve it and collect its methods
86
+ embeddedType := method.Type
87
+
88
+ // Resolve the embedded interface using type information
89
+ if tv, ok := c.pkg.TypesInfo.Types[embeddedType]; ok && tv.Type != nil {
90
+ // Get the underlying interface type
91
+ var ifaceType *types.Interface
92
+ if named, ok := tv.Type.(*types.Named); ok {
93
+ if underlying, ok := named.Underlying().(*types.Interface); ok {
94
+ ifaceType = underlying
95
+ }
96
+ } else if underlying, ok := tv.Type.(*types.Interface); ok {
97
+ ifaceType = underlying
98
+ }
99
+
100
+ // Collect methods from the interface
101
+ if ifaceType != nil {
102
+ for i := 0; i < ifaceType.NumMethods(); i++ {
103
+ methodNamesMap[ifaceType.Method(i).Name()] = struct{}{}
104
+ }
105
+ }
106
+ } else {
107
+ // Couldn't resolve the embedded interface
108
+ c.tsw.WriteCommentLine("// Note: Some embedded interface methods may not be fully resolved")
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ // Convert the map to a slice for a deterministic output order
115
+ var methodNames []string
116
+ for name := range methodNamesMap {
117
+ methodNames = append(methodNames, fmt.Sprintf("'%s'", name))
118
+ }
119
+
120
+ // Sort for deterministic output
121
+ sort.Strings(methodNames)
122
+
123
+ return strings.Join(methodNames, ", ")
124
+ }
125
+
27
126
  // WriteTypeSpec writes the type specification to the output.
28
127
  func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
29
128
  if a.Doc != nil {
@@ -35,11 +134,27 @@ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
35
134
 
36
135
  switch t := a.Type.(type) {
37
136
  case *ast.StructType:
38
- // write the class definition
137
+ // Detect embedded struct (anonymous field) to support Go struct embedding.
138
+ var embeddedExpr ast.Expr
139
+ for _, f := range t.Fields.List {
140
+ if len(f.Names) == 0 { // anonymous field ⇒ embedded type
141
+ embeddedExpr = f.Type
142
+ break // only the first embedded struct is supported for now
143
+ }
144
+ }
145
+ // Write the class header, adding "extends Embedded" when an embedded struct is present.
39
146
  c.tsw.WriteLiterally("class ")
40
147
  if err := c.WriteValueExpr(a.Name); err != nil { // Class name is a value identifier
41
148
  return err
42
149
  }
150
+ if embeddedExpr != nil {
151
+ // For pointer embedding (*T) unwrap the star so we extend T, not (T|null)
152
+ if star, ok := embeddedExpr.(*ast.StarExpr); ok {
153
+ embeddedExpr = star.X
154
+ }
155
+ c.tsw.WriteLiterally(" extends ")
156
+ c.WriteTypeExpr(embeddedExpr) // Embedded type name
157
+ }
43
158
  c.tsw.WriteLine(" {")
44
159
  c.tsw.Indent(1)
45
160
 
@@ -79,8 +194,51 @@ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
79
194
 
80
195
  // constructor and clone using Object.assign for compactness
81
196
  c.tsw.WriteLine("")
82
- c.tsw.WriteLinef("constructor(init?: Partial<%s>) { if (init) Object.assign(this, init as any); }", className)
197
+ if embeddedExpr != nil {
198
+ // Determine the embedded type's identifier (strip any package prefix)
199
+ embeddedTypeNameFull := c.getTypeExprName(embeddedExpr)
200
+ propName := embeddedTypeNameFull
201
+ if idx := strings.LastIndex(propName, "."); idx != -1 {
202
+ propName = propName[idx+1:] // keep only the last segment for the property key
203
+ }
204
+
205
+ // The init parameter allows either flattened fields or a nested {Embedded: {...}} object
206
+ c.tsw.WriteLinef(
207
+ "constructor(init?: Partial<%s> & { %s?: Partial<%s> }) {",
208
+ className, propName, propName,
209
+ )
210
+ c.tsw.Indent(1)
211
+
212
+ // Pass either the nested embedded object or the full init directly to super()
213
+ c.tsw.WriteLinef("super(init?.%s || init);", propName)
214
+
215
+ // Copy fields that belong only to this derived class
216
+ c.tsw.WriteLine("if (init) {")
217
+ c.tsw.Indent(1)
218
+ c.tsw.WriteLinef("const { %s, ...rest } = init as any;", propName)
219
+ c.tsw.WriteLine("Object.assign(this, rest);")
220
+ c.tsw.Indent(-1)
221
+ c.tsw.WriteLine("}")
222
+
223
+ c.tsw.Indent(-1)
224
+ c.tsw.WriteLine("}")
225
+ } else {
226
+ // For a base class without embedding, use simple constructor
227
+ c.tsw.WriteLinef("constructor(init?: Partial<%s>) { if (init) Object.assign(this, init as any); }", className)
228
+ }
83
229
  c.tsw.WriteLinef("public clone(): %s { return Object.assign(Object.create(%s.prototype) as %s, this); }", className, className, className)
230
+
231
+ // Add code to register the type with the runtime system
232
+ c.tsw.WriteLine("")
233
+ c.tsw.WriteLinef("// Register this type with the runtime type system")
234
+ c.tsw.WriteLinef("static __typeInfo = goscript.registerType(")
235
+ c.tsw.WriteLinef(" '%s',", className)
236
+ c.tsw.WriteLinef(" goscript.TypeKind.Struct,")
237
+ c.tsw.WriteLinef(" new %s(),", className)
238
+ c.tsw.WriteLinef(" new Set([%s]),", c.collectMethodNames(className))
239
+ c.tsw.WriteLinef(" %s", className)
240
+ c.tsw.WriteLinef(");")
241
+
84
242
  c.tsw.Indent(-1)
85
243
  c.tsw.WriteLine("}")
86
244
  case *ast.InterfaceType:
@@ -88,8 +246,20 @@ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
88
246
  if err := c.WriteValueExpr(a.Name); err != nil { // Interface name is a value identifier
89
247
  return err
90
248
  }
91
- c.tsw.WriteLine(" ")
92
- c.WriteTypeExpr(a.Type) // The interface definition itself is a type
249
+ c.tsw.WriteLiterally(" ") // Changed from WriteLine to WriteLiterally
250
+ c.WriteTypeExpr(a.Type) // The interface definition itself is a type
251
+
252
+ // Add code to register the interface with the runtime system
253
+ interfaceName := a.Name.Name
254
+ c.tsw.WriteLine("")
255
+ c.tsw.WriteLinef("// Register this interface with the runtime type system")
256
+ c.tsw.WriteLinef("const %s__typeInfo = goscript.registerType(", interfaceName)
257
+ c.tsw.WriteLinef(" '%s',", interfaceName)
258
+ c.tsw.WriteLinef(" goscript.TypeKind.Interface,")
259
+ c.tsw.WriteLinef(" null,") // Zero value for interface is null
260
+ c.tsw.WriteLinef(" new Set([%s]),", c.collectInterfaceMethods(t))
261
+ c.tsw.WriteLinef(" undefined")
262
+ c.tsw.WriteLinef(");")
93
263
  default:
94
264
  // type alias
95
265
  c.tsw.WriteLiterally("type ")
@@ -110,8 +280,17 @@ func (c *GoToTSCompiler) WriteFuncDeclAsMethod(decl *ast.FuncDecl) error {
110
280
  c.WriteDoc(decl.Doc)
111
281
  }
112
282
 
283
+ // Determine if method is async by checking for async operations in the body
284
+ isAsync := c.containsAsyncOperations(decl.Body)
285
+
113
286
  // Methods are typically public in the TS output
114
287
  c.tsw.WriteLiterally("public ")
288
+
289
+ // Add async modifier if needed
290
+ if isAsync {
291
+ c.tsw.WriteLiterally("async ")
292
+ }
293
+
115
294
  // Keep original Go casing for method names
116
295
  if err := c.WriteValueExpr(decl.Name); err != nil { // Method name is a value identifier
117
296
  return err
@@ -129,6 +308,9 @@ func (c *GoToTSCompiler) WriteFuncDeclAsMethod(decl *ast.FuncDecl) error {
129
308
  // Handle return type
130
309
  if funcType.Results != nil && len(funcType.Results.List) > 0 {
131
310
  c.tsw.WriteLiterally(": ")
311
+ if isAsync {
312
+ c.tsw.WriteLiterally("Promise<")
313
+ }
132
314
  if len(funcType.Results.List) == 1 {
133
315
  // Single return value
134
316
  resultType := funcType.Results.List[0].Type
@@ -144,12 +326,27 @@ func (c *GoToTSCompiler) WriteFuncDeclAsMethod(decl *ast.FuncDecl) error {
144
326
  }
145
327
  c.tsw.WriteLiterally("]")
146
328
  }
329
+ if isAsync {
330
+ c.tsw.WriteLiterally(">")
331
+ }
147
332
  } else {
148
333
  // No return value -> void
149
- c.tsw.WriteLiterally(": void")
334
+ if isAsync {
335
+ c.tsw.WriteLiterally(": Promise<void>")
336
+ } else {
337
+ c.tsw.WriteLiterally(": void")
338
+ }
150
339
  }
151
340
 
152
341
  c.tsw.WriteLiterally(" ")
342
+
343
+ // Check if function body has defer statements
344
+ c.nextBlockNeedsDefer = c.scanForDefer(decl.Body)
345
+
346
+ // Save previous async state and set current state based on isAsync
347
+ previousAsyncState := c.inAsyncFunction
348
+ c.inAsyncFunction = isAsync
349
+
153
350
  // Bind receiver name to this
154
351
  if recvField := decl.Recv.List[0]; len(recvField.Names) > 0 {
155
352
  recvName := recvField.Names[0].Name
@@ -157,21 +354,40 @@ func (c *GoToTSCompiler) WriteFuncDeclAsMethod(decl *ast.FuncDecl) error {
157
354
  c.tsw.WriteLine("{")
158
355
  c.tsw.Indent(1)
159
356
  c.tsw.WriteLinef("const %s = this", recvName)
357
+
358
+ // Add using statement if needed
359
+ if c.nextBlockNeedsDefer {
360
+ if c.inAsyncFunction {
361
+ c.tsw.WriteLine("await using __defer = new goscript.AsyncDisposableStack();")
362
+ } else {
363
+ c.tsw.WriteLine("using cleanup = new goscript.DisposableStack();")
364
+ }
365
+ c.nextBlockNeedsDefer = false
366
+ }
367
+
160
368
  // write method body without outer braces
161
369
  for _, stmt := range decl.Body.List {
162
370
  if err := c.WriteStmt(stmt); err != nil {
371
+ c.inAsyncFunction = previousAsyncState // Restore state before returning error
163
372
  return fmt.Errorf("failed to write statement in function body: %w", err)
164
373
  }
165
374
  }
166
375
  c.tsw.Indent(-1)
167
376
  c.tsw.WriteLine("}")
377
+
378
+ // Restore previous async state
379
+ c.inAsyncFunction = previousAsyncState
168
380
  return nil
169
381
  }
170
382
  }
171
383
  // no named receiver, write whole body
172
384
  if err := c.WriteStmt(decl.Body); err != nil {
385
+ c.inAsyncFunction = previousAsyncState // Restore state before returning error
173
386
  return fmt.Errorf("failed to write function body: %w", err)
174
387
  }
388
+
389
+ // Restore previous async state
390
+ c.inAsyncFunction = previousAsyncState
175
391
  return nil
176
392
  }
177
393
 
@@ -254,3 +470,67 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
254
470
 
255
471
  c.tsw.WriteImport(impName, importPath)
256
472
  }
473
+
474
+ // WriteInterfaceMethodSignature writes a TypeScript interface method signature from a Go ast.Field.
475
+ func (c *GoToTSCompiler) WriteInterfaceMethodSignature(field *ast.Field) error {
476
+ if len(field.Names) == 0 {
477
+ // Should not happen for named methods in an interface, but handle defensively
478
+ return fmt.Errorf("interface method field has no name")
479
+ }
480
+
481
+ methodName := field.Names[0]
482
+ funcType, ok := field.Type.(*ast.FuncType)
483
+ if !ok {
484
+ // Should not happen for valid interface methods, but handle defensively
485
+ c.tsw.WriteCommentInline("unexpected interface method type")
486
+ return fmt.Errorf("interface method type is not a FuncType")
487
+ }
488
+
489
+ // Write method name
490
+ c.WriteIdentValue(methodName)
491
+
492
+ // Write parameter list (name: type)
493
+ c.tsw.WriteLiterally("(")
494
+ if funcType.Params != nil {
495
+ for i, param := range funcType.Params.List {
496
+ if i > 0 {
497
+ c.tsw.WriteLiterally(", ")
498
+ }
499
+ // Determine parameter name
500
+ paramName := fmt.Sprintf("_p%d", i) // Default placeholder
501
+ if len(param.Names) > 0 && param.Names[0].Name != "" && param.Names[0].Name != "_" {
502
+ paramName = param.Names[0].Name
503
+ }
504
+ c.tsw.WriteLiterally(paramName)
505
+ c.tsw.WriteLiterally(": ")
506
+ c.WriteTypeExpr(param.Type)
507
+ }
508
+ }
509
+ c.tsw.WriteLiterally(")")
510
+
511
+ // Write return type
512
+ // Use WriteFuncType's logic for return types, but without the async handling
513
+ if funcType.Results != nil && len(funcType.Results.List) > 0 {
514
+ c.tsw.WriteLiterally(": ")
515
+ if len(funcType.Results.List) == 1 && len(funcType.Results.List[0].Names) == 0 {
516
+ // Single unnamed return type
517
+ c.WriteTypeExpr(funcType.Results.List[0].Type)
518
+ } else {
519
+ // Multiple or named return types -> tuple
520
+ c.tsw.WriteLiterally("[")
521
+ for i, result := range funcType.Results.List {
522
+ if i > 0 {
523
+ c.tsw.WriteLiterally(", ")
524
+ }
525
+ c.WriteTypeExpr(result.Type)
526
+ }
527
+ c.tsw.WriteLiterally("]")
528
+ }
529
+ } else {
530
+ // No return value -> void
531
+ c.tsw.WriteLiterally(": void")
532
+ }
533
+
534
+ c.tsw.WriteLine(";") // Semicolon at the end of the method signature
535
+ return nil
536
+ }
@@ -28,6 +28,10 @@ func (c *GoToTSCompiler) WriteStmt(a ast.Stmt) error {
28
28
  if err := c.WriteStmtReturn(exp); err != nil {
29
29
  return fmt.Errorf("failed to write return statement: %w", err)
30
30
  }
31
+ case *ast.DeferStmt:
32
+ if err := c.WriteDeferStmt(exp); err != nil {
33
+ return fmt.Errorf("failed to write defer statement: %w", err)
34
+ }
31
35
  case *ast.IfStmt:
32
36
  if err := c.WriteStmtIf(exp); err != nil {
33
37
  return fmt.Errorf("failed to write if statement: %w", err)
@@ -125,12 +129,70 @@ func (c *GoToTSCompiler) WriteStmt(a ast.Stmt) error {
125
129
  if err := c.WriteStmtSelect(exp); err != nil {
126
130
  return fmt.Errorf("failed to write select statement: %w", err)
127
131
  }
132
+ case *ast.BranchStmt:
133
+ // Handle break and continue statements
134
+ switch exp.Tok {
135
+ case token.BREAK:
136
+ c.tsw.WriteLine("break") // No semicolon needed
137
+ case token.CONTINUE:
138
+ c.tsw.WriteLine("continue") // No semicolon needed
139
+ default:
140
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled branch statement token: %s", exp.Tok.String()))
141
+ }
128
142
  default:
129
143
  c.tsw.WriteCommentLine(fmt.Sprintf("unknown statement: %s\n", litter.Sdump(a)))
130
144
  }
131
145
  return nil
132
146
  }
133
147
 
148
+ // WriteDeferStmt writes a defer statement using TypeScript's DisposableStack.
149
+ func (c *GoToTSCompiler) WriteDeferStmt(exp *ast.DeferStmt) error {
150
+ // Check if deferred call contains async operations
151
+ hasAsyncOps := c.containsAsyncOperations(exp.Call)
152
+
153
+ // Choose the right stack variable and async modifier
154
+ stackVar := "cleanup"
155
+ asyncPrefix := ""
156
+
157
+ if c.inAsyncFunction {
158
+ stackVar = "__defer"
159
+ if hasAsyncOps {
160
+ asyncPrefix = "async "
161
+ }
162
+ }
163
+
164
+ c.tsw.WriteLiterally(fmt.Sprintf("%s.defer(%s() => {", stackVar, asyncPrefix))
165
+ c.tsw.Indent(1)
166
+ c.tsw.WriteLine("")
167
+
168
+ // Write the deferred call or inline the body when it's an immediately-invoked
169
+ // function literal (defer func(){ ... }()).
170
+ if funcLit, ok := exp.Call.Fun.(*ast.FuncLit); ok && len(exp.Call.Args) == 0 {
171
+ // Inline the function literal's body to avoid nested arrow invocation.
172
+ prevAsyncState := c.inAsyncFunction
173
+ c.inAsyncFunction = hasAsyncOps
174
+
175
+ for _, stmt := range funcLit.Body.List {
176
+ if err := c.WriteStmt(stmt); err != nil {
177
+ c.inAsyncFunction = prevAsyncState
178
+ return fmt.Errorf("failed to write statement in deferred function body: %w", err)
179
+ }
180
+ }
181
+
182
+ c.inAsyncFunction = prevAsyncState
183
+ } else {
184
+ // Fallback: write the call expression as-is.
185
+ if err := c.WriteValueExpr(exp.Call); err != nil {
186
+ return fmt.Errorf("failed to write deferred call: %w", err)
187
+ }
188
+ }
189
+
190
+ c.tsw.Indent(-1)
191
+ c.tsw.WriteLine("});")
192
+
193
+ return nil
194
+ }
195
+
134
196
  // WriteStmtSelect writes a select statement.
135
197
  func (c *GoToTSCompiler) WriteStmtSelect(exp *ast.SelectStmt) error {
136
198
  // This is our implementation of the select statement, which will use Promise.race
@@ -435,10 +497,12 @@ func (s *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
435
497
  s.tsw.WriteLiterally(" else ")
436
498
  switch elseStmt := exp.Else.(type) {
437
499
  case *ast.BlockStmt:
500
+ // Always pass false for suppressNewline here
438
501
  if err := s.WriteStmtBlock(elseStmt, false); err != nil {
439
502
  return fmt.Errorf("failed to write else block statement in if statement: %w", err)
440
503
  }
441
504
  case *ast.IfStmt:
505
+ // Recursive call handles its own block formatting
442
506
  if err := s.WriteStmtIf(elseStmt); err != nil {
443
507
  return fmt.Errorf("failed to write else if statement in if statement: %w", err)
444
508
  }
@@ -482,6 +546,16 @@ func (c *GoToTSCompiler) WriteStmtBlock(exp *ast.BlockStmt, suppressNewline bool
482
546
  c.tsw.WriteLine("{")
483
547
  c.tsw.Indent(1)
484
548
 
549
+ // Add "using" statement if needed
550
+ if c.nextBlockNeedsDefer {
551
+ if c.inAsyncFunction {
552
+ c.tsw.WriteLine("await using __defer = new goscript.AsyncDisposableStack();")
553
+ } else {
554
+ c.tsw.WriteLine("using cleanup = new goscript.DisposableStack();")
555
+ }
556
+ c.nextBlockNeedsDefer = false
557
+ }
558
+
485
559
  // Prepare line info
486
560
  var file *token.File
487
561
  if c.pkg != nil && c.pkg.Fset != nil && exp.Lbrace.IsValid() {
@@ -775,32 +849,6 @@ func (c *GoToTSCompiler) writeChannelReceiveWithOk(lhs []ast.Expr, unaryExpr *as
775
849
 
776
850
  func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
777
851
  // writeTypeAssertion handles multi-variable assignment from a type assertion.
778
- writeTypeAssertion := func(typeAssertExpr *ast.TypeAssertExpr) error {
779
- interfaceExpr := typeAssertExpr.X
780
- assertedType := typeAssertExpr.Type
781
-
782
- // Write the TypeScript code for the type assertion.
783
- c.tsw.WriteLiterally("let ok: boolean = (")
784
- if err := c.WriteValueExpr(interfaceExpr); err != nil {
785
- return fmt.Errorf("failed to write interface expression in type assertion (ok check): %w", err)
786
- }
787
- c.tsw.WriteLiterally(" as any) satisfies ")
788
- c.WriteTypeExpr(assertedType)
789
- c.tsw.WriteLine("")
790
-
791
- c.tsw.WriteLiterally("let assertedValue: ")
792
- c.WriteTypeExpr(assertedType)
793
- c.tsw.WriteLiterally(" | null = ok ? (")
794
- if err := c.WriteValueExpr(interfaceExpr); err != nil {
795
- return fmt.Errorf("failed to write interface expression in type assertion (value assignment): %w", err)
796
- }
797
- c.tsw.WriteLiterally(" as ")
798
- c.WriteTypeExpr(assertedType)
799
- c.tsw.WriteLiterally(") : null")
800
- c.tsw.WriteLine("")
801
-
802
- return nil
803
- }
804
852
 
805
853
  // writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
806
854
  writeMultiVarAssignFromCall := func(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
@@ -982,7 +1030,7 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
982
1030
  if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
983
1031
  rhsExpr := exp.Rhs[0]
984
1032
  if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
985
- return writeTypeAssertion(typeAssertExpr)
1033
+ return c.writeTypeAssertion(exp.Lhs, typeAssertExpr, exp.Tok)
986
1034
  } else if indexExpr, ok := rhsExpr.(*ast.IndexExpr); ok {
987
1035
  // Check if this is a map lookup (comma-ok idiom)
988
1036
  if len(exp.Lhs) == 2 {
@@ -1458,41 +1506,104 @@ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
1458
1506
  return fmt.Errorf("unsupported range loop type: %T", underlying)
1459
1507
  }
1460
1508
 
1509
+ // WriteStmtSend writes a channel send statement (ch <- value).
1510
+ func (c *GoToTSCompiler) WriteStmtSend(exp *ast.SendStmt) error {
1511
+ // Translate ch <- value to await ch.send(value)
1512
+ c.tsw.WriteLiterally("await ")
1513
+ if err := c.WriteValueExpr(exp.Chan); err != nil { // The channel expression
1514
+ return fmt.Errorf("failed to write channel expression in send statement: %w", err)
1515
+ }
1516
+ c.tsw.WriteLiterally(".send(")
1517
+ if err := c.WriteValueExpr(exp.Value); err != nil { // The value expression
1518
+ return fmt.Errorf("failed to write value expression in send statement: %w", err)
1519
+ }
1520
+ c.tsw.WriteLiterally(")")
1521
+ c.tsw.WriteLine("") // Add newline after the statement
1522
+ return nil
1523
+ }
1524
+
1461
1525
  // isSlice returns true if the underlying type is a slice.
1462
1526
  func isSlice(typ gtypes.Type) bool {
1463
1527
  _, ok := typ.(*gtypes.Slice)
1464
1528
  return ok
1465
1529
  }
1466
1530
 
1467
- // isLHSMapIndex returns true if the expression is an index expression on a map type.
1468
- func isLHSMapIndex(expr ast.Expr, pkg *packages.Package) bool {
1469
- if pkg == nil || pkg.TypesInfo == nil {
1470
- return false
1531
+ // writeTypeAssertion handles multi-variable assignment from a type assertion.
1532
+ func (c *GoToTSCompiler) writeTypeAssertion(lhs []ast.Expr, typeAssertExpr *ast.TypeAssertExpr, tok token.Token) error {
1533
+ interfaceExpr := typeAssertExpr.X
1534
+ assertedType := typeAssertExpr.Type
1535
+
1536
+ // Ensure LHS has exactly two expressions (value and ok)
1537
+ if len(lhs) != 2 {
1538
+ return fmt.Errorf("type assertion assignment requires exactly 2 variables on LHS, got %d", len(lhs))
1471
1539
  }
1472
- indexExpr, ok := expr.(*ast.IndexExpr)
1473
- if !ok {
1474
- return false
1540
+
1541
+ // Get variable names, handling blank identifiers
1542
+ valueIsBlank := false
1543
+ okIsBlank := false
1544
+ var valueName string
1545
+ var okName string
1546
+
1547
+ if valIdent, ok := lhs[0].(*ast.Ident); ok {
1548
+ if valIdent.Name == "_" {
1549
+ valueIsBlank = true
1550
+ } else {
1551
+ valueName = valIdent.Name
1552
+ }
1553
+ } else {
1554
+ return fmt.Errorf("unhandled LHS expression type for value in type assertion: %T", lhs[0])
1475
1555
  }
1476
- tv, ok := pkg.TypesInfo.Types[indexExpr.X]
1477
- if !ok || tv.Type == nil {
1478
- return false
1556
+
1557
+ if okIdent, ok := lhs[1].(*ast.Ident); ok {
1558
+ if okIdent.Name == "_" {
1559
+ okIsBlank = true
1560
+ } else {
1561
+ okName = okIdent.Name
1562
+ }
1563
+ } else {
1564
+ return fmt.Errorf("unhandled LHS expression type for ok in type assertion: %T", lhs[1])
1479
1565
  }
1480
- _, isMap := tv.Type.Underlying().(*gtypes.Map)
1481
- return isMap
1482
- }
1483
1566
 
1484
- // WriteStmtSend writes a channel send statement (ch <- value).
1485
- func (c *GoToTSCompiler) WriteStmtSend(exp *ast.SendStmt) error {
1486
- // Translate ch <- value to await ch.send(value)
1487
- c.tsw.WriteLiterally("await ")
1488
- if err := c.WriteValueExpr(exp.Chan); err != nil { // The channel expression
1489
- return fmt.Errorf("failed to write channel expression in send statement: %w", err)
1567
+ // Get the type name string for the asserted type
1568
+ typeName := c.getTypeNameString(assertedType)
1569
+
1570
+ // Generate the destructuring assignment
1571
+ if tok == token.DEFINE {
1572
+ c.tsw.WriteLiterally("let ")
1573
+ } else {
1574
+ // We must wrap in parenthesis.
1575
+ c.tsw.WriteLiterally("(")
1490
1576
  }
1491
- c.tsw.WriteLiterally(".send(")
1492
- if err := c.WriteValueExpr(exp.Value); err != nil { // The value expression
1493
- return fmt.Errorf("failed to write value expression in send statement: %w", err)
1577
+
1578
+ c.tsw.WriteLiterally("{ ")
1579
+ // Dynamically build the destructuring pattern
1580
+ parts := []string{}
1581
+ if !valueIsBlank {
1582
+ parts = append(parts, fmt.Sprintf("value: %s", valueName))
1583
+ }
1584
+ if !okIsBlank {
1585
+ parts = append(parts, fmt.Sprintf("ok: %s", okName))
1586
+ }
1587
+ c.tsw.WriteLiterally(strings.Join(parts, ", "))
1588
+ c.tsw.WriteLiterally(" } = goscript.typeAssert<")
1589
+
1590
+ // Write the asserted type for the generic
1591
+ c.WriteTypeExpr(assertedType)
1592
+ c.tsw.WriteLiterally(">(")
1593
+
1594
+ // Write the interface expression
1595
+ if err := c.WriteValueExpr(interfaceExpr); err != nil {
1596
+ return fmt.Errorf("failed to write interface expression in type assertion call: %w", err)
1494
1597
  }
1598
+ c.tsw.WriteLiterally(", ")
1599
+ c.tsw.WriteLiterally(fmt.Sprintf("'%s'", typeName))
1495
1600
  c.tsw.WriteLiterally(")")
1601
+
1602
+ if tok != token.DEFINE {
1603
+ c.tsw.WriteLiterally(")")
1604
+ }
1605
+
1496
1606
  c.tsw.WriteLine("") // Add newline after the statement
1607
+
1497
1608
  return nil
1498
1609
  }
@@ -54,7 +54,7 @@ func (w *TSCodeWriter) Indent(count int) {
54
54
 
55
55
  // WriteImport writes a TypeScript import.
56
56
  func (w *TSCodeWriter) WriteImport(symbolName, importPath string) {
57
- w.WriteLinef("import * as %s from %q;", symbolName, importPath)
57
+ w.WriteLinef("import * as %s from %q", symbolName, importPath)
58
58
  }
59
59
 
60
60
  // WriteCommentLine writes a comment as a // line.
@@ -78,7 +78,7 @@ func (w *TSCodeWriter) WriteLiterally(literal string) {
78
78
  if w.lineWritten {
79
79
  w.WriteLinePreamble()
80
80
  }
81
- w.w.Write([]byte(literal))
81
+ w.w.Write([]byte(literal)) //nolint:errcheck
82
82
  }
83
83
 
84
84
  // WriteSectionTail writes the end of a section.