goscript 0.0.84 → 0.1.0

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 (188) hide show
  1. package/README.md +13 -1
  2. package/cmd/goscript/cmd_compile.go +70 -69
  3. package/cmd/goscript/cmd_compile_test.go +79 -0
  4. package/cmd/goscript/main.go +10 -5
  5. package/compiler/compile-request.go +218 -0
  6. package/compiler/compiler.go +16 -1336
  7. package/compiler/compliance_test.go +196 -0
  8. package/compiler/config.go +6 -13
  9. package/compiler/diagnostic.go +70 -0
  10. package/compiler/index.test.ts +28 -28
  11. package/compiler/index.ts +40 -72
  12. package/compiler/lowered-program.go +132 -0
  13. package/compiler/lowering.go +3576 -0
  14. package/compiler/override-registry.go +422 -0
  15. package/compiler/override-registry_test.go +207 -0
  16. package/compiler/package-graph.go +231 -0
  17. package/compiler/package-graph_test.go +281 -0
  18. package/compiler/result.go +13 -0
  19. package/compiler/runtime-contract.go +279 -0
  20. package/compiler/runtime-contract_test.go +90 -0
  21. package/compiler/semantic-model-types.go +110 -0
  22. package/compiler/semantic-model.go +922 -0
  23. package/compiler/semantic-model_test.go +416 -0
  24. package/compiler/service.go +133 -0
  25. package/compiler/skeleton_test.go +1145 -0
  26. package/compiler/typescript-emitter.go +663 -0
  27. package/compiler/wasm/compile.go +2 -3
  28. package/compiler/wasm/compile_test.go +29 -0
  29. package/compiler/wasm_api.go +10 -159
  30. package/dist/compiler/index.d.ts +1 -3
  31. package/dist/compiler/index.js +31 -55
  32. package/dist/compiler/index.js.map +1 -1
  33. package/dist/gs/builtin/builtin.d.ts +13 -0
  34. package/dist/gs/builtin/builtin.js +23 -0
  35. package/dist/gs/builtin/builtin.js.map +1 -1
  36. package/dist/gs/builtin/channel.d.ts +3 -3
  37. package/dist/gs/builtin/channel.js.map +1 -1
  38. package/dist/gs/builtin/hostio.d.ts +15 -1
  39. package/dist/gs/builtin/hostio.js +134 -49
  40. package/dist/gs/builtin/hostio.js.map +1 -1
  41. package/dist/gs/builtin/index.d.ts +1 -0
  42. package/dist/gs/builtin/index.js +1 -0
  43. package/dist/gs/builtin/index.js.map +1 -1
  44. package/dist/gs/builtin/slice.d.ts +1 -1
  45. package/dist/gs/builtin/slice.js.map +1 -1
  46. package/dist/gs/builtin/type.d.ts +11 -0
  47. package/dist/gs/builtin/type.js +55 -1
  48. package/dist/gs/builtin/type.js.map +1 -1
  49. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  50. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  51. package/dist/gs/bytes/reader.gs.js.map +1 -1
  52. package/dist/gs/context/context.js.map +1 -1
  53. package/dist/gs/crypto/rand/index.d.ts +5 -0
  54. package/dist/gs/crypto/rand/index.js +77 -0
  55. package/dist/gs/crypto/rand/index.js.map +1 -0
  56. package/dist/gs/encoding/json/index.d.ts +3 -0
  57. package/dist/gs/encoding/json/index.js +160 -0
  58. package/dist/gs/encoding/json/index.js.map +1 -0
  59. package/dist/gs/fmt/fmt.js.map +1 -1
  60. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  61. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  62. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  63. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  64. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  65. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  66. package/dist/gs/go/scanner/index.d.ts +29 -0
  67. package/dist/gs/go/scanner/index.js +120 -0
  68. package/dist/gs/go/scanner/index.js.map +1 -0
  69. package/dist/gs/go/token/index.d.ts +31 -0
  70. package/dist/gs/go/token/index.js +82 -0
  71. package/dist/gs/go/token/index.js.map +1 -0
  72. package/dist/gs/internal/abi/index.js.map +1 -1
  73. package/dist/gs/io/fs/fs.js.map +1 -1
  74. package/dist/gs/io/fs/readdir.js.map +1 -1
  75. package/dist/gs/io/fs/readfile.js.map +1 -1
  76. package/dist/gs/io/fs/stat.js.map +1 -1
  77. package/dist/gs/io/fs/sub.js.map +1 -1
  78. package/dist/gs/io/io.js.map +1 -1
  79. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  80. package/dist/gs/os/error.gs.js +2 -4
  81. package/dist/gs/os/error.gs.js.map +1 -1
  82. package/dist/gs/os/exec.gs.js.map +1 -1
  83. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  84. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  85. package/dist/gs/os/root_js.gs.js.map +1 -1
  86. package/dist/gs/os/tempfile.gs.js +66 -9
  87. package/dist/gs/os/tempfile.gs.js.map +1 -1
  88. package/dist/gs/os/types.gs.js.map +1 -1
  89. package/dist/gs/os/types_js.gs.js +9 -9
  90. package/dist/gs/os/types_js.gs.js.map +1 -1
  91. package/dist/gs/os/types_unix.gs.js.map +1 -1
  92. package/dist/gs/path/filepath/match.js.map +1 -1
  93. package/dist/gs/path/match.js.map +1 -1
  94. package/dist/gs/path/path.js.map +1 -1
  95. package/dist/gs/reflect/index.d.ts +2 -2
  96. package/dist/gs/reflect/index.js +1 -1
  97. package/dist/gs/reflect/index.js.map +1 -1
  98. package/dist/gs/reflect/map.js.map +1 -1
  99. package/dist/gs/reflect/type.d.ts +2 -1
  100. package/dist/gs/reflect/type.js +85 -14
  101. package/dist/gs/reflect/type.js.map +1 -1
  102. package/dist/gs/reflect/types.js.map +1 -1
  103. package/dist/gs/reflect/visiblefields.js.map +1 -1
  104. package/dist/gs/runtime/runtime.js.map +1 -1
  105. package/dist/gs/sort/sort.gs.js.map +1 -1
  106. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  107. package/dist/gs/strconv/quote.gs.js.map +1 -1
  108. package/dist/gs/strings/builder.js.map +1 -1
  109. package/dist/gs/strings/reader.js.map +1 -1
  110. package/dist/gs/strings/replace.js.map +1 -1
  111. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  112. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  113. package/dist/gs/sync/sync.d.ts +1 -0
  114. package/dist/gs/sync/sync.js +12 -0
  115. package/dist/gs/sync/sync.js.map +1 -1
  116. package/dist/gs/time/time.js.map +1 -1
  117. package/dist/gs/unicode/unicode.js.map +1 -1
  118. package/go.mod +2 -2
  119. package/gs/builtin/builtin.ts +27 -0
  120. package/gs/builtin/hostio.test.ts +177 -0
  121. package/gs/builtin/hostio.ts +171 -56
  122. package/gs/builtin/index.ts +1 -0
  123. package/gs/builtin/runtime-contract.test.ts +230 -0
  124. package/gs/builtin/type.ts +84 -1
  125. package/gs/crypto/rand/index.test.ts +32 -0
  126. package/gs/crypto/rand/index.ts +90 -0
  127. package/gs/crypto/rand/meta.json +5 -0
  128. package/gs/encoding/json/index.test.ts +65 -0
  129. package/gs/encoding/json/index.ts +186 -0
  130. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  131. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  132. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  133. package/gs/go/scanner/index.test.ts +50 -0
  134. package/gs/go/scanner/index.ts +157 -0
  135. package/gs/go/token/index.test.ts +21 -0
  136. package/gs/go/token/index.ts +120 -0
  137. package/gs/os/file_unix_js.test.ts +50 -0
  138. package/gs/os/meta.json +1 -2
  139. package/gs/os/tempfile.gs.test.ts +85 -0
  140. package/gs/os/tempfile.gs.ts +71 -11
  141. package/gs/os/types_js.gs.ts +9 -9
  142. package/gs/reflect/index.ts +1 -1
  143. package/gs/reflect/type.ts +106 -17
  144. package/gs/reflect/typefor.test.ts +75 -0
  145. package/gs/sync/sync.test.ts +24 -0
  146. package/gs/sync/sync.ts +12 -0
  147. package/package.json +13 -13
  148. package/compiler/analysis.go +0 -3475
  149. package/compiler/analysis_test.go +0 -338
  150. package/compiler/assignment.go +0 -580
  151. package/compiler/builtin_test.go +0 -92
  152. package/compiler/code-writer.go +0 -115
  153. package/compiler/compiler_test.go +0 -149
  154. package/compiler/composite-lit.go +0 -779
  155. package/compiler/config_test.go +0 -62
  156. package/compiler/constraint.go +0 -86
  157. package/compiler/decl.go +0 -801
  158. package/compiler/expr-call-async.go +0 -188
  159. package/compiler/expr-call-builtins.go +0 -208
  160. package/compiler/expr-call-helpers.go +0 -382
  161. package/compiler/expr-call-make.go +0 -318
  162. package/compiler/expr-call-type-conversion.go +0 -520
  163. package/compiler/expr-call.go +0 -413
  164. package/compiler/expr-selector.go +0 -343
  165. package/compiler/expr-star.go +0 -82
  166. package/compiler/expr-type.go +0 -442
  167. package/compiler/expr-value.go +0 -89
  168. package/compiler/expr.go +0 -773
  169. package/compiler/field.go +0 -183
  170. package/compiler/gs_dependencies_test.go +0 -298
  171. package/compiler/lit.go +0 -322
  172. package/compiler/output.go +0 -72
  173. package/compiler/primitive.go +0 -149
  174. package/compiler/protobuf.go +0 -697
  175. package/compiler/sanitize.go +0 -100
  176. package/compiler/spec-struct.go +0 -995
  177. package/compiler/spec-value.go +0 -540
  178. package/compiler/spec.go +0 -725
  179. package/compiler/stmt-assign.go +0 -664
  180. package/compiler/stmt-for.go +0 -266
  181. package/compiler/stmt-range.go +0 -475
  182. package/compiler/stmt-select.go +0 -262
  183. package/compiler/stmt-type-switch.go +0 -147
  184. package/compiler/stmt.go +0 -1308
  185. package/compiler/type-assert.go +0 -386
  186. package/compiler/type-info.go +0 -156
  187. package/compiler/type-utils.go +0 -207
  188. package/compiler/type.go +0 -892
