goscript 0.0.2

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 (160) hide show
  1. package/.aider-prompt +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +427 -0
  4. package/builtin/builtin.ts +507 -0
  5. package/cmd/goscript/cmd_compile.go +59 -0
  6. package/cmd/goscript/main.go +23 -0
  7. package/compiler/compile.go +183 -0
  8. package/compiler/compile_comment.go +41 -0
  9. package/compiler/compile_decls.go +72 -0
  10. package/compiler/compile_expr.go +831 -0
  11. package/compiler/compile_field.go +89 -0
  12. package/compiler/compile_spec.go +256 -0
  13. package/compiler/compile_stmt.go +1509 -0
  14. package/compiler/compiler.go +81 -0
  15. package/compiler/config.go +32 -0
  16. package/compiler/context.go +9 -0
  17. package/compiler/file_compiler.go +80 -0
  18. package/compiler/output_path.go +31 -0
  19. package/compiler/pkg_compiler.go +73 -0
  20. package/compiler/writer.go +90 -0
  21. package/compliance/COMPLIANCE.md +133 -0
  22. package/compliance/compliance.go +313 -0
  23. package/compliance/compliance_test.go +57 -0
  24. package/compliance/tests/array_literal/array_literal.go +15 -0
  25. package/compliance/tests/array_literal/array_literal.gs.ts +19 -0
  26. package/compliance/tests/array_literal/expected.log +3 -0
  27. package/compliance/tests/async_basic/async_basic.go +26 -0
  28. package/compliance/tests/async_basic/async_basic.gs.ts +30 -0
  29. package/compliance/tests/async_basic/expected.log +1 -0
  30. package/compliance/tests/basic_arithmetic/basic_arithmetic.go +15 -0
  31. package/compliance/tests/basic_arithmetic/basic_arithmetic.gs.ts +19 -0
  32. package/compliance/tests/basic_arithmetic/expected.log +5 -0
  33. package/compliance/tests/boolean_logic/boolean_logic.go +13 -0
  34. package/compliance/tests/boolean_logic/boolean_logic.gs.ts +17 -0
  35. package/compliance/tests/boolean_logic/expected.log +3 -0
  36. package/compliance/tests/channel_basic/channel_basic.go +12 -0
  37. package/compliance/tests/channel_basic/channel_basic.gs.ts +18 -0
  38. package/compliance/tests/channel_basic/expected.log +1 -0
  39. package/compliance/tests/composite_literal_assignment/composite_literal_assignment.go +20 -0
  40. package/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.ts +27 -0
  41. package/compliance/tests/composite_literal_assignment/expected.log +2 -0
  42. package/compliance/tests/constants/constants.go +18 -0
  43. package/compliance/tests/constants/constants.gs.ts +22 -0
  44. package/compliance/tests/constants/expected.log +3 -0
  45. package/compliance/tests/copy_independence/copy_independence.go +29 -0
  46. package/compliance/tests/copy_independence/copy_independence.gs.ts +36 -0
  47. package/compliance/tests/copy_independence/expected.log +4 -0
  48. package/compliance/tests/float64/expected.log +6 -0
  49. package/compliance/tests/float64/float64.go +28 -0
  50. package/compliance/tests/float64/float64.gs.ts +32 -0
  51. package/compliance/tests/for_loop_basic/expected.log +5 -0
  52. package/compliance/tests/for_loop_basic/for_loop_basic.go +9 -0
  53. package/compliance/tests/for_loop_basic/for_loop_basic.gs.ts +13 -0
  54. package/compliance/tests/for_loop_condition_only/expected.log +5 -0
  55. package/compliance/tests/for_loop_condition_only/main.go +9 -0
  56. package/compliance/tests/for_loop_condition_only/main.gs.ts +13 -0
  57. package/compliance/tests/for_range/expected.log +9 -0
  58. package/compliance/tests/for_range/for_range.go +26 -0
  59. package/compliance/tests/for_range/for_range.gs.ts +45 -0
  60. package/compliance/tests/for_range_index_use/expected.log +6 -0
  61. package/compliance/tests/for_range_index_use/for_range_index_use.go +11 -0
  62. package/compliance/tests/for_range_index_use/for_range_index_use.gs.ts +18 -0
  63. package/compliance/tests/func_literal/expected.log +1 -0
  64. package/compliance/tests/func_literal/func_literal.go +10 -0
  65. package/compliance/tests/func_literal/func_literal.gs.ts +15 -0
  66. package/compliance/tests/function_call_result_assignment/expected.log +2 -0
  67. package/compliance/tests/function_call_result_assignment/function_call_result_assignment.go +24 -0
  68. package/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.ts +31 -0
  69. package/compliance/tests/if_statement/expected.log +1 -0
  70. package/compliance/tests/if_statement/if_statement.go +11 -0
  71. package/compliance/tests/if_statement/if_statement.gs.ts +15 -0
  72. package/compliance/tests/interface_to_interface_type_assertion/expected.log +1 -0
  73. package/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.go +30 -0
  74. package/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.ts +41 -0
  75. package/compliance/tests/interface_type_assertion/expected.log +1 -0
  76. package/compliance/tests/interface_type_assertion/interface_type_assertion.go +26 -0
  77. package/compliance/tests/interface_type_assertion/interface_type_assertion.gs.ts +36 -0
  78. package/compliance/tests/map_support/expected.log +13 -0
  79. package/compliance/tests/map_support/map_support.go +89 -0
  80. package/compliance/tests/map_support/map_support.gs.ts +102 -0
  81. package/compliance/tests/method_call_on_pointer_receiver/expected.log +1 -0
  82. package/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.go +19 -0
  83. package/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.ts +27 -0
  84. package/compliance/tests/method_call_on_pointer_via_value/expected.log +1 -0
  85. package/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.go +29 -0
  86. package/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.ts +38 -0
  87. package/compliance/tests/method_call_on_value_receiver/expected.log +1 -0
  88. package/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.go +16 -0
  89. package/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.ts +24 -0
  90. package/compliance/tests/method_call_on_value_via_pointer/expected.log +2 -0
  91. package/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.go +30 -0
  92. package/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.ts +38 -0
  93. package/compliance/tests/multiple_return_values/expected.log +6 -0
  94. package/compliance/tests/multiple_return_values/multiple_return_values.go +19 -0
  95. package/compliance/tests/multiple_return_values/multiple_return_values.gs.ts +23 -0
  96. package/compliance/tests/pointer_assignment_no_copy/expected.log +2 -0
  97. package/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.go +28 -0
  98. package/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.ts +35 -0
  99. package/compliance/tests/pointer_composite_literal_assignment/expected.log +3 -0
  100. package/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.go +23 -0
  101. package/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.ts +30 -0
  102. package/compliance/tests/pointer_deref_multiassign/expected.log +0 -0
  103. package/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.go +17 -0
  104. package/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.ts +27 -0
  105. package/compliance/tests/pointer_initialization/expected.log +1 -0
  106. package/compliance/tests/pointer_initialization/pointer_initialization.go +16 -0
  107. package/compliance/tests/pointer_initialization/pointer_initialization.gs.ts +22 -0
  108. package/compliance/tests/select_receive_on_closed_channel_no_default/expected.log +1 -0
  109. package/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.go +15 -0
  110. package/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.ts +31 -0
  111. package/compliance/tests/select_send_on_full_buffered_channel_with_default/expected.log +1 -0
  112. package/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.go +13 -0
  113. package/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.gs.ts +35 -0
  114. package/compliance/tests/select_statement/expected.log +9 -0
  115. package/compliance/tests/select_statement/select_statement.go +109 -0
  116. package/compliance/tests/select_statement/select_statement.gs.ts +239 -0
  117. package/compliance/tests/simple/expected.log +1 -0
  118. package/compliance/tests/simple/simple.go +5 -0
  119. package/compliance/tests/simple/simple.gs.ts +9 -0
  120. package/compliance/tests/simple_deref_assignment/expected.log +2 -0
  121. package/compliance/tests/simple_deref_assignment/simple_deref_assignment.go +19 -0
  122. package/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.ts +26 -0
  123. package/compliance/tests/slices/expected.log +7 -0
  124. package/compliance/tests/slices/slices.go +22 -0
  125. package/compliance/tests/slices/slices.gs.ts +26 -0
  126. package/compliance/tests/string_rune_conversion/expected.log +3 -0
  127. package/compliance/tests/string_rune_conversion/string_rune_conversion.go +16 -0
  128. package/compliance/tests/string_rune_conversion/string_rune_conversion.gs.ts +22 -0
  129. package/compliance/tests/struct_field_access/expected.log +2 -0
  130. package/compliance/tests/struct_field_access/struct_field_access.go +13 -0
  131. package/compliance/tests/struct_field_access/struct_field_access.gs.ts +20 -0
  132. package/compliance/tests/struct_value_init_clone/expected.log +5 -0
  133. package/compliance/tests/struct_value_init_clone/struct_value_init_clone.go +28 -0
  134. package/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.ts +35 -0
  135. package/compliance/tests/switch_statement/expected.log +14 -0
  136. package/compliance/tests/switch_statement/switch_statement.go +59 -0
  137. package/compliance/tests/switch_statement/switch_statement.gs.ts +85 -0
  138. package/compliance/tests/value_type_copy_behavior/expected.log +3 -0
  139. package/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.go +25 -0
  140. package/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.ts +34 -0
  141. package/design/DESIGN.md +599 -0
  142. package/example/simple/build.bash +10 -0
  143. package/example/simple/go.mod +23 -0
  144. package/example/simple/go.sum +39 -0
  145. package/example/simple/main.go +138 -0
  146. package/example/simple/main.gs.ts +133 -0
  147. package/example/simple/main.ts +3 -0
  148. package/example/simple/main_test.go +59 -0
  149. package/example/simple/main_tools.go +5 -0
  150. package/example/simple/package.json +7 -0
  151. package/example/simple/run.bash +6 -0
  152. package/example/simple/tsconfig.json +28 -0
  153. package/example/simple/yarn.lock +8 -0
  154. package/go.mod +22 -0
  155. package/go.sum +39 -0
  156. package/output/output.go +10 -0
  157. package/package.json +14 -0
  158. package/tsconfig.json +10 -0
  159. package/types/tokens.go +65 -0
  160. package/types/types.go +46 -0
