goscript 0.0.13 → 0.0.16

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