package/compiler/stmt.go DELETED
@@ -1,1308 +0,0 @@
1
- package compiler
2
-
3
- import (
4
- "fmt"
5
- "go/ast"
6
- "go/token"
7
- "go/types"
8
- "strings"
9
-
10
- "github.com/pkg/errors"
11
- )
12
-
13
- // WriteStmt is a central dispatcher function that translates a Go statement
14
- // (`ast.Stmt`) into its TypeScript equivalent by calling the appropriate
15
- // specialized `WriteStmt*` or `write*` method.
16
- // It handles a wide variety of Go statements:
17
- // - Block statements (`ast.BlockStmt`): `WriteStmtBlock`.
18
- // - Assignment statements (`ast.AssignStmt`): `WriteStmtAssign`.
19
- // - Return statements (`ast.ReturnStmt`): `WriteStmtReturn`.
20
- // - Defer statements (`ast.DeferStmt`): `WriteStmtDefer`.
21
- // - If statements (`ast.IfStmt`): `WriteStmtIf`.
22
- // - Expression statements (`ast.ExprStmt`): `WriteStmtExpr`.
23
- // - Declaration statements (`ast.DeclStmt`): `WriteStmtDecl`.
24
- // - For statements (`ast.ForStmt`): `WriteStmtFor`.
25
- // - Range statements (`ast.RangeStmt`): `WriteStmtRange`.
26
- // - Switch statements (`ast.SwitchStmt`): `WriteStmtSwitch`.
27
- // - Increment/decrement statements (`ast.IncDecStmt`): `WriteStmtIncDec`.
28
- // - Send statements (`ast.SendStmt`): `WriteStmtSend`.
29
- // - Go statements (`ast.GoStmt`): `WriteStmtGo`.
30
- // - Select statements (`ast.SelectStmt`): `WriteStmtSelect`.
31
- // - Branch statements (`ast.BranchStmt`): `WriteStmtBranch`.
32
- // - Type switch statements (`ast.TypeSwitchStmt`): `WriteStmtTypeSwitch`.
33
- // - Labeled statements (`ast.LabeledStmt`): `WriteStmtLabeled`.
34
- //
35
- // If an unknown statement type is encountered, it returns an error.
36
- func (c *GoToTSCompiler) WriteStmt(a ast.Stmt) error {
37
- switch exp := a.(type) {
38
- case *ast.BlockStmt:
39
- if err := c.WriteStmtBlock(exp, false); err != nil {
40
- return fmt.Errorf("failed to write block statement: %w", err)
41
- }
42
- case *ast.AssignStmt:
43
- // special case: if the left side of the assign has () we need a ; to prepend the line
44
- // ;(myStruct!.value).MyInt = 5
45
- // otherwise typescript assumes it is a function call
46
- if len(exp.Lhs) == 1 {
47
- if lhsSel, ok := exp.Lhs[0].(*ast.SelectorExpr); ok {
48
- if _, ok := lhsSel.X.(*ast.ParenExpr); ok {
49
- c.tsw.WriteLiterally(";")
50
- }
51
- }
52
- }
53
-
54
- if err := c.WriteStmtAssign(exp); err != nil {
55
- return fmt.Errorf("failed to write assignment statement: %w", err)
56
- }
57
- case *ast.ReturnStmt:
58
- if err := c.WriteStmtReturn(exp); err != nil {
59
- return fmt.Errorf("failed to write return statement: %w", err)
60
- }
61
- case *ast.DeferStmt:
62
- if err := c.WriteStmtDefer(exp); err != nil {
63
- return fmt.Errorf("failed to write defer statement: %w", err)
64
- }
65
- case *ast.IfStmt:
66
- if err := c.WriteStmtIf(exp); err != nil {
67
- return fmt.Errorf("failed to write if statement: %w", err)
68
- }
69
- case *ast.ExprStmt:
70
- if err := c.WriteStmtExpr(exp); err != nil {
71
- return fmt.Errorf("failed to write expression statement: %w", err)
72
- }
73
- case *ast.DeclStmt:
74
- if err := c.WriteStmtDecl(exp); err != nil {
75
- return fmt.Errorf("failed to write declaration statement: %w", err)
76
- }
77
- case *ast.ForStmt:
78
- if err := c.WriteStmtFor(exp); err != nil {
79
- return fmt.Errorf("failed to write for statement: %w", err)
80
- }
81
- case *ast.RangeStmt:
82
- // Generate TS for for…range loops, log if something goes wrong
83
- if err := c.WriteStmtRange(exp); err != nil {
84
- return fmt.Errorf("failed to write range statement: %w", err)
85
- }
86
- case *ast.SwitchStmt:
87
- if err := c.WriteStmtSwitch(exp); err != nil {
88
- return fmt.Errorf("failed to write switch statement: %w", err)
89
- }
90
- case *ast.IncDecStmt:
91
- if err := c.WriteStmtIncDec(exp); err != nil {
92
- return fmt.Errorf("failed to write increment/decrement statement: %w", err)
93
- }
94
- case *ast.SendStmt:
95
- if err := c.WriteStmtSend(exp); err != nil {
96
- return fmt.Errorf("failed to write send statement: %w", err)
97
- }
98
- case *ast.GoStmt:
99
- if err := c.WriteStmtGo(exp); err != nil {
100
- return fmt.Errorf("failed to write go statement: %w", err)
101
- }
102
- case *ast.SelectStmt:
103
- // Handle select statement
104
- if err := c.WriteStmtSelect(exp); err != nil {
105
- return fmt.Errorf("failed to write select statement: %w", err)
106
- }
107
- case *ast.BranchStmt:
108
- if err := c.WriteStmtBranch(exp); err != nil {
109
- return fmt.Errorf("failed to write branch statement: %w", err)
110
- }
111
- case *ast.TypeSwitchStmt:
112
- if err := c.WriteStmtTypeSwitch(exp); err != nil {
113
- return fmt.Errorf("failed to write type switch statement: %w", err)
114
- }
115
- case *ast.LabeledStmt:
116
- if err := c.WriteStmtLabeled(exp); err != nil {
117
- return fmt.Errorf("failed to write labeled statement: %w", err)
118
- }
119
- default:
120
- return errors.Errorf("unknown statement: %#v\n", a)
121
- }
122
- return nil
123
- }
124
-
125
- // WriteStmtDecl handles declaration statements (`ast.DeclStmt`),
126
- // such as short variable declarations or type declarations within a statement list.
127
- // It processes `ValueSpec`s and `TypeSpec`s within the declaration.
128
- func (c *GoToTSCompiler) WriteStmtDecl(stmt *ast.DeclStmt) error {
129
- // This typically contains a GenDecl
130
- if genDecl, ok := stmt.Decl.(*ast.GenDecl); ok {
131
- for _, spec := range genDecl.Specs {
132
- // Value specs within a declaration statement
133
- if valueSpec, ok := spec.(*ast.ValueSpec); ok {
134
- if err := c.WriteValueSpec(valueSpec); err != nil {
135
- return fmt.Errorf("failed to write value spec in declaration statement: %w", err)
136
- }
137
- } else if typeSpec, ok := spec.(*ast.TypeSpec); ok {
138
- if err := c.WriteTypeSpec(typeSpec); err != nil {
139
- return fmt.Errorf("failed to write type spec in declaration statement: %w", err)
140
- }
141
- } else {
142
- return fmt.Errorf("unhandled spec in DeclStmt: %T", spec)
143
- }
144
- }
145
- } else {
146
- return errors.Errorf("unhandled declaration type in DeclStmt: %T", stmt.Decl)
147
- }
148
- return nil
149
- }
150
-
151
- // WriteStmtIncDec handles increment and decrement statements (`ast.IncDecStmt`).
152
- // It writes the expression followed by `++` or `--`.
153
- func (c *GoToTSCompiler) WriteStmtIncDec(stmt *ast.IncDecStmt) error {
154
- if err := c.WriteValueExpr(stmt.X); err != nil { // The expression (e.g., i)
155
- return fmt.Errorf("failed to write increment/decrement expression: %w", err)
156
- }
157
- tokStr, ok := TokenToTs(stmt.Tok)
158
- if !ok {
159
- return errors.Errorf("unknown incdec token: %s", stmt.Tok.String())
160
- }
161
- c.tsw.WriteLiterally(tokStr) // The token (e.g., ++)
162
- c.tsw.WriteLine("")
163
- return nil
164
- }
165
-
166
- // WriteStmtBranch handles branch statements (`ast.BranchStmt`), such as `break` and `continue`.
167
- func (c *GoToTSCompiler) WriteStmtBranch(stmt *ast.BranchStmt) error {
168
- switch stmt.Tok {
169
- case token.BREAK:
170
- if stmt.Label != nil {
171
- c.tsw.WriteLinef("break %s", stmt.Label.Name)
172
- } else {
173
- c.tsw.WriteLine("break")
174
- }
175
- case token.CONTINUE:
176
- if stmt.Label != nil {
177
- c.tsw.WriteLinef("continue %s", stmt.Label.Name)
178
- } else {
179
- c.tsw.WriteLine("continue")
180
- }
181
- case token.GOTO:
182
- // TypeScript doesn't support goto, but we can handle it by skipping it
183
- // since the labeled statement restructuring should handle the control flow
184
- c.tsw.WriteCommentLinef("goto %s // goto statement skipped", stmt.Label.Name)
185
- case token.FALLTHROUGH:
186
- // Fallthrough is handled in switch statements, should not appear elsewhere
187
- c.tsw.WriteCommentLinef("fallthrough // fallthrough statement skipped")
188
- default:
189
- // This case should ideally not be reached if the Go parser is correct,
190
- // as ast.BranchStmt only covers break, continue, goto, fallthrough.
191
- c.tsw.WriteCommentLinef("unhandled branch statement token: %s", stmt.Tok.String())
192
- }
193
- return nil
194
- }
195
-
196
- // WriteStmtGo translates a Go statement (`ast.GoStmt`) into its TypeScript equivalent.
197
- // It handles `go func(){...}()`, `go namedFunc(args)`, and `go x.Method(args)`.
198
- func (c *GoToTSCompiler) WriteStmtGo(exp *ast.GoStmt) error {
199
- // Handle goroutine statement
200
- // Translate 'go func() { ... }()' to 'queueMicrotask(() => { ... compiled body ... })'
201
- callExpr := exp.Call
202
-
203
- switch fun := callExpr.Fun.(type) {
204
- case *ast.FuncLit:
205
- // For function literals, we need to check if the function literal itself is async
206
- // This happens during analysis in analysisVisitor.Visit for FuncLit nodes
207
- isAsync := c.analysis.IsFuncLitAsync(fun)
208
- if isAsync {
209
- c.tsw.WriteLiterally("queueMicrotask(async () => ")
210
- } else {
211
- c.tsw.WriteLiterally("queueMicrotask(() => ")
212
- }
213
-
214
- // Compile the function literal's body directly
215
- if err := c.WriteStmtBlock(fun.Body, true); err != nil {
216
- return fmt.Errorf("failed to write goroutine function literal body: %w", err)
217
- }
218
-
219
- c.tsw.WriteLine(")") // Close the queueMicrotask statement
220
-
221
- case *ast.Ident:
222
- // Handle named functions: go namedFunc(args)
223
- // Get the object for this function
224
- obj := c.objectOfIdent(fun)
225
- if obj == nil {
226
- return errors.Errorf("could not find object for function: %s", fun.Name)
227
- }
228
-
229
- // Check if the function is async
230
- isAsync := c.analysis.IsAsyncFunc(obj)
231
- if isAsync {
232
- c.tsw.WriteLiterally("queueMicrotask(async () => {")
233
- } else {
234
- c.tsw.WriteLiterally("queueMicrotask(() => {")
235
- }
236
-
237
- c.tsw.Indent(1)
238
- c.tsw.WriteLine("")
239
-
240
- // Write the function call, using await if the function is async
241
- if isAsync {
242
- c.tsw.WriteLiterally("await ")
243
- }
244
-
245
- // Write the function name
246
- c.tsw.WriteLiterally(fun.Name)
247
-
248
- // Write the function arguments
249
- c.tsw.WriteLiterally("(")
250
- for i, arg := range callExpr.Args {
251
- if i != 0 {
252
- c.tsw.WriteLiterally(", ")
253
- }
254
- if err := c.WriteValueExpr(arg); err != nil {
255
- return fmt.Errorf("failed to write argument %d in goroutine function call: %w", i, err)
256
- }
257
- }
258
- c.tsw.WriteLiterally(")")
259
- c.tsw.WriteLine("")
260
-
261
- c.tsw.Indent(-1)
262
- c.tsw.WriteLine("})") // Close the queueMicrotask callback and the statement
263
- case *ast.SelectorExpr:
264
- // Handle selector expressions: go x.Method(args)
265
- // Get the object for the selected method
266
- obj := c.objectOfIdent(fun.Sel)
267
- if obj == nil {
268
- return errors.Errorf("could not find object for selected method: %s", fun.Sel.Name)
269
- }
270
-
271
- // Check if the function is async
272
- isAsync := c.analysis.IsAsyncFunc(obj)
273
- if isAsync {
274
- c.tsw.WriteLiterally("queueMicrotask(async () => {")
275
- } else {
276
- c.tsw.WriteLiterally("queueMicrotask(() => {")
277
- }
278
-
279
- c.tsw.Indent(1)
280
- c.tsw.WriteLine("")
281
-
282
- // Write the function call, using await if the function is async
283
- if isAsync {
284
- c.tsw.WriteLiterally("await ")
285
- }
286
-
287
- // Write the selector expression (e.g., f.Bar)
288
- // Note: callExpr.Fun is the *ast.SelectorExpr itself
289
- // For method calls, we need to add null assertion since Go would panic on nil receiver
290
- if selectorExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
291
- if err := c.WriteValueExpr(selectorExpr.X); err != nil {
292
- return fmt.Errorf("failed to write selector base expression in goroutine: %w", err)
293
- }
294
- // Add null assertion for method calls - Go would panic if receiver is nil
295
- c.tsw.WriteLiterally("!.")
296
- c.WriteIdent(selectorExpr.Sel, true)
297
- } else {
298
- if err := c.WriteValueExpr(callExpr.Fun); err != nil {
299
- return fmt.Errorf("failed to write selector expression in goroutine: %w", err)
300
- }
301
- }
302
-
303
- // Write the function arguments
304
- c.tsw.WriteLiterally("(")
305
- for i, arg := range callExpr.Args {
306
- if i != 0 {
307
- c.tsw.WriteLiterally(", ")
308
- }
309
- if err := c.WriteValueExpr(arg); err != nil {
310
- return fmt.Errorf("failed to write argument %d in goroutine selector function call: %w", i, err)
311
- }
312
- }
313
- c.tsw.WriteLiterally(")")
314
- c.tsw.WriteLine("")
315
-
316
- c.tsw.Indent(-1)
317
- c.tsw.WriteLine("})") // Close the queueMicrotask callback and the statement
318
- case *ast.TypeAssertExpr:
319
- // Handle type assertion expressions: go x.(func())()
320
- // We assume this is always synchronous (no async function returned by type assertion)
321
- c.tsw.WriteLiterally("queueMicrotask(() => {")
322
-
323
- c.tsw.Indent(1)
324
- c.tsw.WriteLine("")
325
-
326
- // Write the type assertion call
327
- if err := c.WriteTypeAssertExpr(fun); err != nil {
328
- return fmt.Errorf("failed to write type assertion expression in goroutine: %w", err)
329
- }
330
-
331
- // Add non-null assertion since mustTypeAssert throws on failure rather than returning null
332
- c.tsw.WriteLiterally("!")
333
-
334
- // Write the function arguments
335
- c.tsw.WriteLiterally("(")
336
- for i, arg := range callExpr.Args {
337
- if i != 0 {
338
- c.tsw.WriteLiterally(", ")
339
- }
340
- if err := c.WriteValueExpr(arg); err != nil {
341
- return fmt.Errorf("failed to write argument %d in goroutine type assertion function call: %w", i, err)
342
- }
343
- }
344
- c.tsw.WriteLiterally(")")
345
- c.tsw.WriteLine("")
346
-
347
- c.tsw.Indent(-1)
348
- c.tsw.WriteLine("})") // Close the queueMicrotask callback and the statement
349
- default:
350
- return errors.Errorf("unhandled goroutine function type: %T", callExpr.Fun)
351
- }
352
- return nil
353
- }
354
-
355
- // WriteStmtExpr translates a Go expression statement (`ast.ExprStmt`) into
356
- // its TypeScript equivalent. An expression statement in Go is an expression
357
- // evaluated for its side effects (e.g., a function call).
358
- // - A special case is a simple channel receive used as a statement (`<-ch`). This
359
- // is translated to `await ch_ts.receive();` (the value is discarded).
360
- // - For other expression statements, the underlying expression `exp.X` is translated
361
- // using `WriteValueExpr`.
362
- // - It attempts to preserve inline comments associated with the expression statement
363
- // or its underlying expression `exp.X`.
364
- //
365
- // The translated statement is terminated with a newline.
366
- func (c *GoToTSCompiler) WriteStmtExpr(exp *ast.ExprStmt) error {
367
- // Handle simple channel receive used as a statement (<-ch)
368
- if unaryExpr, ok := exp.X.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
369
- // Translate <-ch to await $.chanRecv(ch)
370
- c.tsw.WriteLiterally("await $.chanRecv(")
371
- if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
372
- return fmt.Errorf("failed to write channel expression in receive statement: %w", err)
373
- }
374
- c.tsw.WriteLiterally(")") // Use chanRecv() as the value is discarded
375
- c.tsw.WriteLine("")
376
- return nil
377
- }
378
-
379
- // Defensive semicolon: if the expression will start with '(' in TypeScript,
380
- // prepend a semicolon to prevent JavaScript from treating the previous line
381
- // as a function call. This happens when:
382
- // 1. CallExpr where Fun itself will be parenthesized (e.g., (await fn())())
383
- // 2. Array/slice literals starting with '['
384
- if c.needsDefensiveSemicolon(exp.X) {
385
- c.tsw.WriteLiterally(";")
386
- }
387
-
388
- // Handle other expression statements
389
- if err := c.WriteValueExpr(exp.X); err != nil { // Expression statement evaluates a value
390
- return err
391
- }
392
-
393
- // Handle potential inline comment for ExprStmt
394
- inlineCommentWritten := false
395
- if c.pkg != nil && c.pkg.Fset != nil && exp.End().IsValid() {
396
- if file := c.pkg.Fset.File(exp.End()); file != nil {
397
- endLine := file.Line(exp.End())
398
- // Check comments associated *directly* with the ExprStmt node
399
- for _, cg := range c.analysis.Cmap[exp] {
400
- if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
401
- commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
402
- c.tsw.WriteLiterally(" // " + commentText)
403
- inlineCommentWritten = true
404
- break
405
- }
406
- }
407
- // Also check comments associated with the underlying expression X
408
- // This might be necessary if the comment map links it to X instead of ExprStmt
409
- if !inlineCommentWritten {
410
- for _, cg := range c.analysis.Cmap[exp.X] {
411
- if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
412
- commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
413
- c.tsw.WriteLiterally(" // " + commentText)
414
- inlineCommentWritten = true //nolint:ineffassign
415
- break
416
- }
417
- }
418
- }
419
- }
420
- }
421
-
422
- // Add semicolon according to design doc (omit semicolons) - REMOVED semicolon
423
- c.tsw.WriteLine("") // Finish with a newline
424
- return nil
425
- }
426
-
427
- // WriteStmtSend translates a Go channel send statement (`ast.SendStmt`),
428
- // which has the form `ch <- value`, into its asynchronous TypeScript equivalent.
429
- // The translation is `await ch_ts.send(value_ts)`.
430
- // Both the channel expression (`exp.Chan`) and the value expression (`exp.Value`)
431
- // are translated using `WriteValueExpr`. The `await` keyword is used because
432
- // channel send operations are asynchronous in the TypeScript model.
433
- // The statement is terminated with a newline.
434
- func (c *GoToTSCompiler) WriteStmtSend(exp *ast.SendStmt) error {
435
- // Translate ch <- value to await $.chanSend(ch, value)
436
- c.tsw.WriteLiterally("await $.chanSend(")
437
- if err := c.WriteValueExpr(exp.Chan); err != nil { // The channel expression
438
- return fmt.Errorf("failed to write channel expression in send statement: %w", err)
439
- }
440
- c.tsw.WriteLiterally(", ")
441
- if err := c.WriteValueExpr(exp.Value); err != nil { // The value expression
442
- return fmt.Errorf("failed to write value expression in send statement: %w", err)
443
- }
444
- c.tsw.WriteLiterally(")")
445
- c.tsw.WriteLine("") // Add newline after the statement
446
- return nil
447
- }
448
-
449
- // WriteStmtIf translates a Go `if` statement (`ast.IfStmt`) into its
450
- // TypeScript equivalent.
451
- // - If the Go `if` has an initialization statement (`exp.Init`), it's wrapped
452
- // in a TypeScript block `{...}` before the `if` keyword, and the initializer
453
- // is translated within this block. This emulates Go's `if` statement scope.
454
- // - The condition (`exp.Cond`) is translated using `WriteValueExpr` and placed
455
- // within parentheses `(...)`.
456
- // - The `if` body (`exp.Body`) is translated as a block statement using
457
- // `WriteStmtBlock`. If `exp.Body` is nil, an empty block `{}` is written.
458
- // - The `else` branch (`exp.Else`) is handled:
459
- // - If `exp.Else` is a block statement (`ast.BlockStmt`), it's written as `else { ...body_ts... }`.
460
- // - If `exp.Else` is another `if` statement (`ast.IfStmt`), it's written as `else if (...) ...`,
461
- // recursively calling `WriteStmtIf`.
462
- //
463
- // The function aims to produce idiomatic TypeScript `if/else if/else` structures.
464
- func (c *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
465
- // Handle optional initialization statement
466
- if exp.Init != nil {
467
- shadowingInfo := c.analysis.GetShadowingInfo(exp)
468
-
469
- // Write temp variables if shadowing detected
470
- if shadowingInfo != nil {
471
- c.writeShadowingTempVars(shadowingInfo)
472
- }
473
-
474
- c.tsw.WriteLine("{")
475
- c.tsw.Indent(1)
476
-
477
- // Write initialization with shadowing support if needed
478
- if assignStmt, ok := exp.Init.(*ast.AssignStmt); ok && shadowingInfo != nil {
479
- if err := c.writeAssignmentWithShadowing(assignStmt, shadowingInfo); err != nil {
480
- return fmt.Errorf("failed to write shadowed assignment in if init: %w", err)
481
- }
482
- } else {
483
- if err := c.WriteStmt(exp.Init); err != nil {
484
- return fmt.Errorf("failed to write if initialization statement: %w", err)
485
- }
486
- }
487
-
488
- // Write the if statement itself within the initialization block
489
- c.tsw.WriteLiterally("if (")
490
- if err := c.WriteValueExpr(exp.Cond); err != nil {
491
- return err
492
- }
493
- c.tsw.WriteLiterally(") ")
494
-
495
- if err := c.writeIfBody(exp); err != nil {
496
- return err
497
- }
498
-
499
- c.tsw.Indent(-1)
500
- c.tsw.WriteLine("}")
501
- return nil
502
- }
503
-
504
- // No initialization statement, write if statement normally
505
- c.tsw.WriteLiterally("if (")
506
- if err := c.WriteValueExpr(exp.Cond); err != nil {
507
- return err
508
- }
509
- c.tsw.WriteLiterally(") ")
510
-
511
- return c.writeIfBody(exp)
512
- }
513
-
514
- // writeIfBody writes the if body and optional else clause, handling newline suppression.
515
- func (c *GoToTSCompiler) writeIfBody(exp *ast.IfStmt) error {
516
- hasElse := exp.Else != nil
517
- if err := c.WriteStmtBlock(exp.Body, hasElse); err != nil {
518
- return err
519
- }
520
-
521
- if hasElse {
522
- c.tsw.WriteLiterally(" else ")
523
- switch elseStmt := exp.Else.(type) {
524
- case *ast.BlockStmt:
525
- if err := c.WriteStmtBlock(elseStmt, false); err != nil {
526
- return err
527
- }
528
- case *ast.IfStmt:
529
- if err := c.WriteStmtIf(elseStmt); err != nil {
530
- return err
531
- }
532
- default:
533
- if err := c.WriteStmt(exp.Else); err != nil {
534
- return err
535
- }
536
- }
537
- }
538
-
539
- return nil
540
- }
541
-
542
- // WriteStmtReturn translates a Go `return` statement (`ast.ReturnStmt`) into
543
- // its TypeScript equivalent.
544
- // - It writes the `return` keyword.
545
- // - If there are multiple return values (`len(exp.Results) > 1`), the translated
546
- // results are wrapped in a TypeScript array literal `[...]`.
547
- // - Each result expression in `exp.Results` is translated using `WriteValueExpr`.
548
- // - If there are no results, it simply writes `return`.
549
- //
550
- // The statement is terminated with a newline.
551
- func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
552
- c.tsw.WriteLiterally("return ")
553
-
554
- // Check if it's a bare named return
555
- nodeInfo := c.analysis.NodeData[exp]
556
- if nodeInfo != nil && nodeInfo.IsBareReturn {
557
- var namedReturns []string
558
- if funcInfo := c.analysis.GetFunctionInfoFromContext(nodeInfo, c.pkg); funcInfo != nil {
559
- namedReturns = funcInfo.NamedReturns
560
- }
561
-
562
- if len(namedReturns) == 1 {
563
- // Single named return - don't wrap in array
564
- c.tsw.WriteLiterally(c.sanitizeIdentifier(namedReturns[0]))
565
- } else if len(namedReturns) > 1 {
566
- // Multiple named returns - wrap in array
567
- c.tsw.WriteLiterally("[")
568
- for i, name := range namedReturns {
569
- if i != 0 {
570
- c.tsw.WriteLiterally(", ")
571
- }
572
- c.tsw.WriteLiterally(c.sanitizeIdentifier(name))
573
- }
574
- c.tsw.WriteLiterally("]")
575
- }
576
- } else {
577
- // Handle explicit return values
578
- if len(exp.Results) > 1 {
579
- c.tsw.WriteLiterally("[")
580
- }
581
- for i, res := range exp.Results {
582
- if i != 0 {
583
- c.tsw.WriteLiterally(", ")
584
- }
585
- // Special handling for nil in generic function returns
586
- if ident, isIdent := res.(*ast.Ident); isIdent && ident.Name == "nil" {
587
- // Check if we're in a generic function and get the return type
588
- if nodeInfo := c.analysis.NodeData[exp]; nodeInfo != nil && nodeInfo.EnclosingFuncDecl != nil {
589
- funcDecl := nodeInfo.EnclosingFuncDecl
590
- if funcDecl.Type.Results != nil && i < len(funcDecl.Type.Results.List) {
591
- // Get the return type for this result position
592
- resultField := funcDecl.Type.Results.List[i]
593
- if resultType := c.pkg.TypesInfo.TypeOf(resultField.Type); resultType != nil {
594
- if _, isTypeParam := resultType.(*types.TypeParam); isTypeParam {
595
- // This is a generic type parameter, use type assertion with unknown intermediate
596
- c.tsw.WriteLiterally("null as unknown as ")
597
- c.WriteGoType(resultType, GoTypeContextFunctionReturn)
598
- continue
599
- }
600
- }
601
- }
602
- }
603
- }
604
-
605
- // Special handling for primitive types that implement error interface
606
- if c.writePrimitiveErrorWrapperIfNeeded(exp, res, i) {
607
- continue
608
- }
609
-
610
- if err := c.WriteValueExpr(res); err != nil { // Return results are values
611
- return err
612
- }
613
- }
614
- if len(exp.Results) > 1 {
615
- c.tsw.WriteLiterally("]")
616
- }
617
- }
618
- c.tsw.WriteLine("")
619
- return nil
620
- }
621
-
622
- // writePrimitiveErrorWrapperIfNeeded checks if a return value is a primitive type
623
- // that implements the error interface, and if so, wraps it with $.wrapPrimitiveError.
624
- // Returns true if the wrapper was written, false otherwise.
625
- func (c *GoToTSCompiler) writePrimitiveErrorWrapperIfNeeded(retStmt *ast.ReturnStmt, res ast.Expr, resultIndex int) bool {
626
- // Get the expected return type for this position
627
- nodeInfo := c.analysis.NodeData[retStmt]
628
- if nodeInfo == nil || nodeInfo.EnclosingFuncDecl == nil {
629
- return false
630
- }
631
-
632
- funcDecl := nodeInfo.EnclosingFuncDecl
633
- if funcDecl.Type.Results == nil {
634
- return false
635
- }
636
-
637
- // Find the expected return type for this result index
638
- var expectedType types.Type
639
- resultIdx := 0
640
- for _, field := range funcDecl.Type.Results.List {
641
- count := len(field.Names)
642
- if count == 0 {
643
- count = 1
644
- }
645
- for j := 0; j < count; j++ {
646
- if resultIdx == resultIndex {
647
- expectedType = c.pkg.TypesInfo.TypeOf(field.Type)
648
- break
649
- }
650
- resultIdx++
651
- }
652
- if expectedType != nil {
653
- break
654
- }
655
- }
656
-
657
- if expectedType == nil {
658
- return false
659
- }
660
-
661
- // Check if the expected type is the error interface
662
- if iface, ok := expectedType.Underlying().(*types.Interface); !ok || iface.String() != "interface{Error() string}" {
663
- return false
664
- }
665
-
666
- // Get the actual type of the return expression
667
- actualType := c.pkg.TypesInfo.TypeOf(res)
668
- if actualType == nil {
669
- return false
670
- }
671
-
672
- // Check if the actual type is a wrapper type (named type with basic underlying type)
673
- if !c.isWrapperType(actualType) {
674
- return false
675
- }
676
-
677
- // Check if the actual type has an Error() method
678
- if !c.typeHasMethods(actualType, "Error") {
679
- return false
680
- }
681
-
682
- // Get the qualified type name for the Error function
683
- typeName := c.getQualifiedTypeName(actualType)
684
- if typeName == "" {
685
- return false
686
- }
687
-
688
- // Write: $.wrapPrimitiveError(value, TypeName_Error)
689
- c.tsw.WriteLiterally("$.wrapPrimitiveError(")
690
- if err := c.WriteValueExpr(res); err != nil {
691
- return false
692
- }
693
- c.tsw.WriteLiterally(", ")
694
- c.tsw.WriteLiterally(typeName)
695
- c.tsw.WriteLiterally("_Error)")
696
-
697
- return true
698
- }
699
-
700
- // WriteStmtBlock translates a Go block statement (`ast.BlockStmt`), typically
701
- // `{ ...stmts... }`, into its TypeScript equivalent, carefully preserving
702
- // comments and blank lines to maintain code readability and structure.
703
- // - It writes an opening brace `{` and indents.
704
- // - If the analysis (`c.analysis.NeedsDefer`) indicates that the block (or a
705
- // function it's part of) contains `defer` statements, it injects a
706
- // `using __defer = new $.DisposableStack();` (or `AsyncDisposableStack` if
707
- // the context is async or contains async defers) at the beginning of the block.
708
- // This `__defer` stack is used by `WriteStmtDefer` to register cleanup actions.
709
- // - It iterates through the statements (`exp.List`) in the block:
710
- // - Leading comments associated with each statement are written first, with
711
- // blank lines preserved based on original source line numbers.
712
- // - The statement itself is then translated using `WriteStmt`.
713
- // - Inline comments (comments on the same line after a statement) are expected
714
- // to be handled by the individual statement writers (e.g., `WriteStmtExpr`).
715
- // - Trailing comments within the block (after the last statement but before the
716
- // closing brace) are written.
717
- // - Blank lines before the closing brace are preserved.
718
- // - Finally, it unindents and writes the closing brace `}`.
719
- //
720
- // If `suppressNewline` is true, the final newline after the closing brace is omitted
721
- // (used, for example, when an `if` block is followed by an `else`).
722
- func (c *GoToTSCompiler) WriteStmtBlock(exp *ast.BlockStmt, suppressNewline bool) error {
723
- if exp == nil {
724
- c.tsw.WriteLiterally("{}")
725
- if !suppressNewline {
726
- c.tsw.WriteLine("")
727
- }
728
- return nil
729
- }
730
-
731
- // Opening brace
732
- c.tsw.WriteLine("{")
733
- c.tsw.Indent(1)
734
-
735
- // Determine if there is any defer to an async function literal in this block
736
- hasAsyncDefer := false
737
- for _, stmt := range exp.List {
738
- if deferStmt, ok := stmt.(*ast.DeferStmt); ok {
739
- if deferStmt.Call == nil || deferStmt.Call.Fun == nil {
740
- continue
741
- }
742
- if funcLit, ok := deferStmt.Call.Fun.(*ast.FuncLit); ok {
743
- if c.analysis.IsFuncLitAsync(funcLit) {
744
- hasAsyncDefer = true
745
- break
746
- }
747
- } else {
748
- // Check if the deferred call is to an async function
749
- if c.isCallExprAsync(deferStmt.Call) {
750
- hasAsyncDefer = true
751
- break
752
- }
753
- }
754
- }
755
- }
756
-
757
- // Add using statement if needed, considering async function or async defer
758
- if c.analysis.NeedsDefer(exp) {
759
- if c.analysis.IsInAsyncFunction(exp) || hasAsyncDefer {
760
- c.tsw.WriteLine("await using __defer = new $.AsyncDisposableStack();")
761
- } else {
762
- c.tsw.WriteLine("using __defer = new $.DisposableStack();")
763
- }
764
- }
765
-
766
- // Prepare line info
767
- var file *token.File
768
- if c.pkg != nil && c.pkg.Fset != nil && exp.Lbrace.IsValid() {
769
- file = c.pkg.Fset.File(exp.Lbrace)
770
- }
771
-
772
- // writeBlank emits a single blank line if gap > 1
773
- writeBlank := func(prev, curr int) {
774
- if file != nil && prev > 0 && curr > prev+1 {
775
- c.tsw.WriteLine("")
776
- }
777
- }
778
-
779
- // Track last printed line, start at opening brace
780
- lastLine := 0
781
- if file != nil {
782
- lastLine = file.Line(exp.Lbrace)
783
- }
784
-
785
- // 1. For each statement: write its leading comments, blank space, then the stmt
786
- for _, stmt := range exp.List {
787
- // Get statement's end line and position for inline comment check
788
- stmtEndLine := 0
789
- stmtEndPos := token.NoPos
790
- if file != nil && stmt.End().IsValid() {
791
- stmtEndLine = file.Line(stmt.End())
792
- stmtEndPos = stmt.End()
793
- }
794
-
795
- // Process leading comments for stmt
796
- comments := c.analysis.Cmap.Filter(stmt).Comments()
797
- for _, cg := range comments {
798
- // Check if this comment group is an inline comment for the current statement
799
- isInlineComment := false
800
- if file != nil && cg.Pos().IsValid() && stmtEndPos.IsValid() {
801
- commentStartLine := file.Line(cg.Pos())
802
- // Inline if starts on same line as stmt end AND starts after stmt end position
803
- if commentStartLine == stmtEndLine && cg.Pos() > stmtEndPos {
804
- isInlineComment = true
805
- }
806
- }
807
-
808
- // If it's NOT an inline comment for this statement, write it here
809
- if !isInlineComment {
810
- start := 0
811
- if file != nil && cg.Pos().IsValid() {
812
- start = file.Line(cg.Pos())
813
- }
814
- writeBlank(lastLine, start)
815
- c.WriteDoc(cg) // WriteDoc will handle the actual comment text
816
- if file != nil && cg.End().IsValid() {
817
- lastLine = file.Line(cg.End())
818
- }
819
- }
820
- // If it IS an inline comment, skip it. The statement writer will handle it.
821
- }
822
-
823
- // the statement itself
824
- stmtStart := 0
825
- if file != nil && stmt.Pos().IsValid() {
826
- stmtStart = file.Line(stmt.Pos())
827
- }
828
- writeBlank(lastLine, stmtStart)
829
- // Call the specific statement writer (e.g., WriteStmtAssign).
830
- // It is responsible for handling its own inline comment.
831
- if err := c.WriteStmt(stmt); err != nil {
832
- return fmt.Errorf("failed to write statement in block: %w", err)
833
- }
834
-
835
- if file != nil && stmt.End().IsValid() {
836
- // Update lastLine based on the statement's end, *including* potential inline comment handled by WriteStmt*
837
- lastLine = file.Line(stmt.End())
838
- }
839
- }
840
-
841
- // 2. Trailing comments on the block (after last stmt, before closing brace)
842
- trailing := c.analysis.Cmap.Filter(exp).Comments()
843
- for _, cg := range trailing {
844
- start := 0
845
- if file != nil && cg.Pos().IsValid() {
846
- start = file.Line(cg.Pos())
847
- }
848
- // only emit if it follows the last content
849
- if start > lastLine {
850
- writeBlank(lastLine, start)
851
- c.WriteDoc(cg)
852
- if file != nil && cg.End().IsValid() {
853
- lastLine = file.Line(cg.End())
854
- }
855
- }
856
- }
857
-
858
- // 3. Blank lines before closing brace
859
- closing := 0
860
- if file != nil && exp.Rbrace.IsValid() {
861
- closing = file.Line(exp.Rbrace)
862
- }
863
- writeBlank(lastLine, closing)
864
-
865
- // Closing brace
866
- c.tsw.Indent(-1)
867
- c.tsw.WriteLiterally("}")
868
-
869
- if !suppressNewline {
870
- c.tsw.WriteLine("")
871
- }
872
- return nil
873
- }
874
-
875
- // WriteStmtSwitch translates a Go `switch` statement into its TypeScript equivalent.
876
- // - If the Go switch has an initialization statement (`exp.Init`), it's wrapped
877
- // in a TypeScript block `{...}` before the `switch` keyword, and the
878
- // initializer is translated within this block. This emulates Go's switch scope.
879
- // - The switch condition (`exp.Tag`):
880
- // - If `exp.Tag` is present, it's translated using `WriteValueExpr`.
881
- // - If `exp.Tag` is nil (a "tagless" switch, like `switch { case cond1: ... }`),
882
- // it's translated as `switch (true)` in TypeScript.
883
- // - Each case clause (`ast.CaseClause`) in `exp.Body.List` is translated using
884
- // `WriteCaseClause`.
885
- //
886
- // The overall structure is `[optional_init_block] switch (condition_ts) { ...cases_ts... }`.
887
- func (c *GoToTSCompiler) WriteStmtSwitch(exp *ast.SwitchStmt) error {
888
- // Handle optional initialization statement
889
- if exp.Init != nil {
890
- c.tsw.WriteLiterally("{")
891
- c.tsw.Indent(1)
892
- if err := c.WriteStmt(exp.Init); err != nil {
893
- return fmt.Errorf("failed to write switch initialization statement: %w", err)
894
- }
895
- defer func() {
896
- c.tsw.Indent(-1)
897
- c.tsw.WriteLiterally("}")
898
- }()
899
- }
900
-
901
- c.tsw.WriteLiterally("switch (")
902
- // Handle the switch tag (the expression being switched on)
903
- if exp.Tag != nil {
904
- if err := c.WriteValueExpr(exp.Tag); err != nil {
905
- return fmt.Errorf("failed to write switch tag expression: %w", err)
906
- }
907
- } else {
908
- c.tsw.WriteLiterally("true") // Write 'true' for switch without expression
909
- }
910
- c.tsw.WriteLiterally(") {")
911
- c.tsw.WriteLine("")
912
- c.tsw.Indent(1)
913
-
914
- // Handle case clauses
915
- for _, stmt := range exp.Body.List {
916
- if caseClause, ok := stmt.(*ast.CaseClause); ok {
917
- if err := c.WriteCaseClause(caseClause); err != nil {
918
- return fmt.Errorf("failed to write case clause in switch statement: %w", err)
919
- }
920
- } else {
921
- return fmt.Errorf("unhandled statement in switch body: %T", stmt)
922
- }
923
- }
924
-
925
- c.tsw.Indent(-1)
926
- c.tsw.WriteLine("}")
927
- return nil
928
- }
929
-
930
- // WriteStmtDefer translates a Go `defer` statement into TypeScript code that
931
- // utilizes a disposable stack (`$.DisposableStack` or `$.AsyncDisposableStack`).
932
- // The Go `defer` semantics (LIFO execution at function exit) are emulated by
933
- // registering a cleanup function with this stack.
934
- // - `defer funcCall()` becomes `__defer.defer(() => { funcCall_ts(); });`.
935
- // - `defer func(){ ...body... }()` (an immediately-invoked function literal, IIFL)
936
- // has its body inlined: `__defer.defer(() => { ...body_ts... });`.
937
- // - If the deferred call is to an async function or an async function literal
938
- // (determined by `c.analysis.IsInAsyncFunctionMap`), the registered callback
939
- // is marked `async`: `__defer.defer(async () => { ... });`.
940
- //
941
- // The `__defer` variable is assumed to be declared at the beginning of the
942
- // function scope (see `WriteStmtBlock` or `WriteFuncDeclAsMethod`) using
943
- // `await using __defer = new $.AsyncDisposableStack();` for async functions/contexts
944
- // or `using __defer = new $.DisposableStack();` for sync contexts.
945
- func (c *GoToTSCompiler) WriteStmtDefer(exp *ast.DeferStmt) error {
946
- // Determine if the deferred call is to an async function literal using analysis
947
- isAsyncDeferred := false
948
- if funcLit, ok := exp.Call.Fun.(*ast.FuncLit); ok {
949
- isAsyncDeferred = c.analysis.IsFuncLitAsync(funcLit)
950
- } else {
951
- // Check if the deferred call is to an async function
952
- isAsyncDeferred = c.isCallExprAsync(exp.Call)
953
- }
954
-
955
- // Set async prefix based on pre-computed async status
956
- asyncPrefix := ""
957
- if isAsyncDeferred {
958
- asyncPrefix = "async "
959
- }
960
-
961
- // Set stack variable based on whether we are in an async function
962
- stackVar := "__defer"
963
- c.tsw.WriteLiterallyf("%s.defer(%s() => {", stackVar, asyncPrefix)
964
- c.tsw.Indent(1)
965
- c.tsw.WriteLine("")
966
-
967
- // Write the deferred call or inline the body when it's an immediately-invoked
968
- // function literal (defer func(){ ... }()).
969
- if funcLit, ok := exp.Call.Fun.(*ast.FuncLit); ok && len(exp.Call.Args) == 0 {
970
- // Inline the function literal's body to avoid nested arrow invocation.
971
- for _, stmt := range funcLit.Body.List {
972
- if err := c.WriteStmt(stmt); err != nil {
973
- return fmt.Errorf("failed to write statement in deferred function body: %w", err)
974
- }
975
- }
976
- } else {
977
- // Write the call expression as-is.
978
- if err := c.WriteValueExpr(exp.Call); err != nil {
979
- return fmt.Errorf("failed to write deferred call: %w", err)
980
- }
981
- c.tsw.WriteLine("")
982
- }
983
-
984
- c.tsw.Indent(-1)
985
- c.tsw.WriteLine("});")
986
-
987
- return nil
988
- }
989
-
990
- // WriteStmtLabeled handles labeled statements (ast.LabeledStmt), such as "label: statement".
991
- // In TypeScript, labels cannot be used with variable declarations, so we need to handle this case specially.
992
- func (c *GoToTSCompiler) WriteStmtLabeled(stmt *ast.LabeledStmt) error {
993
- // Check if the labeled statement is a declaration statement or assignment with :=
994
- needsBlock := false
995
- if _, ok := stmt.Stmt.(*ast.DeclStmt); ok {
996
- needsBlock = true
997
- } else if assignStmt, ok := stmt.Stmt.(*ast.AssignStmt); ok && assignStmt.Tok == token.DEFINE {
998
- // Assignment with := is also a declaration and needs special handling
999
- needsBlock = true
1000
- }
1001
-
1002
- if needsBlock {
1003
- // For declaration statements and := assignments, we need to put the label on a separate line
1004
- // because TypeScript doesn't allow labels with declarations
1005
- c.tsw.WriteLiterally(stmt.Label.Name)
1006
- c.tsw.WriteLine(": {")
1007
- c.tsw.Indent(1)
1008
-
1009
- // Write the statement without the label
1010
- if err := c.WriteStmt(stmt.Stmt); err != nil {
1011
- return fmt.Errorf("failed to write labeled declaration/assignment statement: %w", err)
1012
- }
1013
-
1014
- c.tsw.Indent(-1)
1015
- c.tsw.WriteLine("}")
1016
- } else {
1017
- // For non-declaration statements, write the label normally
1018
- c.tsw.WriteLiterally(stmt.Label.Name)
1019
- c.tsw.WriteLiterally(": ")
1020
-
1021
- // Write the labeled statement
1022
- if err := c.WriteStmt(stmt.Stmt); err != nil {
1023
- return fmt.Errorf("failed to write labeled statement: %w", err)
1024
- }
1025
- }
1026
-
1027
- return nil
1028
- }
1029
-
1030
- // ============ Variable Shadowing Support ============
1031
-
1032
- // writeShadowingTempVars creates temporary variables for shadowed variables
1033
- func (c *GoToTSCompiler) writeShadowingTempVars(shadowingInfo *ShadowingInfo) {
1034
- for varName, tempVarName := range shadowingInfo.TempVariables {
1035
- c.tsw.WriteLiterally("const ")
1036
- c.tsw.WriteLiterally(tempVarName)
1037
- c.tsw.WriteLiterally(" = ")
1038
-
1039
- // Check if this is a built-in function and handle it directly
1040
- if c.isBuiltinFunction(varName) {
1041
- c.tsw.WriteLiterally("$.")
1042
- c.tsw.WriteLiterally(varName)
1043
- } else {
1044
- // Get the original object for this shadowed variable
1045
- if originalObj, exists := shadowingInfo.ShadowedVariables[varName]; exists {
1046
- // Create an identifier with the original name and use WriteValueExpr to properly resolve it
1047
- originalIdent := &ast.Ident{Name: varName}
1048
- // Set the identifier in the Uses map so WriteValueExpr can find the object
1049
- c.pkg.TypesInfo.Uses[originalIdent] = originalObj
1050
- c.WriteValueExpr(originalIdent)
1051
- } else {
1052
- // Fallback to literal name if no object found (shouldn't happen in normal cases)
1053
- c.tsw.WriteLiterally(varName)
1054
- }
1055
- }
1056
- c.tsw.WriteLine("")
1057
- }
1058
- }
1059
-
1060
- // writeAssignmentWithShadowing writes an assignment statement that has variable shadowing.
1061
- // Handles both regular assignments and type assertions, with optional temp variable creation.
1062
- func (c *GoToTSCompiler) writeAssignmentWithShadowing(stmt *ast.AssignStmt, shadowingInfo *ShadowingInfo) error {
1063
- // Check for type assertion special case
1064
- if len(stmt.Rhs) == 1 && len(stmt.Lhs) == 2 {
1065
- if typeAssert, ok := stmt.Rhs[0].(*ast.TypeAssertExpr); ok {
1066
- return c.writeTypeAssertWithShadowing(stmt, typeAssert, shadowingInfo)
1067
- }
1068
- }
1069
-
1070
- // Regular assignment: write LHS declarations
1071
- firstDecl := true
1072
- for i, lhsExpr := range stmt.Lhs {
1073
- if i > 0 {
1074
- c.tsw.WriteLiterally(", ")
1075
- }
1076
- if ident, ok := lhsExpr.(*ast.Ident); ok {
1077
- if ident.Name != "_" {
1078
- if firstDecl {
1079
- c.tsw.WriteLiterally("let ")
1080
- firstDecl = false
1081
- }
1082
- c.WriteIdent(ident, false)
1083
- } else {
1084
- c.tsw.WriteLiterally("_")
1085
- }
1086
- } else {
1087
- if err := c.WriteValueExpr(lhsExpr); err != nil {
1088
- return err
1089
- }
1090
- }
1091
- }
1092
-
1093
- // Write RHS with shadowed variable substitution
1094
- c.tsw.WriteLiterally(" = ")
1095
- for i, rhsExpr := range stmt.Rhs {
1096
- if i > 0 {
1097
- c.tsw.WriteLiterally(", ")
1098
- }
1099
- if err := c.substituteExprForShadowing(rhsExpr, shadowingInfo); err != nil {
1100
- return err
1101
- }
1102
- }
1103
- c.tsw.WriteLine("")
1104
- return nil
1105
- }
1106
-
1107
- // writeTypeAssertWithShadowing writes a type assertion assignment (v, ok := x.(T)) with shadowing support
1108
- func (c *GoToTSCompiler) writeTypeAssertWithShadowing(stmt *ast.AssignStmt, typeAssert *ast.TypeAssertExpr, shadowingInfo *ShadowingInfo) error {
1109
- valueIdent, valueIsIdent := stmt.Lhs[0].(*ast.Ident)
1110
- okIdent, okIsIdent := stmt.Lhs[1].(*ast.Ident)
1111
-
1112
- if !valueIsIdent || !okIsIdent {
1113
- return fmt.Errorf("type assertion LHS must be identifiers")
1114
- }
1115
-
1116
- valueName := valueIdent.Name
1117
- okName := okIdent.Name
1118
- valueIsBlank := valueName == "_"
1119
- okIsBlank := okName == "_"
1120
-
1121
- if valueIsBlank && okIsBlank {
1122
- // Both blank, evaluate RHS for side effects only
1123
- if err := c.substituteExprForShadowing(typeAssert.X, shadowingInfo); err != nil {
1124
- return err
1125
- }
1126
- c.tsw.WriteLine("")
1127
- return nil
1128
- }
1129
-
1130
- // Destructure into value and ok
1131
- c.tsw.WriteLiterally("let { ")
1132
- var parts []string
1133
- if !valueIsBlank {
1134
- parts = append(parts, "value: "+valueName)
1135
- }
1136
- if !okIsBlank {
1137
- parts = append(parts, "ok: "+okName)
1138
- }
1139
- c.tsw.WriteLiterally(strings.Join(parts, ", "))
1140
- c.tsw.WriteLiterally(" } = $.typeAssert<")
1141
- c.WriteTypeExpr(typeAssert.Type)
1142
- c.tsw.WriteLiterally(">(")
1143
- if err := c.substituteExprForShadowing(typeAssert.X, shadowingInfo); err != nil {
1144
- return err
1145
- }
1146
- c.tsw.WriteLiterally(", ")
1147
- c.writeTypeDescription(typeAssert.Type)
1148
- c.tsw.WriteLiterally(")")
1149
- c.tsw.WriteLine("")
1150
- return nil
1151
- }
1152
-
1153
- // substituteExprForShadowing writes an expression, replacing shadowed variables with temporary variables
1154
- func (c *GoToTSCompiler) substituteExprForShadowing(expr ast.Expr, shadowingInfo *ShadowingInfo) error {
1155
- switch e := expr.(type) {
1156
- case *ast.Ident:
1157
- // Check if this identifier is a shadowed variable
1158
- if tempVar, isShadowed := shadowingInfo.TempVariables[e.Name]; isShadowed {
1159
- // Use the temporary variable instead
1160
- c.tsw.WriteLiterally(tempVar)
1161
- } else {
1162
- // Use the original identifier
1163
- c.WriteIdent(e, true)
1164
- }
1165
- return nil
1166
-
1167
- case *ast.CallExpr:
1168
- // Handle function calls - replace identifiers in arguments with temp variables
1169
- if err := c.substituteExprForShadowing(e.Fun, shadowingInfo); err != nil {
1170
- return err
1171
- }
1172
-
1173
- // Add non-null assertion for function calls (same logic as WriteCallExpr)
1174
- c.addNonNullAssertion(e.Fun)
1175
-
1176
- c.tsw.WriteLiterally("(")
1177
- for i, arg := range e.Args {
1178
- if i > 0 {
1179
- c.tsw.WriteLiterally(", ")
1180
- }
1181
- if err := c.substituteExprForShadowing(arg, shadowingInfo); err != nil {
1182
- return err
1183
- }
1184
- }
1185
- c.tsw.WriteLiterally(")")
1186
- return nil
1187
-
1188
- case *ast.SelectorExpr:
1189
- // Handle selector expressions (e.g., obj.Method)
1190
- if err := c.substituteExprForShadowing(e.X, shadowingInfo); err != nil {
1191
- return err
1192
- }
1193
- c.tsw.WriteLiterally(".")
1194
- c.WriteIdent(e.Sel, true)
1195
- return nil
1196
-
1197
- case *ast.IndexExpr:
1198
- // Handle index expressions (e.g., arr[i])
1199
- if err := c.substituteExprForShadowing(e.X, shadowingInfo); err != nil {
1200
- return err
1201
- }
1202
- c.tsw.WriteLiterally("[")
1203
- if err := c.substituteExprForShadowing(e.Index, shadowingInfo); err != nil {
1204
- return err
1205
- }
1206
- c.tsw.WriteLiterally("]")
1207
- return nil
1208
-
1209
- case *ast.UnaryExpr:
1210
- // Handle unary expressions (e.g., &x, -x)
1211
- c.tsw.WriteLiterally(e.Op.String())
1212
- return c.substituteExprForShadowing(e.X, shadowingInfo)
1213
-
1214
- case *ast.BinaryExpr:
1215
- // Handle binary expressions (e.g., x + y)
1216
- if err := c.substituteExprForShadowing(e.X, shadowingInfo); err != nil {
1217
- return err
1218
- }
1219
- c.tsw.WriteLiterally(" ")
1220
- c.tsw.WriteLiterally(e.Op.String())
1221
- c.tsw.WriteLiterally(" ")
1222
- return c.substituteExprForShadowing(e.Y, shadowingInfo)
1223
-
1224
- case *ast.ParenExpr:
1225
- // Handle parenthesized expressions
1226
- c.tsw.WriteLiterally("(")
1227
- if err := c.substituteExprForShadowing(e.X, shadowingInfo); err != nil {
1228
- return err
1229
- }
1230
- c.tsw.WriteLiterally(")")
1231
- return nil
1232
-
1233
- default:
1234
- // For other expression types, fall back to normal WriteValueExpr
1235
- return c.WriteValueExpr(expr)
1236
- }
1237
- }
1238
-
1239
- // isBuiltinFunction checks if the given name is a Go built-in function
1240
- func (c *GoToTSCompiler) isBuiltinFunction(name string) bool {
1241
- return builtinFunctions[name]
1242
- }
1243
-
1244
- // needsDefensiveSemicolon determines if an expression will generate TypeScript
1245
- // code starting with '(' or '[', which would require a defensive semicolon to
1246
- // prevent JavaScript from treating the previous line as a function call.
1247
- func (c *GoToTSCompiler) needsDefensiveSemicolon(expr ast.Expr) bool {
1248
- switch e := expr.(type) {
1249
- case *ast.CallExpr:
1250
- // Check if the function being called will be parenthesized
1251
- // This happens when Fun is itself a CallExpr, TypeAssertExpr, or other complex expression
1252
- switch e.Fun.(type) {
1253
- case *ast.CallExpr:
1254
- // (fn())() - needs defensive semicolon
1255
- return true
1256
- case *ast.TypeAssertExpr:
1257
- // (x.(T))() - needs defensive semicolon
1258
- return true
1259
- case *ast.IndexExpr:
1260
- // Could generate (arr[i])() if indexed result is called
1261
- // But typically doesn't need defensive semicolon as arr[i]() is fine
1262
- return false
1263
- case *ast.ParenExpr:
1264
- // Already parenthesized - needs defensive semicolon
1265
- return true
1266
- }
1267
- case *ast.CompositeLit:
1268
- // Array/slice literals start with '['
1269
- if _, isArray := e.Type.(*ast.ArrayType); isArray {
1270
- return true
1271
- }
1272
- case *ast.ParenExpr:
1273
- // Parenthesized expressions start with '('
1274
- return true
1275
- }
1276
- return false
1277
- }
1278
-
1279
- // isTerminatingStmt checks if a statement terminates control flow, meaning
1280
- // code after it would be unreachable. This includes return, panic, continue,
1281
- // break, goto, and fallthrough statements.
1282
- func isTerminatingStmt(stmt ast.Stmt) bool {
1283
- switch s := stmt.(type) {
1284
- case *ast.ReturnStmt:
1285
- return true
1286
- case *ast.BranchStmt:
1287
- // break, continue, goto, fallthrough all terminate the current block
1288
- return true
1289
- case *ast.ExprStmt:
1290
- // Check for panic() calls
1291
- if call, ok := s.X.(*ast.CallExpr); ok {
1292
- if ident, ok := call.Fun.(*ast.Ident); ok {
1293
- if ident.Name == "panic" {
1294
- return true
1295
- }
1296
- }
1297
- }
1298
- }
1299
- return false
1300
- }
1301
-
1302
- // endsWithTerminatingStmt checks if a statement list ends with a terminating statement.
1303
- func endsWithTerminatingStmt(stmts []ast.Stmt) bool {
1304
- if len(stmts) == 0 {
1305
- return false
1306
- }
1307
- return isTerminatingStmt(stmts[len(stmts)-1])
1308
- }