@@ -0,0 +1,1509 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/token"
7
+ gtypes "go/types"
8
+ "strings"
9
+
10
+ gstypes "github.com/paralin/goscript/types"
11
+ "github.com/sanity-io/litter"
12
+ "golang.org/x/tools/go/packages"
13
+ )
14
+
15
+ // WriteStmt writes a statement to the output.
16
+ func (c *GoToTSCompiler) WriteStmt(a ast.Stmt) error {
17
+ switch exp := a.(type) {
18
+ case *ast.BlockStmt:
19
+ // WriteStmtBlock does not currently return an error, assuming it's safe for now.
20
+ if err := c.WriteStmtBlock(exp, false); err != nil {
21
+ return fmt.Errorf("failed to write block statement: %w", err)
22
+ }
23
+ case *ast.AssignStmt:
24
+ if err := c.WriteStmtAssign(exp); err != nil {
25
+ return fmt.Errorf("failed to write assignment statement: %w", err)
26
+ }
27
+ case *ast.ReturnStmt:
28
+ if err := c.WriteStmtReturn(exp); err != nil {
29
+ return fmt.Errorf("failed to write return statement: %w", err)
30
+ }
31
+ case *ast.IfStmt:
32
+ if err := c.WriteStmtIf(exp); err != nil {
33
+ return fmt.Errorf("failed to write if statement: %w", err)
34
+ }
35
+ case *ast.ExprStmt:
36
+ if err := c.WriteStmtExpr(exp); err != nil {
37
+ return fmt.Errorf("failed to write expression statement: %w", err)
38
+ }
39
+ case *ast.DeclStmt:
40
+ // Handle declarations within a statement list (e.g., short variable declarations)
41
+ // This typically contains a GenDecl
42
+ if genDecl, ok := exp.Decl.(*ast.GenDecl); ok {
43
+ for _, spec := range genDecl.Specs {
44
+ // Value specs within a declaration statement
45
+ if valueSpec, ok := spec.(*ast.ValueSpec); ok {
46
+ // WriteValueSpec does not currently return an error, assuming it's safe for now.
47
+ if err := c.WriteValueSpec(valueSpec); err != nil {
48
+ return fmt.Errorf("failed to write value spec in declaration statement: %w", err)
49
+ }
50
+ } else {
51
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled spec in DeclStmt: %T", spec))
52
+ }
53
+ }
54
+ } else {
55
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled declaration type in DeclStmt: %T", exp.Decl))
56
+ }
57
+ case *ast.ForStmt:
58
+ // WriteStmtFor does not currently return an error, assuming it's safe for now.
59
+ if err := c.WriteStmtFor(exp); err != nil {
60
+ return fmt.Errorf("failed to write for statement: %w", err)
61
+ }
62
+ case *ast.RangeStmt:
63
+ // Generate TS for for…range loops, log if something goes wrong
64
+ if err := c.WriteStmtRange(exp); err != nil {
65
+ return fmt.Errorf("failed to write range statement: %w", err)
66
+ }
67
+ case *ast.SwitchStmt:
68
+ // WriteStmtSwitch does not currently return an error, assuming it's safe for now.
69
+ if err := c.WriteStmtSwitch(exp); err != nil {
70
+ return fmt.Errorf("failed to write switch statement: %w", err)
71
+ }
72
+ case *ast.IncDecStmt:
73
+ // Handle increment/decrement (e.g., i++ or i--)
74
+ if err := c.WriteValueExpr(exp.X); err != nil { // The expression (e.g., i)
75
+ return fmt.Errorf("failed to write increment/decrement expression: %w", err)
76
+ }
77
+ tokStr, ok := gstypes.TokenToTs(exp.Tok)
78
+ if !ok {
79
+ c.tsw.WriteCommentLine(fmt.Sprintf("unknown incdec token: %s", exp.Tok.String()))
80
+ } else {
81
+ c.tsw.WriteLiterally(tokStr) // The token (e.g., ++)
82
+ }
83
+ c.tsw.WriteLine("")
84
+ case *ast.SendStmt:
85
+ if err := c.WriteStmtSend(exp); err != nil {
86
+ return fmt.Errorf("failed to write send statement: %w", err)
87
+ }
88
+ case *ast.GoStmt:
89
+ // Handle goroutine statement
90
+ // Translate 'go func() { ... }()' to 'queueMicrotask(() => { ... compiled body ... })'
91
+
92
+ // The call expression's function is the function literal
93
+ callExpr := exp.Call
94
+ if funcLit, ok := callExpr.Fun.(*ast.FuncLit); ok {
95
+ // Check if the function literal body contains async operations
96
+ isAsync := c.containsAsyncOperations(funcLit.Body)
97
+
98
+ if isAsync {
99
+ c.tsw.WriteLiterally("queueMicrotask(async () => {")
100
+ } else {
101
+ c.tsw.WriteLiterally("queueMicrotask(() => {")
102
+ }
103
+
104
+ c.tsw.Indent(1)
105
+ c.tsw.WriteLine("")
106
+
107
+ // Compile the function literal's body directly
108
+ if err := c.WriteStmt(funcLit.Body); err != nil {
109
+ return fmt.Errorf("failed to write goroutine function literal body: %w", err)
110
+ }
111
+
112
+ c.tsw.Indent(-1)
113
+ c.tsw.WriteLine("})") // Close the queueMicrotask callback and the statement
114
+
115
+ } else {
116
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled goroutine function type: %T", callExpr.Fun))
117
+ // Fallback: try to compile the call expression directly, though it might not work
118
+ if err := c.WriteValueExpr(exp.Call); err != nil {
119
+ return fmt.Errorf("failed to write fallback goroutine call expression: %w", err)
120
+ }
121
+ c.tsw.WriteLine("") // Ensure a newline even on fallback
122
+ }
123
+ case *ast.SelectStmt:
124
+ // Handle select statement
125
+ if err := c.WriteStmtSelect(exp); err != nil {
126
+ return fmt.Errorf("failed to write select statement: %w", err)
127
+ }
128
+ default:
129
+ c.tsw.WriteCommentLine(fmt.Sprintf("unknown statement: %s\n", litter.Sdump(a)))
130
+ }
131
+ return nil
132
+ }
133
+
134
+ // WriteStmtSelect writes a select statement.
135
+ func (c *GoToTSCompiler) WriteStmtSelect(exp *ast.SelectStmt) error {
136
+ // This is our implementation of the select statement, which will use Promise.race
137
+ // to achieve the same semantics as Go's select statement.
138
+
139
+ // Create an array to hold all the select cases
140
+ tempArrayName := c.newTempVar()
141
+ c.tsw.WriteLiterally("const ")
142
+ c.tsw.WriteLiterally(tempArrayName)
143
+ c.tsw.WriteLiterally(": goscript.SelectCase<any>[] = []") // Array of SelectCase objects
144
+ c.tsw.WriteLine("")
145
+
146
+ // Variable to track whether we have a default case
147
+ hasDefault := false
148
+
149
+ // For each case clause, generate a SelectCase object and add it to the array
150
+ for i, stmt := range exp.Body.List {
151
+ if commClause, ok := stmt.(*ast.CommClause); ok {
152
+ if commClause.Comm == nil {
153
+ // This is a default case
154
+ hasDefault = true
155
+ // Add a SelectCase object for the default case with a special ID
156
+ c.tsw.WriteLiterally(tempArrayName)
157
+ c.tsw.WriteLiterally(".push({")
158
+ c.tsw.Indent(1)
159
+ c.tsw.WriteLine("")
160
+ c.tsw.WriteLiterally("id: -1,") // Special ID for default case
161
+ c.tsw.WriteLine("")
162
+ c.tsw.WriteLiterally("isSend: false,") // Default case is neither send nor receive, but needs a value
163
+ c.tsw.WriteLine("")
164
+ c.tsw.WriteLiterally("channel: null,") // No channel for default case
165
+ c.tsw.WriteLine("")
166
+ c.tsw.WriteLiterally("onSelected: async (result) => {") // Mark as async because case body might contain await
167
+ c.tsw.Indent(1)
168
+ c.tsw.WriteLine("")
169
+ // Write the case body
170
+ for _, bodyStmt := range commClause.Body {
171
+ if err := c.WriteStmt(bodyStmt); err != nil {
172
+ return fmt.Errorf("failed to write statement in select default case body (onSelected): %w", err)
173
+ }
174
+ }
175
+ c.tsw.Indent(-1)
176
+ c.tsw.WriteLine("}") // Close onSelected handler
177
+ c.tsw.Indent(-1)
178
+ c.tsw.WriteLine("}),") // Close SelectCase object and add comma
179
+ c.tsw.WriteLine("")
180
+
181
+ continue
182
+ }
183
+
184
+ // Generate a unique ID for this case
185
+ caseID := i
186
+
187
+ // Start writing the SelectCase object
188
+ c.tsw.WriteLiterally(tempArrayName)
189
+ c.tsw.WriteLiterally(".push({")
190
+ c.tsw.Indent(1)
191
+ c.tsw.WriteLine("")
192
+ c.tsw.WriteLiterally(fmt.Sprintf("id: %d,", caseID))
193
+ c.tsw.WriteLine("")
194
+
195
+ // Handle different types of comm statements
196
+ switch comm := commClause.Comm.(type) {
197
+ case *ast.AssignStmt:
198
+ // This is a receive operation with assignment: case v := <-ch: or case v, ok := <-ch:
199
+ if len(comm.Rhs) == 1 {
200
+ if unaryExpr, ok := comm.Rhs[0].(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
201
+ // It's a receive operation
202
+ c.tsw.WriteLiterally("isSend: false,")
203
+ c.tsw.WriteLine("")
204
+ c.tsw.WriteLiterally("channel: ")
205
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // The channel expression
206
+ return fmt.Errorf("failed to write channel expression in select receive case: %w", err)
207
+ }
208
+ c.tsw.WriteLiterally(",")
209
+ c.tsw.WriteLine("")
210
+ } else {
211
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled RHS in select assignment case: %T", comm.Rhs[0]))
212
+ }
213
+ } else {
214
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled RHS count in select assignment case: %d", len(comm.Rhs)))
215
+ }
216
+ case *ast.ExprStmt:
217
+ // This is a simple receive: case <-ch:
218
+ if unaryExpr, ok := comm.X.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
219
+ c.tsw.WriteLiterally("isSend: false,")
220
+ c.tsw.WriteLine("")
221
+ c.tsw.WriteLiterally("channel: ")
222
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // The channel expression
223
+ return fmt.Errorf("failed to write channel expression in select receive case: %w", err)
224
+ }
225
+ c.tsw.WriteLiterally(",")
226
+ c.tsw.WriteLine("")
227
+ } else {
228
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled expression in select case: %T", comm.X))
229
+ }
230
+ case *ast.SendStmt:
231
+ // This is a send operation: case ch <- v:
232
+ c.tsw.WriteLiterally("isSend: true,")
233
+ c.tsw.WriteLine("")
234
+ c.tsw.WriteLiterally("channel: ")
235
+ if err := c.WriteValueExpr(comm.Chan); err != nil { // The channel expression
236
+ return fmt.Errorf("failed to write channel expression in select send case: %w", err)
237
+ }
238
+ c.tsw.WriteLiterally(",")
239
+ c.tsw.WriteLine("")
240
+ c.tsw.WriteLiterally("value: ")
241
+ if err := c.WriteValueExpr(comm.Value); err != nil { // The value expression
242
+ return fmt.Errorf("failed to write value expression in select send case: %w", err)
243
+ }
244
+ c.tsw.WriteLiterally(",")
245
+ c.tsw.WriteLine("")
246
+ default:
247
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled comm statement in select case: %T", comm))
248
+ }
249
+
250
+ // Add the onSelected handler to execute the case body after the select resolves
251
+ c.tsw.WriteLiterally("onSelected: async (result) => {") // Mark as async because case body might contain await
252
+ c.tsw.Indent(1)
253
+ c.tsw.WriteLine("")
254
+
255
+ // Handle assignment for channel receives if needed (inside the onSelected handler)
256
+ if assignStmt, ok := commClause.Comm.(*ast.AssignStmt); ok {
257
+ // This is a receive operation with assignment
258
+ if len(assignStmt.Lhs) == 1 {
259
+ // Simple receive: case v := <-ch:
260
+ valIdent, ok := assignStmt.Lhs[0].(*ast.Ident)
261
+ if ok && valIdent.Name != "_" { // Check for blank identifier
262
+ c.tsw.WriteLiterally("const ")
263
+ c.WriteIdentValue(valIdent)
264
+ c.tsw.WriteLiterally(" = result.value")
265
+ c.tsw.WriteLine("")
266
+ }
267
+ } else if len(assignStmt.Lhs) == 2 {
268
+ // Receive with ok: case v, ok := <-ch:
269
+ valIdent, valOk := assignStmt.Lhs[0].(*ast.Ident)
270
+ okIdent, okOk := assignStmt.Lhs[1].(*ast.Ident)
271
+
272
+ if valOk && valIdent.Name != "_" {
273
+ c.tsw.WriteLiterally("const ")
274
+ c.WriteIdentValue(valIdent)
275
+ c.tsw.WriteLiterally(" = result.value")
276
+ c.tsw.WriteLine("")
277
+ }
278
+
279
+ if okOk && okIdent.Name != "_" {
280
+ c.tsw.WriteLiterally("const ")
281
+ c.WriteIdentValue(okIdent)
282
+ c.tsw.WriteLiterally(" = result.ok")
283
+ c.tsw.WriteLine("")
284
+ }
285
+ }
286
+ }
287
+ // Note: Simple receive (case <-ch:) and send (case ch <- v:) don't require assignment here,
288
+ // as the operation was already performed by selectReceive/selectSend and the result is in 'result'.
289
+
290
+ // Write the case body
291
+ for _, bodyStmt := range commClause.Body {
292
+ if err := c.WriteStmt(bodyStmt); err != nil {
293
+ return fmt.Errorf("failed to write statement in select case body (onSelected): %w", err)
294
+ }
295
+ }
296
+
297
+ c.tsw.Indent(-1)
298
+ c.tsw.WriteLine("}") // Close onSelected handler
299
+ c.tsw.Indent(-1)
300
+ c.tsw.WriteLine("}),") // Close SelectCase object and add comma
301
+ c.tsw.WriteLine("")
302
+
303
+ } else {
304
+ c.tsw.WriteCommentLine(fmt.Sprintf("unknown statement in select body: %T", stmt))
305
+ }
306
+ }
307
+
308
+ // Call the selectStatement helper with the array of SelectCase objects and the hasDefault flag
309
+ // The result of selectStatement is the result of the selected case, but we handle assignments
310
+ // within the onSelected handlers, so we don't need to assign the result here.
311
+ c.tsw.WriteLiterally("await goscript.selectStatement(")
312
+ c.tsw.WriteLiterally(tempArrayName)
313
+ c.tsw.WriteLiterally(", ")
314
+ c.tsw.WriteLiterally(fmt.Sprintf("%t", hasDefault))
315
+ c.tsw.WriteLiterally(")")
316
+ c.tsw.WriteLine("")
317
+
318
+ return nil
319
+ }
320
+
321
+ // WriteStmtSwitch writes a switch statement.
322
+ func (c *GoToTSCompiler) WriteStmtSwitch(exp *ast.SwitchStmt) error {
323
+ // Handle optional initialization statement
324
+ if exp.Init != nil {
325
+ c.tsw.WriteLiterally("{")
326
+ c.tsw.Indent(1)
327
+ if err := c.WriteStmt(exp.Init); err != nil {
328
+ return fmt.Errorf("failed to write switch initialization statement: %w", err)
329
+ }
330
+ defer func() {
331
+ c.tsw.Indent(-1)
332
+ c.tsw.WriteLiterally("}")
333
+ }()
334
+ }
335
+
336
+ c.tsw.WriteLiterally("switch (")
337
+ // Handle the switch tag (the expression being switched on)
338
+ if exp.Tag != nil {
339
+ if err := c.WriteValueExpr(exp.Tag); err != nil {
340
+ return fmt.Errorf("failed to write switch tag expression: %w", err)
341
+ }
342
+ } else {
343
+ c.tsw.WriteLiterally("true") // Write 'true' for switch without expression
344
+ }
345
+ c.tsw.WriteLiterally(") {")
346
+ c.tsw.WriteLine("")
347
+ c.tsw.Indent(1)
348
+
349
+ // Handle case clauses
350
+ for _, stmt := range exp.Body.List {
351
+ if caseClause, ok := stmt.(*ast.CaseClause); ok {
352
+ // WriteCaseClause does not currently return an error, assuming it's safe for now.
353
+ if err := c.WriteCaseClause(caseClause); err != nil {
354
+ return fmt.Errorf("failed to write case clause in switch statement: %w", err)
355
+ }
356
+ } else {
357
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled statement in switch body: %T", stmt))
358
+ }
359
+ }
360
+
361
+ c.tsw.Indent(-1)
362
+ c.tsw.WriteLine("}")
363
+ return nil
364
+ }
365
+
366
+ // WriteCaseClause writes a case clause within a switch statement.
367
+ func (c *GoToTSCompiler) WriteCaseClause(exp *ast.CaseClause) error {
368
+ if exp.List == nil {
369
+ // Default case
370
+ c.tsw.WriteLiterally("default:")
371
+ c.tsw.WriteLine("")
372
+ } else {
373
+ // Case with expressions
374
+ c.tsw.WriteLiterally("case ")
375
+ for i, expr := range exp.List {
376
+ if i > 0 {
377
+ c.tsw.WriteLiterally(", ") // Although Go doesn't support multiple expressions per case like this,
378
+ } // TypeScript does, so we'll write it this way for now.
379
+ if err := c.WriteValueExpr(expr); err != nil {
380
+ return fmt.Errorf("failed to write case clause expression: %w", err)
381
+ }
382
+ }
383
+ c.tsw.WriteLiterally(":")
384
+ c.tsw.WriteLine("")
385
+ }
386
+
387
+ c.tsw.Indent(1)
388
+ // Write the body of the case clause
389
+ for _, stmt := range exp.Body {
390
+ if err := c.WriteStmt(stmt); err != nil {
391
+ return fmt.Errorf("failed to write statement in case clause body: %w", err)
392
+ }
393
+ }
394
+ // Add break statement (Go's switch has implicit breaks)
395
+ c.tsw.WriteLine("break") // Remove semicolon
396
+ c.tsw.Indent(-1)
397
+ return nil
398
+ }
399
+
400
+ // Overload for backward compatibility
401
+ func (c *GoToTSCompiler) WriteStmtCompat(a ast.Stmt) error {
402
+ // This function is for backward compatibility and simply calls WriteStmt.
403
+ // It should propagate any error returned by WriteStmt.
404
+ return c.WriteStmt(a)
405
+ }
406
+
407
+ // WriteStmtIf writes an if statement.
408
+ func (s *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
409
+ if exp.Init != nil {
410
+ s.tsw.WriteLiterally("{")
411
+ s.tsw.Indent(1)
412
+
413
+ if err := s.WriteStmt(exp.Init); err != nil {
414
+ return err
415
+ }
416
+
417
+ defer func() {
418
+ s.tsw.Indent(-1)
419
+ s.tsw.WriteLiterally("}")
420
+ }()
421
+ }
422
+
423
+ s.tsw.WriteLiterally("if (")
424
+ if err := s.WriteValueExpr(exp.Cond); err != nil { // Condition is a value
425
+ return err
426
+ }
427
+ s.tsw.WriteLiterally(") ")
428
+
429
+ if exp.Body != nil {
430
+ if err := s.WriteStmtBlock(exp.Body, exp.Else != nil); err != nil {
431
+ return fmt.Errorf("failed to write if body block statement: %w", err)
432
+ }
433
+ } else {
434
+ // Handle nil body case using WriteStmtBlock with an empty block
435
+ if err := s.WriteStmtBlock(&ast.BlockStmt{}, exp.Else != nil); err != nil {
436
+ return fmt.Errorf("failed to write empty block statement in if statement: %w", err)
437
+ }
438
+ }
439
+
440
+ // handle else branch
441
+ if exp.Else != nil {
442
+ s.tsw.WriteLiterally(" else ")
443
+ switch elseStmt := exp.Else.(type) {
444
+ case *ast.BlockStmt:
445
+ if err := s.WriteStmtBlock(elseStmt, false); err != nil {
446
+ return fmt.Errorf("failed to write else block statement in if statement: %w", err)
447
+ }
448
+ case *ast.IfStmt:
449
+ if err := s.WriteStmtIf(elseStmt); err != nil {
450
+ return fmt.Errorf("failed to write else if statement in if statement: %w", err)
451
+ }
452
+ }
453
+ }
454
+ return nil
455
+ }
456
+
457
+ // WriteStmtReturn writes a return statement.
458
+ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
459
+ c.tsw.WriteLiterally("return ")
460
+ if len(exp.Results) > 1 {
461
+ c.tsw.WriteLiterally("[")
462
+ }
463
+ for i, res := range exp.Results {
464
+ if i != 0 {
465
+ c.tsw.WriteLiterally(", ")
466
+ }
467
+ if err := c.WriteValueExpr(res); err != nil { // Return results are values
468
+ return err
469
+ }
470
+ }
471
+ if len(exp.Results) > 1 {
472
+ c.tsw.WriteLiterally("]")
473
+ }
474
+ c.tsw.WriteLine("") // Remove semicolon
475
+ return nil
476
+ }
477
+
478
+ // WriteStmtBlock writes a block statement, preserving comments and blank lines.
479
+ func (c *GoToTSCompiler) WriteStmtBlock(exp *ast.BlockStmt, suppressNewline bool) error {
480
+ if exp == nil {
481
+ c.tsw.WriteLiterally("{}")
482
+ if !suppressNewline {
483
+ c.tsw.WriteLine("")
484
+ }
485
+ return nil
486
+ }
487
+
488
+ // Opening brace
489
+ c.tsw.WriteLine("{")
490
+ c.tsw.Indent(1)
491
+
492
+ // Prepare line info
493
+ var file *token.File
494
+ if c.pkg != nil && c.pkg.Fset != nil && exp.Lbrace.IsValid() {
495
+ file = c.pkg.Fset.File(exp.Lbrace)
496
+ }
497
+
498
+ // writeBlank emits a single blank line if gap > 1
499
+ writeBlank := func(prev, curr int) {
500
+ if file != nil && prev > 0 && curr > prev+1 {
501
+ c.tsw.WriteLine("")
502
+ }
503
+ }
504
+
505
+ // Track last printed line, start at opening brace
506
+ lastLine := 0
507
+ if file != nil {
508
+ lastLine = file.Line(exp.Lbrace)
509
+ }
510
+
511
+ // 1. For each statement: write its leading comments, blank space, then the stmt
512
+ for _, stmt := range exp.List {
513
+ // Get statement's end line and position for inline comment check
514
+ stmtEndLine := 0
515
+ stmtEndPos := token.NoPos
516
+ if file != nil && stmt.End().IsValid() {
517
+ stmtEndLine = file.Line(stmt.End())
518
+ stmtEndPos = stmt.End()
519
+ }
520
+
521
+ // Process leading comments for stmt
522
+ comments := c.cmap.Filter(stmt).Comments()
523
+ for _, cg := range comments {
524
+ // Check if this comment group is an inline comment for the current statement
525
+ isInlineComment := false
526
+ if file != nil && cg.Pos().IsValid() && stmtEndPos.IsValid() {
527
+ commentStartLine := file.Line(cg.Pos())
528
+ // Inline if starts on same line as stmt end AND starts after stmt end position
529
+ if commentStartLine == stmtEndLine && cg.Pos() > stmtEndPos {
530
+ isInlineComment = true
531
+ }
532
+ }
533
+
534
+ // If it's NOT an inline comment for this statement, write it here
535
+ if !isInlineComment {
536
+ start := 0
537
+ if file != nil && cg.Pos().IsValid() {
538
+ start = file.Line(cg.Pos())
539
+ }
540
+ writeBlank(lastLine, start)
541
+ // WriteDoc does not currently return an error, assuming it's safe for now.
542
+ c.WriteDoc(cg) // WriteDoc will handle the actual comment text
543
+ if file != nil && cg.End().IsValid() {
544
+ lastLine = file.Line(cg.End())
545
+ }
546
+ }
547
+ // If it IS an inline comment, skip it. The statement writer will handle it.
548
+ }
549
+
550
+ // the statement itself
551
+ stmtStart := 0
552
+ if file != nil && stmt.Pos().IsValid() {
553
+ stmtStart = file.Line(stmt.Pos())
554
+ }
555
+ writeBlank(lastLine, stmtStart)
556
+ // Call the specific statement writer (e.g., WriteStmtAssign).
557
+ // It is responsible for handling its own inline comment.
558
+ if err := c.WriteStmt(stmt); err != nil {
559
+ return fmt.Errorf("failed to write statement in block: %w", err)
560
+ }
561
+
562
+ if file != nil && stmt.End().IsValid() {
563
+ // Update lastLine based on the statement's end, *including* potential inline comment handled by WriteStmt*
564
+ lastLine = file.Line(stmt.End())
565
+ }
566
+ }
567
+
568
+ // 2. Trailing comments on the block (after last stmt, before closing brace)
569
+ trailing := c.cmap.Filter(exp).Comments()
570
+ for _, cg := range trailing {
571
+ start := 0
572
+ if file != nil && cg.Pos().IsValid() {
573
+ start = file.Line(cg.Pos())
574
+ }
575
+ // only emit if it follows the last content
576
+ if start > lastLine {
577
+ writeBlank(lastLine, start)
578
+ // WriteDoc does not currently return an error, assuming it's safe for now.
579
+ c.WriteDoc(cg)
580
+ if file != nil && cg.End().IsValid() {
581
+ lastLine = file.Line(cg.End())
582
+ }
583
+ }
584
+ }
585
+
586
+ // 3. Blank lines before closing brace
587
+ closing := 0
588
+ if file != nil && exp.Rbrace.IsValid() {
589
+ closing = file.Line(exp.Rbrace)
590
+ }
591
+ writeBlank(lastLine, closing)
592
+
593
+ // Closing brace
594
+ c.tsw.Indent(-1)
595
+ c.tsw.WriteLiterally("}")
596
+
597
+ if !suppressNewline {
598
+ c.tsw.WriteLine("")
599
+ }
600
+ return nil
601
+ }
602
+
603
+ // writeAssignmentCore writes the core LHS, operator, and RHS of an assignment.
604
+ // It does NOT handle blank identifiers, 'let' keyword, or trailing semicolons/comments/newlines.
605
+ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Token) error {
606
+ // Special handling for integer division assignment (/=)
607
+ if tok == token.QUO_ASSIGN && len(lhs) == 1 && len(rhs) == 1 {
608
+ lhsType := c.pkg.TypesInfo.TypeOf(lhs[0])
609
+ rhsType := c.pkg.TypesInfo.TypeOf(rhs[0])
610
+
611
+ if lhsType != nil && rhsType != nil {
612
+ lhsBasic, lhsIsBasic := lhsType.Underlying().(*gtypes.Basic)
613
+ rhsBasic, rhsIsBasic := rhsType.Underlying().(*gtypes.Basic)
614
+
615
+ if lhsIsBasic && rhsIsBasic && (lhsBasic.Info()&gtypes.IsInteger != 0) && (rhsBasic.Info()&gtypes.IsInteger != 0) {
616
+ // Integer division assignment: lhs = Math.floor(lhs / rhs)
617
+ if err := c.WriteValueExpr(lhs[0]); err != nil {
618
+ return err
619
+ }
620
+ c.tsw.WriteLiterally(" = Math.floor(")
621
+ if err := c.WriteValueExpr(lhs[0]); err != nil { // Write LHS again for the division
622
+ return err
623
+ }
624
+ c.tsw.WriteLiterally(" / ")
625
+ if err := c.WriteValueExpr(rhs[0]); err != nil {
626
+ return err
627
+ }
628
+ c.tsw.WriteLiterally(")")
629
+ return nil // Handled integer division assignment
630
+ }
631
+ }
632
+ }
633
+
634
+ // --- Original logic for other assignments ---
635
+ isMapIndexLHS := false // Track if the first LHS is a map index
636
+ for i, l := range lhs {
637
+ if i != 0 {
638
+ c.tsw.WriteLiterally(", ")
639
+ }
640
+ // Handle map indexing assignment specially
641
+ currentIsMapIndex := false
642
+ if indexExpr, ok := l.(*ast.IndexExpr); ok {
643
+ if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
644
+ if _, isMap := tv.Type.Underlying().(*gtypes.Map); isMap {
645
+ currentIsMapIndex = true
646
+ if i == 0 {
647
+ isMapIndexLHS = true
648
+ }
649
+ // Use mapSet helper
650
+ c.tsw.WriteLiterally("goscript.mapSet(")
651
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
652
+ return err
653
+ }
654
+ c.tsw.WriteLiterally(", ")
655
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
656
+ return err
657
+ }
658
+ c.tsw.WriteLiterally(", ")
659
+ // Value will be added after operator and RHS
660
+ }
661
+ }
662
+ }
663
+
664
+ if !currentIsMapIndex {
665
+ if err := c.WriteValueExpr(l); err != nil { // LHS is a value
666
+ return err
667
+ }
668
+ }
669
+ }
670
+
671
+ // Only write the assignment operator for regular variables, not for map assignments handled by mapSet
672
+ if isMapIndexLHS && len(lhs) == 1 { // Only skip operator if it's a single map assignment
673
+ // Continue, we've already written part of the mapSet() function call
674
+ } else {
675
+ c.tsw.WriteLiterally(" ")
676
+ tokStr, ok := gstypes.TokenToTs(tok) // Use explicit gstypes alias
677
+ if !ok {
678
+ c.tsw.WriteLiterally("?= ")
679
+ c.tsw.WriteCommentLine("Unknown token " + tok.String())
680
+ } else {
681
+ c.tsw.WriteLiterally(tokStr)
682
+ }
683
+ c.tsw.WriteLiterally(" ")
684
+ }
685
+
686
+ for i, r := range rhs {
687
+ if i != 0 {
688
+ c.tsw.WriteLiterally(", ")
689
+ }
690
+ // Check if we should apply clone for value-type semantics
691
+ if shouldApplyClone(c.pkg, r) {
692
+ if err := c.WriteValueExpr(r); err != nil { // RHS is a value
693
+ return err
694
+ }
695
+ c.tsw.WriteLiterally(".clone()")
696
+ } else {
697
+ if err := c.WriteValueExpr(r); err != nil { // RHS is a value
698
+ return err
699
+ }
700
+ }
701
+ }
702
+ // If the LHS was a single map index, close the mapSet call
703
+ if isMapIndexLHS && len(lhs) == 1 {
704
+ c.tsw.WriteLiterally(")")
705
+ }
706
+ return nil
707
+ }
708
+
709
+ // writeChannelReceiveWithOk handles the val, ok := <-channel assignment pattern.
710
+ func (c *GoToTSCompiler) writeChannelReceiveWithOk(lhs []ast.Expr, unaryExpr *ast.UnaryExpr, tok token.Token) error {
711
+ // Ensure LHS has exactly two expressions
712
+ if len(lhs) != 2 {
713
+ return fmt.Errorf("internal error: writeChannelReceiveWithOk called with %d LHS expressions", len(lhs))
714
+ }
715
+
716
+ // Get variable names, handling blank identifiers
717
+ valueIsBlank := false
718
+ okIsBlank := false
719
+ var valueName string
720
+ var okName string
721
+
722
+ if valIdent, ok := lhs[0].(*ast.Ident); ok {
723
+ if valIdent.Name == "_" {
724
+ valueIsBlank = true
725
+ } else {
726
+ valueName = valIdent.Name
727
+ }
728
+ } else {
729
+ return fmt.Errorf("unhandled LHS expression type for value in channel receive: %T", lhs[0])
730
+ }
731
+
732
+ if okIdent, ok := lhs[1].(*ast.Ident); ok {
733
+ if okIdent.Name == "_" {
734
+ okIsBlank = true
735
+ } else {
736
+ okName = okIdent.Name
737
+ }
738
+ } else {
739
+ return fmt.Errorf("unhandled LHS expression type for ok in channel receive: %T", lhs[1])
740
+ }
741
+
742
+ // Generate temporary variable for the result
743
+ resultVar := c.newTempVar()
744
+ c.tsw.WriteLiterally("const ")
745
+ c.tsw.WriteLiterally(resultVar)
746
+ c.tsw.WriteLiterally(" = await ")
747
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
748
+ return fmt.Errorf("failed to write channel expression in receive: %w", err)
749
+ }
750
+ c.tsw.WriteLiterally(".receiveWithOk()")
751
+ c.tsw.WriteLine("")
752
+
753
+ // Assign to variables, declaring if needed (tok == token.DEFINE)
754
+ assignOrDeclare := func(varName string, isBlank bool, valueSource string) error {
755
+ if !isBlank {
756
+ if tok == token.DEFINE {
757
+ // Check if the variable is already declared in the current scope to avoid redeclaration error
758
+ // This requires scope tracking, which is complex. For now, assume := might redeclare.
759
+ // A safer approach might be to always assign, assuming prior declaration or handling scope elsewhere.
760
+ // Let's stick with 'let' for := for now, acknowledging potential issues.
761
+ c.tsw.WriteLiterally("let ")
762
+ }
763
+ c.tsw.WriteLiterally(varName)
764
+ c.tsw.WriteLiterally(" = ")
765
+ c.tsw.WriteLiterally(resultVar)
766
+ c.tsw.WriteLiterally(".")
767
+ c.tsw.WriteLiterally(valueSource)
768
+ c.tsw.WriteLine("")
769
+ }
770
+ return nil
771
+ }
772
+
773
+ if err := assignOrDeclare(valueName, valueIsBlank, "value"); err != nil {
774
+ return err
775
+ }
776
+ if err := assignOrDeclare(okName, okIsBlank, "ok"); err != nil {
777
+ return err
778
+ }
779
+
780
+ return nil
781
+ }
782
+
783
+ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
784
+ // writeTypeAssertion handles multi-variable assignment from a type assertion.
785
+ writeTypeAssertion := func(typeAssertExpr *ast.TypeAssertExpr) error {
786
+ interfaceExpr := typeAssertExpr.X
787
+ assertedType := typeAssertExpr.Type
788
+
789
+ // Write the TypeScript code for the type assertion.
790
+ c.tsw.WriteLiterally("let ok: boolean = (")
791
+ if err := c.WriteValueExpr(interfaceExpr); err != nil {
792
+ return fmt.Errorf("failed to write interface expression in type assertion (ok check): %w", err)
793
+ }
794
+ c.tsw.WriteLiterally(" as any) satisfies ")
795
+ c.WriteTypeExpr(assertedType)
796
+ c.tsw.WriteLine("")
797
+
798
+ c.tsw.WriteLiterally("let assertedValue: ")
799
+ c.WriteTypeExpr(assertedType)
800
+ c.tsw.WriteLiterally(" | null = ok ? (")
801
+ if err := c.WriteValueExpr(interfaceExpr); err != nil {
802
+ return fmt.Errorf("failed to write interface expression in type assertion (value assignment): %w", err)
803
+ }
804
+ c.tsw.WriteLiterally(" as ")
805
+ c.WriteTypeExpr(assertedType)
806
+ c.tsw.WriteLiterally(") : null")
807
+ c.tsw.WriteLine("")
808
+
809
+ return nil
810
+ }
811
+
812
+ // writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
813
+ writeMultiVarAssignFromCall := func(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
814
+ // Determine if 'let' or 'const' is needed for :=
815
+ if tok == token.DEFINE {
816
+ // For simplicity, use 'let' for := in multi-variable assignments.
817
+ // More advanced analysis might be needed to determine if const is possible.
818
+ c.tsw.WriteLiterally("let ")
819
+ }
820
+
821
+ // Write the left-hand side as a destructuring pattern
822
+ c.tsw.WriteLiterally("[")
823
+ for i, lhsExpr := range lhs {
824
+ if i != 0 {
825
+ c.tsw.WriteLiterally(", ")
826
+ }
827
+ // Write the variable name, omitting '_' for blank identifier
828
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
829
+ c.WriteIdentValue(ident)
830
+ } else if !ok {
831
+ // Should not happen for valid Go code in this context, but handle defensively
832
+ c.tsw.WriteCommentInline(fmt.Sprintf("unhandled LHS expression in destructuring: %T", lhsExpr))
833
+ }
834
+ }
835
+ c.tsw.WriteLiterally("] = ")
836
+
837
+ // Write the right-hand side (the function call)
838
+ if err := c.WriteValueExpr(callExpr); err != nil {
839
+ return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
840
+ }
841
+
842
+ c.tsw.WriteLine("") // Remove semicolon
843
+ return nil
844
+ }
845
+
846
+ // writeSingleAssign handles a single assignment pair, including blank identifiers and short declarations.
847
+ writeSingleAssign := func(lhsExpr, rhsExpr ast.Expr, tok token.Token, isLast bool) error {
848
+ // Check for blank identifier on the left-hand side
849
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name == "_" {
850
+ // Blank identifier: evaluate the RHS for side effects, but discard the value.
851
+ if err := c.WriteValueExpr(rhsExpr); err != nil {
852
+ return fmt.Errorf("failed to write RHS for blank identifier assignment: %w", err)
853
+ }
854
+ c.tsw.WriteCommentInline("discarded value")
855
+ c.tsw.WriteLine("") // Remove semicolon, each assignment gets its own line
856
+ return nil
857
+ }
858
+
859
+ // Not a blank identifier: generate an assignment statement.
860
+
861
+ // Handle short variable declaration (:=)
862
+ if tok == token.DEFINE {
863
+ // Use 'let' for the first declaration of a variable.
864
+ // More sophisticated analysis might be needed to determine if it's truly the first declaration
865
+ // in the current scope, but for now, assume := implies a new variable.
866
+ c.tsw.WriteLiterally("let ")
867
+ }
868
+
869
+ // Write the core assignment (LHS = RHS)
870
+ // Pass single-element slices for LHS and RHS to writeAssignmentCore
871
+ if err := c.writeAssignmentCore([]ast.Expr{lhsExpr}, []ast.Expr{rhsExpr}, tok); err != nil {
872
+ return fmt.Errorf("failed to write core assignment: %w", err)
873
+ }
874
+
875
+ // Handle trailing inline comment for this specific assignment line
876
+ // This is more complex with multi-variable assignments. For simplicity,
877
+ // we'll associate any comment immediately following the *entire* Go statement
878
+ // with the *last* generated TypeScript assignment line.
879
+ // A more accurate approach would require mapping comments to specific LHS/RHS pairs.
880
+ if isLast {
881
+ if c.pkg != nil && c.pkg.Fset != nil && exp.End().IsValid() {
882
+ if file := c.pkg.Fset.File(exp.End()); file != nil {
883
+ endLine := file.Line(exp.End())
884
+ // Check comments associated *directly* with the AssignStmt node
885
+ for _, cg := range c.cmap[exp] {
886
+ if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
887
+ commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
888
+ c.tsw.WriteLiterally(" // " + commentText)
889
+ break // Assume only one inline comment per statement
890
+ }
891
+ }
892
+ }
893
+ }
894
+ }
895
+
896
+ // Write the newline to finish the statement line
897
+ c.tsw.WriteLine("") // Remove semicolon
898
+ return nil
899
+ }
900
+
901
+ // writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
902
+ writeMapLookupWithExists := func(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
903
+ // First check that we have exactly two LHS expressions (value and exists)
904
+ if len(lhs) != 2 {
905
+ return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
906
+ }
907
+
908
+ // Check for blank identifiers and get variable names
909
+ valueIsBlank := false
910
+ existsIsBlank := false
911
+ var valueName string
912
+ var existsName string
913
+
914
+ if valIdent, ok := lhs[0].(*ast.Ident); ok {
915
+ if valIdent.Name == "_" {
916
+ valueIsBlank = true
917
+ } else {
918
+ valueName = valIdent.Name
919
+ }
920
+ } else {
921
+ return fmt.Errorf("unhandled LHS expression type for value in map comma-ok: %T", lhs[0])
922
+ }
923
+
924
+ if existsIdent, ok := lhs[1].(*ast.Ident); ok {
925
+ if existsIdent.Name == "_" {
926
+ existsIsBlank = true
927
+ } else {
928
+ existsName = existsIdent.Name
929
+ }
930
+ } else {
931
+ return fmt.Errorf("unhandled LHS expression type for exists in map comma-ok: %T", lhs[1])
932
+ }
933
+
934
+ // Declare variables if using := and not blank
935
+ if tok == token.DEFINE {
936
+ if !valueIsBlank {
937
+ c.tsw.WriteLiterally("let ")
938
+ c.tsw.WriteLiterally(valueName)
939
+ c.tsw.WriteLine("")
940
+ }
941
+ if !existsIsBlank {
942
+ c.tsw.WriteLiterally("let ")
943
+ c.tsw.WriteLiterally(existsName)
944
+ c.tsw.WriteLine("")
945
+ }
946
+ }
947
+
948
+ // Assign 'exists'
949
+ if !existsIsBlank {
950
+ c.tsw.WriteLiterally(existsName)
951
+ c.tsw.WriteLiterally(" = ")
952
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
953
+ return err
954
+ }
955
+ c.tsw.WriteLiterally(".has(")
956
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
957
+ return err
958
+ }
959
+ c.tsw.WriteLiterally(")")
960
+ c.tsw.WriteLine("")
961
+ }
962
+
963
+ // Assign 'value'
964
+ if !valueIsBlank {
965
+ c.tsw.WriteLiterally(valueName)
966
+ c.tsw.WriteLiterally(" = ")
967
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
968
+ return err
969
+ }
970
+ c.tsw.WriteLiterally(".get(")
971
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
972
+ return err
973
+ }
974
+ c.tsw.WriteLiterally(") ?? ")
975
+ // Write the zero value for the map's value type
976
+ if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
977
+ if mapType, isMap := tv.Type.Underlying().(*gtypes.Map); isMap {
978
+ c.WriteZeroValueForType(mapType.Elem())
979
+ } else {
980
+ // Fallback zero value if type info is missing or not a map
981
+ c.tsw.WriteLiterally("null")
982
+ }
983
+ } else {
984
+ c.tsw.WriteLiterally("null")
985
+ }
986
+ c.tsw.WriteLine("")
987
+ }
988
+
989
+ return nil
990
+ }
991
+
992
+ // Handle multi-variable assignment from a single expression.
993
+ if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
994
+ rhsExpr := exp.Rhs[0]
995
+ if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
996
+ return writeTypeAssertion(typeAssertExpr)
997
+ } else if indexExpr, ok := rhsExpr.(*ast.IndexExpr); ok {
998
+ // Check if this is a map lookup (comma-ok idiom)
999
+ if len(exp.Lhs) == 2 {
1000
+ // Get the type of the indexed expression
1001
+ if c.pkg != nil && c.pkg.TypesInfo != nil {
1002
+ tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]
1003
+ if ok {
1004
+ // Check if it's a map type
1005
+ if _, isMap := tv.Type.Underlying().(*gtypes.Map); isMap {
1006
+ return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
1007
+ }
1008
+ }
1009
+ }
1010
+ }
1011
+ } else if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
1012
+ // Handle val, ok := <-channel
1013
+ if len(exp.Lhs) == 2 {
1014
+ return c.writeChannelReceiveWithOk(exp.Lhs, unaryExpr, exp.Tok)
1015
+ }
1016
+ // If LHS count is not 2, fall through to error or other handling
1017
+ } else if callExpr, ok := rhsExpr.(*ast.CallExpr); ok {
1018
+ return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
1019
+ }
1020
+ // If none of the specific multi-assign patterns match, fall through to the error check below
1021
+ }
1022
+
1023
+ // Handle all other assignment cases (one-to-one, multiple RHS expressions, etc.)
1024
+ // Ensure LHS and RHS have the same length for valid Go code in these cases
1025
+ if len(exp.Lhs) != len(exp.Rhs) {
1026
+ // Allow single RHS channel receive to be discarded (case <-ch:)
1027
+ if len(exp.Lhs) == 0 && len(exp.Rhs) == 1 {
1028
+ if unaryExpr, ok := exp.Rhs[0].(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
1029
+ // Translate <-ch to await ch.receive() and discard result
1030
+ c.tsw.WriteLiterally("await ")
1031
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
1032
+ return fmt.Errorf("failed to write channel expression in discarded receive: %w", err)
1033
+ }
1034
+ c.tsw.WriteLiterally(".receive()") // Use receive() as receiveWithOk() result isn't needed
1035
+ c.tsw.WriteLine("")
1036
+ return nil
1037
+ }
1038
+ }
1039
+
1040
+ c.tsw.WriteCommentLine(fmt.Sprintf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs)))
1041
+ return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
1042
+ }
1043
+
1044
+ // Process each assignment pair using the core writer
1045
+ for i := range exp.Lhs {
1046
+ if err := writeSingleAssign(exp.Lhs[i], exp.Rhs[i], exp.Tok, i == len(exp.Lhs)-1); err != nil {
1047
+ return err
1048
+ }
1049
+ }
1050
+
1051
+ return nil
1052
+ }
1053
+
1054
+ // Note: delegates to WriteZeroValueForType for unified zero‐value mapping.
1055
+
1056
+ // shouldApplyClone determines if .clone() should be applied to the RHS of an assignment
1057
+ // to emulate Go's value semantics for structs.
1058
+ // This requires type information.
1059
+ func shouldApplyClone(pkg *packages.Package, rhs ast.Expr) bool {
1060
+ if pkg == nil || pkg.TypesInfo == nil {
1061
+ // Cannot determine type without type info, default to no clone
1062
+ return false
1063
+ }
1064
+
1065
+ // Get the type of the RHS expression
1066
+ tv, found := pkg.TypesInfo.Types[rhs]
1067
+ if !found || tv.Type == nil {
1068
+ // Type information not found, default to no clone
1069
+ return false
1070
+ }
1071
+
1072
+ // Optimization: If the RHS is a composite literal itself, we don't need to clone immediately.
1073
+ if _, isCompositeLit := rhs.(*ast.CompositeLit); isCompositeLit {
1074
+ // Check if it's a struct *value* literal (not a pointer like &T{})
1075
+ // We rely on the type check below to confirm it's a struct.
1076
+ // If it IS a struct composite literal, don't clone.
1077
+ // If it's something else (like a slice literal), the type check below will handle it.
1078
+ if _, isStruct := tv.Type.Underlying().(*gtypes.Struct); isStruct {
1079
+ return false // Don't clone direct composite literal initialization
1080
+ }
1081
+ // If it's a composite literal but not for a struct (e.g., slice), proceed with normal type check.
1082
+ }
1083
+
1084
+ // Check if the underlying type is a struct (for non-composite-literal RHS)
1085
+ // Also check if it's a named type whose underlying type is a struct
1086
+ switch t := tv.Type.Underlying().(type) {
1087
+ case *gtypes.Struct:
1088
+ // It's a struct, apply clone
1089
+ return true
1090
+ case *gtypes.Named:
1091
+ // It's a named type, check its underlying type
1092
+ if _, ok := t.Underlying().(*gtypes.Struct); ok {
1093
+ return true
1094
+ }
1095
+ }
1096
+
1097
+ // Not a struct, do not apply clone
1098
+ return false
1099
+ }
1100
+
1101
+ // WriteStmtExpr writes an expr statement.
1102
+ func (c *GoToTSCompiler) WriteStmtExpr(exp *ast.ExprStmt) error {
1103
+ // Handle simple channel receive used as a statement (<-ch)
1104
+ if unaryExpr, ok := exp.X.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
1105
+ // Translate <-ch to await ch.receive()
1106
+ c.tsw.WriteLiterally("await ")
1107
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
1108
+ return fmt.Errorf("failed to write channel expression in receive statement: %w", err)
1109
+ }
1110
+ c.tsw.WriteLiterally(".receive()") // Use receive() as the value is discarded
1111
+ c.tsw.WriteLine("")
1112
+ return nil
1113
+ }
1114
+
1115
+ // Handle other expression statements
1116
+ if err := c.WriteValueExpr(exp.X); err != nil { // Expression statement evaluates a value
1117
+ return err
1118
+ }
1119
+
1120
+ // Handle potential inline comment for ExprStmt
1121
+ inlineCommentWritten := false
1122
+ if c.pkg != nil && c.pkg.Fset != nil && exp.End().IsValid() {
1123
+ if file := c.pkg.Fset.File(exp.End()); file != nil {
1124
+ endLine := file.Line(exp.End())
1125
+ // Check comments associated *directly* with the ExprStmt node
1126
+ for _, cg := range c.cmap[exp] {
1127
+ if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
1128
+ commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
1129
+ c.tsw.WriteLiterally(" // " + commentText)
1130
+ inlineCommentWritten = true
1131
+ break
1132
+ }
1133
+ }
1134
+ // Also check comments associated with the underlying expression X
1135
+ // This might be necessary if the comment map links it to X instead of ExprStmt
1136
+ if !inlineCommentWritten {
1137
+ for _, cg := range c.cmap[exp.X] {
1138
+ if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
1139
+ commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
1140
+ c.tsw.WriteLiterally(" // " + commentText)
1141
+ inlineCommentWritten = true //nolint:ineffassign
1142
+ break
1143
+ }
1144
+ }
1145
+ }
1146
+ }
1147
+ }
1148
+
1149
+ // Add semicolon according to design doc (omit semicolons) - REMOVED semicolon
1150
+ c.tsw.WriteLine("") // Finish with a newline
1151
+ return nil
1152
+ }
1153
+
1154
+ // WriteStmtFor writes a for statement.
1155
+ func (c *GoToTSCompiler) WriteStmtFor(exp *ast.ForStmt) error {
1156
+ c.tsw.WriteLiterally("for (")
1157
+ if exp.Init != nil {
1158
+ if err := c.WriteForInit(exp.Init); err != nil { // Use WriteForInit
1159
+ return fmt.Errorf("failed to write for loop initialization: %w", err)
1160
+ }
1161
+ }
1162
+ c.tsw.WriteLiterally("; ")
1163
+ if exp.Cond != nil {
1164
+ if err := c.WriteValueExpr(exp.Cond); err != nil { // Condition is a value
1165
+ return fmt.Errorf("failed to write for loop condition: %w", err)
1166
+ }
1167
+ }
1168
+ c.tsw.WriteLiterally("; ")
1169
+ if exp.Post != nil {
1170
+ if err := c.WriteForPost(exp.Post); err != nil { // Use WriteForPost
1171
+ return fmt.Errorf("failed to write for loop post statement: %w", err)
1172
+ }
1173
+ }
1174
+ c.tsw.WriteLiterally(") ")
1175
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
1176
+ return fmt.Errorf("failed to write for loop body: %w", err)
1177
+ }
1178
+ return nil
1179
+ }
1180
+
1181
+ // WriteForInit writes the initialization part of a for loop header.
1182
+ func (c *GoToTSCompiler) WriteForInit(stmt ast.Stmt) error {
1183
+ switch s := stmt.(type) {
1184
+ case *ast.AssignStmt:
1185
+ // Handle assignment in init (e.g., i := 0 or i = 0)
1186
+ // Need to handle 'let' for :=
1187
+ if s.Tok == token.DEFINE {
1188
+ c.tsw.WriteLiterally("let ")
1189
+ }
1190
+ // Write the core assignment without trailing syntax
1191
+ // Blank identifiers should already be handled by filterBlankIdentifiers if needed,
1192
+ // but for init statements they are less common. Let's assume simple assignments for now.
1193
+ if err := c.writeAssignmentCore(s.Lhs, s.Rhs, s.Tok); err != nil {
1194
+ return err
1195
+ }
1196
+ case *ast.ExprStmt:
1197
+ // Handle expression statement in init (less common, but possible)
1198
+ if err := c.WriteValueExpr(s.X); err != nil {
1199
+ return err
1200
+ }
1201
+ default:
1202
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled for loop init statement: %T", stmt))
1203
+ }
1204
+ return nil
1205
+ }
1206
+
1207
+ // WriteForPost writes the post part of a for loop header.
1208
+ func (c *GoToTSCompiler) WriteForPost(stmt ast.Stmt) error {
1209
+ switch s := stmt.(type) {
1210
+ case *ast.IncDecStmt:
1211
+ // Handle increment/decrement (e.g., i++)
1212
+ if err := c.WriteValueExpr(s.X); err != nil { // The expression (e.g., i)
1213
+ return err
1214
+ }
1215
+ tokStr, ok := gstypes.TokenToTs(s.Tok)
1216
+ if !ok {
1217
+ c.tsw.WriteLiterally("/* unknown incdec token */")
1218
+ } else {
1219
+ c.tsw.WriteLiterally(tokStr) // The token (e.g., ++)
1220
+ }
1221
+ case *ast.AssignStmt:
1222
+ // Handle assignment in post (e.g., i = i + 1)
1223
+ // No 'let' keyword here
1224
+ // Blank identifiers should already be handled by filterBlankIdentifiers if needed.
1225
+ if err := c.writeAssignmentCore(s.Lhs, s.Rhs, s.Tok); err != nil {
1226
+ return err
1227
+ }
1228
+ case *ast.ExprStmt:
1229
+ // Handle expression statement in post (less common)
1230
+ if err := c.WriteValueExpr(s.X); err != nil {
1231
+ return err
1232
+ }
1233
+ default:
1234
+ c.tsw.WriteCommentLine(fmt.Sprintf("unhandled for loop post statement: %T", stmt))
1235
+ }
1236
+ return nil
1237
+ }
1238
+
1239
+ // WriteZeroValue writes the TypeScript zero‐value for a Go type.
1240
+ func (c *GoToTSCompiler) WriteZeroValue(expr ast.Expr) {
1241
+ switch t := expr.(type) {
1242
+ case *ast.Ident:
1243
+ // Try to resolve identifier type
1244
+ if tv, found := c.pkg.TypesInfo.Types[t]; found {
1245
+ underlying := tv.Type.Underlying()
1246
+ switch u := underlying.(type) {
1247
+ case *gtypes.Basic: // Use gotypes alias
1248
+ if u.Info()&gtypes.IsNumeric != 0 { // Use gotypes alias
1249
+ c.tsw.WriteLiterally("0")
1250
+ } else if u.Info()&gtypes.IsString != 0 { // Use gotypes alias
1251
+ c.tsw.WriteLiterally(`""`)
1252
+ } else if u.Info()&gtypes.IsBoolean != 0 { // Use gotypes alias
1253
+ c.tsw.WriteLiterally("false")
1254
+ } else {
1255
+ c.tsw.WriteLiterally("null // unknown basic type")
1256
+ }
1257
+ case *gtypes.Struct: // Use gotypes alias
1258
+ // Zero value for struct is new instance
1259
+ c.tsw.WriteLiterally("new ")
1260
+ c.WriteTypeExpr(t) // Write the type name
1261
+ c.tsw.WriteLiterally("()")
1262
+ case *gtypes.Pointer, *gtypes.Interface, *gtypes.Slice, *gtypes.Map, *gtypes.Chan, *gtypes.Signature: // Use gotypes alias
1263
+ // Pointers, interfaces, slices, maps, channels, functions zero value is null/undefined
1264
+ c.tsw.WriteLiterally("null")
1265
+ default:
1266
+ c.tsw.WriteLiterally("null // unknown underlying type")
1267
+ }
1268
+ } else {
1269
+ // Fallback for unresolved identifiers (basic types)
1270
+ switch t.Name {
1271
+ case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64", "complex64", "complex128":
1272
+ c.tsw.WriteLiterally("0")
1273
+ case "string":
1274
+ c.tsw.WriteLiterally(`""`)
1275
+ case "bool":
1276
+ c.tsw.WriteLiterally("false")
1277
+ default:
1278
+ // Assume custom type, might be struct or interface -> null
1279
+ c.tsw.WriteLiterally("null // unresolved identifier")
1280
+ }
1281
+ }
1282
+ case *ast.StarExpr, *ast.InterfaceType, *ast.ArrayType, *ast.MapType, *ast.ChanType, *ast.FuncType:
1283
+ // Pointers, interfaces, arrays, maps, channels, functions zero value is null/undefined
1284
+ c.tsw.WriteLiterally("null")
1285
+ case *ast.StructType:
1286
+ // Anonymous struct zero value is complex, default to null for now
1287
+ c.tsw.WriteLiterally("null")
1288
+ default:
1289
+ // everything else defaults to null in TS
1290
+ c.tsw.WriteLiterally("null")
1291
+ }
1292
+ }
1293
+
1294
+ // WriteStmtRange writes a for…range loop by generating equivalent TypeScript code.
1295
+ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
1296
+ // Get the type of the iterable expression
1297
+ iterType := c.pkg.TypesInfo.TypeOf(exp.X)
1298
+ underlying := iterType.Underlying()
1299
+
1300
+ // Handle map types
1301
+ if _, ok := underlying.(*gtypes.Map); ok {
1302
+ // Use for-of with entries() for proper Map iteration
1303
+ c.tsw.WriteLiterally("for (const [k, v] of ")
1304
+ if err := c.WriteValueExpr(exp.X); err != nil {
1305
+ return fmt.Errorf("failed to write range loop map expression: %w", err)
1306
+ }
1307
+ c.tsw.WriteLiterally(".entries()) {")
1308
+ c.tsw.Indent(1)
1309
+ c.tsw.WriteLine("")
1310
+ // If a key variable is provided and is not blank, declare it as a constant
1311
+ if exp.Key != nil {
1312
+ if ident, ok := exp.Key.(*ast.Ident); ok && ident.Name != "_" {
1313
+ c.tsw.WriteLiterally("const ")
1314
+ // WriteIdentValue does not currently return an error, assuming it's safe for now.
1315
+ c.WriteIdentValue(ident)
1316
+ c.tsw.WriteLiterally(" = k")
1317
+ c.tsw.WriteLine("")
1318
+ }
1319
+ }
1320
+ // If a value variable is provided and is not blank, use the value from entries()
1321
+ if exp.Value != nil {
1322
+ if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
1323
+ c.tsw.WriteLiterally("const ")
1324
+ // WriteIdentValue does not currently return an error, assuming it's safe for now.
1325
+ c.WriteIdentValue(ident)
1326
+ c.tsw.WriteLiterally(" = v")
1327
+ c.tsw.WriteLine("")
1328
+ }
1329
+ }
1330
+ // Write the loop body
1331
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
1332
+ return fmt.Errorf("failed to write range loop map body: %w", err)
1333
+ }
1334
+ c.tsw.Indent(-1)
1335
+ c.tsw.WriteLine("}")
1336
+ return nil
1337
+ }
1338
+
1339
+ // Handle string type by converting the string to a rune array
1340
+ if basic, ok := underlying.(*gtypes.Basic); ok && (basic.Info()&gtypes.IsString != 0) {
1341
+ // Convert the string to runes using goscript.stringToRunes
1342
+ c.tsw.WriteLiterally("const _runes = goscript.stringToRunes(")
1343
+ if err := c.WriteValueExpr(exp.X); err != nil {
1344
+ return fmt.Errorf("failed to write range loop string conversion expression: %w", err)
1345
+ }
1346
+ c.tsw.WriteLiterally(")")
1347
+ c.tsw.WriteLine("")
1348
+ // Determine the index variable name for the generated loop
1349
+ indexVarName := "i" // Default name
1350
+ if exp.Key != nil {
1351
+ if keyIdent, ok := exp.Key.(*ast.Ident); ok && keyIdent.Name != "_" {
1352
+ indexVarName = keyIdent.Name
1353
+ }
1354
+ }
1355
+ c.tsw.WriteLiterally(fmt.Sprintf("for (let %s = 0; %s < _runes.length; %s++) {", indexVarName, indexVarName, indexVarName))
1356
+ c.tsw.Indent(1)
1357
+ c.tsw.WriteLine("")
1358
+ // Declare value if provided and not blank
1359
+ if exp.Value != nil {
1360
+ if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
1361
+ c.tsw.WriteLiterally("const ")
1362
+ // WriteIdentValue does not currently return an error, assuming it's safe for now.
1363
+ c.WriteIdentValue(ident)
1364
+ c.tsw.WriteLiterally(" = _runes[i]")
1365
+ c.tsw.WriteLine("")
1366
+ }
1367
+ }
1368
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
1369
+ return fmt.Errorf("failed to write range loop string body: %w", err)
1370
+ }
1371
+ c.tsw.Indent(-1)
1372
+ c.tsw.WriteLine("}")
1373
+ return nil
1374
+ }
1375
+
1376
+ // Handle array and slice types
1377
+ if _, isArray := underlying.(*gtypes.Array); isArray || isSlice(underlying) {
1378
+ // Determine the index variable name for the generated loop
1379
+ indexVarName := "i" // Default name
1380
+ if exp.Key != nil {
1381
+ if keyIdent, ok := exp.Key.(*ast.Ident); ok && keyIdent.Name != "_" {
1382
+ indexVarName = keyIdent.Name
1383
+ }
1384
+ }
1385
+ // If both key and value are provided, use an index loop and assign both
1386
+ if exp.Key != nil && exp.Value != nil {
1387
+ c.tsw.WriteLiterally(fmt.Sprintf("for (let %s = 0; %s < ", indexVarName, indexVarName))
1388
+ if err := c.WriteValueExpr(exp.X); err != nil { // Write the expression for the iterable
1389
+ return fmt.Errorf("failed to write range loop array/slice expression (key and value): %w", err)
1390
+ }
1391
+ c.tsw.WriteLiterally(fmt.Sprintf(".length; %s++) {", indexVarName))
1392
+ c.tsw.Indent(1)
1393
+ c.tsw.WriteLine("")
1394
+ // Declare value if not blank
1395
+ if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
1396
+ c.tsw.WriteLiterally("const ")
1397
+ // WriteIdentValue does not currently return an error, assuming it's safe for now.
1398
+ c.WriteIdentValue(ident)
1399
+ c.tsw.WriteLiterally(" = ")
1400
+ if err := c.WriteValueExpr(exp.X); err != nil {
1401
+ return fmt.Errorf("failed to write range loop array/slice value expression: %w", err)
1402
+ }
1403
+ c.tsw.WriteLiterally(fmt.Sprintf("[%s]", indexVarName)) // Use indexVarName
1404
+ c.tsw.WriteLine("")
1405
+ }
1406
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
1407
+ return fmt.Errorf("failed to write range loop array/slice body (key and value): %w", err)
1408
+ }
1409
+ c.tsw.Indent(-1)
1410
+ c.tsw.WriteLine("}")
1411
+ return nil
1412
+ } else if exp.Key != nil && exp.Value == nil { // Only key provided
1413
+ c.tsw.WriteLiterally(fmt.Sprintf("for (let %s = 0; %s < ", indexVarName, indexVarName))
1414
+ // Write the expression for the iterable
1415
+ if err := c.WriteValueExpr(exp.X); err != nil {
1416
+ return fmt.Errorf("failed to write expression for the iterable: %w", err)
1417
+ }
1418
+ c.tsw.WriteLiterally(fmt.Sprintf(".length; %s++) {", indexVarName))
1419
+ c.tsw.Indent(1)
1420
+ c.tsw.WriteLine("")
1421
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
1422
+ return fmt.Errorf("failed to write range loop array/slice body (only key): %w", err)
1423
+ }
1424
+ c.tsw.Indent(-1)
1425
+ c.tsw.WriteLine("}")
1426
+ return nil
1427
+ } else if exp.Key == nil && exp.Value != nil { // Only value provided; use for-of loop
1428
+ c.tsw.WriteLiterally("for (const v of ")
1429
+ if err := c.WriteValueExpr(exp.X); err != nil {
1430
+ return fmt.Errorf("failed to write range loop array/slice expression (only value): %w", err)
1431
+ }
1432
+ c.tsw.WriteLiterally(") {")
1433
+ c.tsw.Indent(1)
1434
+ c.tsw.WriteLine("")
1435
+ if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
1436
+ c.tsw.WriteLiterally("const ")
1437
+ // WriteIdentValue does not currently return an error, assuming it's safe for now.
1438
+ c.WriteIdentValue(ident)
1439
+ c.tsw.WriteLiterally(" = v")
1440
+ c.tsw.WriteLine("")
1441
+ }
1442
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
1443
+ return fmt.Errorf("failed to write range loop array/slice body (only value): %w", err)
1444
+ }
1445
+ c.tsw.Indent(-1)
1446
+ c.tsw.WriteLine("}")
1447
+ return nil
1448
+ } else {
1449
+ // Fallback: simple index loop without declaring range variables, use _i
1450
+ indexVarName := "_i"
1451
+ c.tsw.WriteLiterally(fmt.Sprintf("for (let %s = 0; %s < ", indexVarName, indexVarName))
1452
+ if err := c.WriteValueExpr(exp.X); err != nil {
1453
+ return fmt.Errorf("failed to write range loop array/slice length expression (fallback): %w", err)
1454
+ }
1455
+ c.tsw.WriteLiterally(fmt.Sprintf(".length; %s++) {", indexVarName))
1456
+ c.tsw.Indent(1)
1457
+ c.tsw.WriteLine("")
1458
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
1459
+ return fmt.Errorf("failed to write range loop array/slice body (fallback): %w", err)
1460
+ }
1461
+ c.tsw.Indent(-1)
1462
+ c.tsw.WriteLine("}")
1463
+ return nil
1464
+ }
1465
+ }
1466
+
1467
+ // Fallback case if the ranged type is not supported.
1468
+ c.tsw.WriteCommentLine("unsupported range loop")
1469
+ return fmt.Errorf("unsupported range loop type: %T", underlying)
1470
+ }
1471
+
1472
+ // isSlice returns true if the underlying type is a slice.
1473
+ func isSlice(typ gtypes.Type) bool {
1474
+ _, ok := typ.(*gtypes.Slice)
1475
+ return ok
1476
+ }
1477
+
1478
+ // isLHSMapIndex returns true if the expression is an index expression on a map type.
1479
+ func isLHSMapIndex(expr ast.Expr, pkg *packages.Package) bool {
1480
+ if pkg == nil || pkg.TypesInfo == nil {
1481
+ return false
1482
+ }
1483
+ indexExpr, ok := expr.(*ast.IndexExpr)
1484
+ if !ok {
1485
+ return false
1486
+ }
1487
+ tv, ok := pkg.TypesInfo.Types[indexExpr.X]
1488
+ if !ok || tv.Type == nil {
1489
+ return false
1490
+ }
1491
+ _, isMap := tv.Type.Underlying().(*gtypes.Map)
1492
+ return isMap
1493
+ }
1494
+
1495
+ // WriteStmtSend writes a channel send statement (ch <- value).
1496
+ func (c *GoToTSCompiler) WriteStmtSend(exp *ast.SendStmt) error {
1497
+ // Translate ch <- value to await ch.send(value)
1498
+ c.tsw.WriteLiterally("await ")
1499
+ if err := c.WriteValueExpr(exp.Chan); err != nil { // The channel expression
1500
+ return fmt.Errorf("failed to write channel expression in send statement: %w", err)
1501
+ }
1502
+ c.tsw.WriteLiterally(".send(")
1503
+ if err := c.WriteValueExpr(exp.Value); err != nil { // The value expression
1504
+ return fmt.Errorf("failed to write value expression in send statement: %w", err)
1505
+ }
1506
+ c.tsw.WriteLiterally(")")
1507
+ c.tsw.WriteLine("") // Add newline after the statement
1508
+ return nil
1509
+ }