goscript 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,792 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/token"
7
+ "strings"
8
+
9
+ "github.com/pkg/errors"
10
+ )
11
+
12
+ // WriteStmt is a central dispatcher function that translates a Go statement
13
+ // (`ast.Stmt`) into its TypeScript equivalent by calling the appropriate
14
+ // specialized `WriteStmt*` or `write*` method.
15
+ // It handles a wide variety of Go statements:
16
+ // - Block statements (`ast.BlockStmt`): `WriteStmtBlock`.
17
+ // - Assignment statements (`ast.AssignStmt`): `WriteStmtAssign`.
18
+ // - Return statements (`ast.ReturnStmt`): `WriteStmtReturn`.
19
+ // - Defer statements (`ast.DeferStmt`): `WriteStmtDefer`.
20
+ // - If statements (`ast.IfStmt`): `WriteStmtIf`.
21
+ // - Expression statements (`ast.ExprStmt`): `WriteStmtExpr`.
22
+ // - Declaration statements (`ast.DeclStmt`): `WriteStmtDecl`.
23
+ // - For statements (`ast.ForStmt`): `WriteStmtFor`.
24
+ // - Range statements (`ast.RangeStmt`): `WriteStmtRange`.
25
+ // - Switch statements (`ast.SwitchStmt`): `WriteStmtSwitch`.
26
+ // - Increment/decrement statements (`ast.IncDecStmt`): `WriteStmtIncDec`.
27
+ // - Send statements (`ast.SendStmt`): `WriteStmtSend`.
28
+ // - Go statements (`ast.GoStmt`): `WriteStmtGo`.
29
+ // - Select statements (`ast.SelectStmt`): `WriteStmtSelect`.
30
+ // - Branch statements (`ast.BranchStmt`): `WriteStmtBranch`.
31
+ //
32
+ // If an unknown statement type is encountered, it returns an error.
33
+ func (c *GoToTSCompiler) WriteStmt(a ast.Stmt) error {
34
+ switch exp := a.(type) {
35
+ case *ast.BlockStmt:
36
+ if err := c.WriteStmtBlock(exp, false); err != nil {
37
+ return fmt.Errorf("failed to write block statement: %w", err)
38
+ }
39
+ case *ast.AssignStmt:
40
+ // special case: if the left side of the assign has () we need a ; to prepend the line
41
+ // ;(myStruct!.value).MyInt = 5
42
+ // otherwise typescript assumes it is a function call
43
+ if len(exp.Lhs) == 1 {
44
+ if lhsSel, ok := exp.Lhs[0].(*ast.SelectorExpr); ok {
45
+ if _, ok := lhsSel.X.(*ast.ParenExpr); ok {
46
+ c.tsw.WriteLiterally(";")
47
+ }
48
+ }
49
+ }
50
+
51
+ if err := c.WriteStmtAssign(exp); err != nil {
52
+ return fmt.Errorf("failed to write assignment statement: %w", err)
53
+ }
54
+ case *ast.ReturnStmt:
55
+ if err := c.WriteStmtReturn(exp); err != nil {
56
+ return fmt.Errorf("failed to write return statement: %w", err)
57
+ }
58
+ case *ast.DeferStmt:
59
+ if err := c.WriteStmtDefer(exp); err != nil {
60
+ return fmt.Errorf("failed to write defer statement: %w", err)
61
+ }
62
+ case *ast.IfStmt:
63
+ if err := c.WriteStmtIf(exp); err != nil {
64
+ return fmt.Errorf("failed to write if statement: %w", err)
65
+ }
66
+ case *ast.ExprStmt:
67
+ if err := c.WriteStmtExpr(exp); err != nil {
68
+ return fmt.Errorf("failed to write expression statement: %w", err)
69
+ }
70
+ case *ast.DeclStmt:
71
+ if err := c.WriteStmtDecl(exp); err != nil {
72
+ return fmt.Errorf("failed to write declaration statement: %w", err)
73
+ }
74
+ case *ast.ForStmt:
75
+ if err := c.WriteStmtFor(exp); err != nil {
76
+ return fmt.Errorf("failed to write for statement: %w", err)
77
+ }
78
+ case *ast.RangeStmt:
79
+ // Generate TS for for…range loops, log if something goes wrong
80
+ if err := c.WriteStmtRange(exp); err != nil {
81
+ return fmt.Errorf("failed to write range statement: %w", err)
82
+ }
83
+ case *ast.SwitchStmt:
84
+ if err := c.WriteStmtSwitch(exp); err != nil {
85
+ return fmt.Errorf("failed to write switch statement: %w", err)
86
+ }
87
+ case *ast.IncDecStmt:
88
+ if err := c.WriteStmtIncDec(exp); err != nil {
89
+ return fmt.Errorf("failed to write increment/decrement statement: %w", err)
90
+ }
91
+ case *ast.SendStmt:
92
+ if err := c.WriteStmtSend(exp); err != nil {
93
+ return fmt.Errorf("failed to write send statement: %w", err)
94
+ }
95
+ case *ast.GoStmt:
96
+ if err := c.WriteStmtGo(exp); err != nil {
97
+ return fmt.Errorf("failed to write go statement: %w", err)
98
+ }
99
+ case *ast.SelectStmt:
100
+ // Handle select statement
101
+ if err := c.WriteStmtSelect(exp); err != nil {
102
+ return fmt.Errorf("failed to write select statement: %w", err)
103
+ }
104
+ case *ast.BranchStmt:
105
+ if err := c.WriteStmtBranch(exp); err != nil {
106
+ return fmt.Errorf("failed to write branch statement: %w", err)
107
+ }
108
+ case *ast.TypeSwitchStmt:
109
+ if err := c.WriteStmtTypeSwitch(exp); err != nil {
110
+ return fmt.Errorf("failed to write type switch statement: %w", err)
111
+ }
112
+ default:
113
+ return errors.Errorf("unknown statement: %#v\n", a)
114
+ }
115
+ return nil
116
+ }
117
+
118
+ // WriteStmtDecl handles declaration statements (`ast.DeclStmt`),
119
+ // such as short variable declarations or type declarations within a statement list.
120
+ // It processes `ValueSpec`s and `TypeSpec`s within the declaration.
121
+ func (c *GoToTSCompiler) WriteStmtDecl(stmt *ast.DeclStmt) error {
122
+ // This typically contains a GenDecl
123
+ if genDecl, ok := stmt.Decl.(*ast.GenDecl); ok {
124
+ for _, spec := range genDecl.Specs {
125
+ // Value specs within a declaration statement
126
+ if valueSpec, ok := spec.(*ast.ValueSpec); ok {
127
+ if err := c.WriteValueSpec(valueSpec); err != nil {
128
+ return fmt.Errorf("failed to write value spec in declaration statement: %w", err)
129
+ }
130
+ } else if typeSpec, ok := spec.(*ast.TypeSpec); ok {
131
+ if err := c.WriteTypeSpec(typeSpec); err != nil {
132
+ return fmt.Errorf("failed to write type spec in declaration statement: %w", err)
133
+ }
134
+ } else {
135
+ c.tsw.WriteCommentLinef("unhandled spec in DeclStmt: %T", spec)
136
+ }
137
+ }
138
+ } else {
139
+ return errors.Errorf("unhandled declaration type in DeclStmt: %T", stmt.Decl)
140
+ }
141
+ return nil
142
+ }
143
+
144
+ // WriteStmtIncDec handles increment and decrement statements (`ast.IncDecStmt`).
145
+ // It writes the expression followed by `++` or `--`.
146
+ func (c *GoToTSCompiler) WriteStmtIncDec(stmt *ast.IncDecStmt) error {
147
+ if err := c.WriteValueExpr(stmt.X); err != nil { // The expression (e.g., i)
148
+ return fmt.Errorf("failed to write increment/decrement expression: %w", err)
149
+ }
150
+ tokStr, ok := TokenToTs(stmt.Tok)
151
+ if !ok {
152
+ return errors.Errorf("unknown incdec token: %s", stmt.Tok.String())
153
+ }
154
+ c.tsw.WriteLiterally(tokStr) // The token (e.g., ++)
155
+ c.tsw.WriteLine("")
156
+ return nil
157
+ }
158
+
159
+ // WriteStmtBranch handles branch statements (`ast.BranchStmt`), such as `break` and `continue`.
160
+ func (c *GoToTSCompiler) WriteStmtBranch(stmt *ast.BranchStmt) error {
161
+ switch stmt.Tok {
162
+ case token.BREAK:
163
+ c.tsw.WriteLine("break") // No semicolon needed
164
+ case token.CONTINUE:
165
+ c.tsw.WriteLine("continue") // No semicolon needed
166
+ default:
167
+ // This case should ideally not be reached if the Go parser is correct,
168
+ // as ast.BranchStmt only covers break, continue, goto, fallthrough.
169
+ // 'goto' and 'fallthrough' are handled elsewhere or not supported.
170
+ c.tsw.WriteCommentLinef("unhandled branch statement token: %s", stmt.Tok.String())
171
+ }
172
+ return nil
173
+ }
174
+
175
+ // WriteStmtGo translates a Go statement (`ast.GoStmt`) into its TypeScript equivalent.
176
+ // It handles `go func(){...}()`, `go namedFunc(args)`, and `go x.Method(args)`.
177
+ func (c *GoToTSCompiler) WriteStmtGo(exp *ast.GoStmt) error {
178
+ // Handle goroutine statement
179
+ // Translate 'go func() { ... }()' to 'queueMicrotask(() => { ... compiled body ... })'
180
+ callExpr := exp.Call
181
+
182
+ switch fun := callExpr.Fun.(type) {
183
+ case *ast.FuncLit:
184
+ // For function literals, we need to check if the function literal itself is async
185
+ // This happens during analysis in analysisVisitor.Visit for FuncLit nodes
186
+ isAsync := c.analysis.IsFuncLitAsync(fun)
187
+ if isAsync {
188
+ c.tsw.WriteLiterally("queueMicrotask(async () => ")
189
+ } else {
190
+ c.tsw.WriteLiterally("queueMicrotask(() => ")
191
+ }
192
+
193
+ // Compile the function literal's body directly
194
+ if err := c.WriteStmtBlock(fun.Body, true); err != nil {
195
+ return fmt.Errorf("failed to write goroutine function literal body: %w", err)
196
+ }
197
+
198
+ c.tsw.WriteLine(")") // Close the queueMicrotask statement
199
+
200
+ case *ast.Ident:
201
+ // Handle named functions: go namedFunc(args)
202
+ // Get the object for this function
203
+ obj := c.pkg.TypesInfo.Uses[fun]
204
+ if obj == nil {
205
+ return errors.Errorf("could not find object for function: %s", fun.Name)
206
+ }
207
+
208
+ // Check if the function is async
209
+ isAsync := c.analysis.IsAsyncFunc(obj)
210
+ if isAsync {
211
+ c.tsw.WriteLiterally("queueMicrotask(async () => {")
212
+ } else {
213
+ c.tsw.WriteLiterally("queueMicrotask(() => {")
214
+ }
215
+
216
+ c.tsw.Indent(1)
217
+ c.tsw.WriteLine("")
218
+
219
+ // Write the function call, using await if the function is async
220
+ if isAsync {
221
+ c.tsw.WriteLiterally("await ")
222
+ }
223
+
224
+ // Write the function name
225
+ c.tsw.WriteLiterally(fun.Name)
226
+
227
+ // Write the function arguments
228
+ c.tsw.WriteLiterally("(")
229
+ for i, arg := range callExpr.Args {
230
+ if i != 0 {
231
+ c.tsw.WriteLiterally(", ")
232
+ }
233
+ if err := c.WriteValueExpr(arg); err != nil {
234
+ return fmt.Errorf("failed to write argument %d in goroutine function call: %w", i, err)
235
+ }
236
+ }
237
+ c.tsw.WriteLiterally(")")
238
+ c.tsw.WriteLine("")
239
+
240
+ c.tsw.Indent(-1)
241
+ c.tsw.WriteLine("})") // Close the queueMicrotask callback and the statement
242
+ case *ast.SelectorExpr:
243
+ // Handle selector expressions: go x.Method(args)
244
+ // Get the object for the selected method
245
+ obj := c.pkg.TypesInfo.Uses[fun.Sel]
246
+ if obj == nil {
247
+ return errors.Errorf("could not find object for selected method: %s", fun.Sel.Name)
248
+ }
249
+
250
+ // Check if the function is async
251
+ isAsync := c.analysis.IsAsyncFunc(obj)
252
+ if isAsync {
253
+ c.tsw.WriteLiterally("queueMicrotask(async () => {")
254
+ } else {
255
+ c.tsw.WriteLiterally("queueMicrotask(() => {")
256
+ }
257
+
258
+ c.tsw.Indent(1)
259
+ c.tsw.WriteLine("")
260
+
261
+ // Write the function call, using await if the function is async
262
+ if isAsync {
263
+ c.tsw.WriteLiterally("await ")
264
+ }
265
+
266
+ // Write the selector expression (e.g., f.Bar)
267
+ // Note: callExpr.Fun is the *ast.SelectorExpr itself
268
+ // For method calls, we need to add null assertion since Go would panic on nil receiver
269
+ if selectorExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
270
+ if err := c.WriteValueExpr(selectorExpr.X); err != nil {
271
+ return fmt.Errorf("failed to write selector base expression in goroutine: %w", err)
272
+ }
273
+ // Add null assertion for method calls - Go would panic if receiver is nil
274
+ c.tsw.WriteLiterally("!.")
275
+ c.WriteIdent(selectorExpr.Sel, true)
276
+ } else {
277
+ if err := c.WriteValueExpr(callExpr.Fun); err != nil {
278
+ return fmt.Errorf("failed to write selector expression in goroutine: %w", err)
279
+ }
280
+ }
281
+
282
+ // Write the function arguments
283
+ c.tsw.WriteLiterally("(")
284
+ for i, arg := range callExpr.Args {
285
+ if i != 0 {
286
+ c.tsw.WriteLiterally(", ")
287
+ }
288
+ if err := c.WriteValueExpr(arg); err != nil {
289
+ return fmt.Errorf("failed to write argument %d in goroutine selector function call: %w", i, err)
290
+ }
291
+ }
292
+ c.tsw.WriteLiterally(")")
293
+ c.tsw.WriteLine("")
294
+
295
+ c.tsw.Indent(-1)
296
+ c.tsw.WriteLine("})") // Close the queueMicrotask callback and the statement
297
+ default:
298
+ return errors.Errorf("unhandled goroutine function type: %T", callExpr.Fun)
299
+ }
300
+ return nil
301
+ }
302
+
303
+ // WriteStmtExpr translates a Go expression statement (`ast.ExprStmt`) into
304
+ // its TypeScript equivalent. An expression statement in Go is an expression
305
+ // evaluated for its side effects (e.g., a function call).
306
+ // - A special case is a simple channel receive used as a statement (`<-ch`). This
307
+ // is translated to `await ch_ts.receive();` (the value is discarded).
308
+ // - For other expression statements, the underlying expression `exp.X` is translated
309
+ // using `WriteValueExpr`.
310
+ // - It attempts to preserve inline comments associated with the expression statement
311
+ // or its underlying expression `exp.X`.
312
+ //
313
+ // The translated statement is terminated with a newline.
314
+ func (c *GoToTSCompiler) WriteStmtExpr(exp *ast.ExprStmt) error {
315
+ // Handle simple channel receive used as a statement (<-ch)
316
+ if unaryExpr, ok := exp.X.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
317
+ // Translate <-ch to await $.chanRecv(ch)
318
+ c.tsw.WriteLiterally("await $.chanRecv(")
319
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
320
+ return fmt.Errorf("failed to write channel expression in receive statement: %w", err)
321
+ }
322
+ c.tsw.WriteLiterally(")") // Use chanRecv() as the value is discarded
323
+ c.tsw.WriteLine("")
324
+ return nil
325
+ }
326
+
327
+ // Handle other expression statements
328
+ if err := c.WriteValueExpr(exp.X); err != nil { // Expression statement evaluates a value
329
+ return err
330
+ }
331
+
332
+ // Handle potential inline comment for ExprStmt
333
+ inlineCommentWritten := false
334
+ if c.pkg != nil && c.pkg.Fset != nil && exp.End().IsValid() {
335
+ if file := c.pkg.Fset.File(exp.End()); file != nil {
336
+ endLine := file.Line(exp.End())
337
+ // Check comments associated *directly* with the ExprStmt node
338
+ for _, cg := range c.analysis.Cmap[exp] {
339
+ if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
340
+ commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
341
+ c.tsw.WriteLiterally(" // " + commentText)
342
+ inlineCommentWritten = true
343
+ break
344
+ }
345
+ }
346
+ // Also check comments associated with the underlying expression X
347
+ // This might be necessary if the comment map links it to X instead of ExprStmt
348
+ if !inlineCommentWritten {
349
+ for _, cg := range c.analysis.Cmap[exp.X] {
350
+ if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
351
+ commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
352
+ c.tsw.WriteLiterally(" // " + commentText)
353
+ inlineCommentWritten = true //nolint:ineffassign
354
+ break
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }
360
+
361
+ // Add semicolon according to design doc (omit semicolons) - REMOVED semicolon
362
+ c.tsw.WriteLine("") // Finish with a newline
363
+ return nil
364
+ }
365
+
366
+ // WriteStmtSend translates a Go channel send statement (`ast.SendStmt`),
367
+ // which has the form `ch <- value`, into its asynchronous TypeScript equivalent.
368
+ // The translation is `await ch_ts.send(value_ts)`.
369
+ // Both the channel expression (`exp.Chan`) and the value expression (`exp.Value`)
370
+ // are translated using `WriteValueExpr`. The `await` keyword is used because
371
+ // channel send operations are asynchronous in the TypeScript model.
372
+ // The statement is terminated with a newline.
373
+ func (c *GoToTSCompiler) WriteStmtSend(exp *ast.SendStmt) error {
374
+ // Translate ch <- value to await $.chanSend(ch, value)
375
+ c.tsw.WriteLiterally("await $.chanSend(")
376
+ if err := c.WriteValueExpr(exp.Chan); err != nil { // The channel expression
377
+ return fmt.Errorf("failed to write channel expression in send statement: %w", err)
378
+ }
379
+ c.tsw.WriteLiterally(", ")
380
+ if err := c.WriteValueExpr(exp.Value); err != nil { // The value expression
381
+ return fmt.Errorf("failed to write value expression in send statement: %w", err)
382
+ }
383
+ c.tsw.WriteLiterally(")")
384
+ c.tsw.WriteLine("") // Add newline after the statement
385
+ return nil
386
+ }
387
+
388
+ // WriteStmtIf translates a Go `if` statement (`ast.IfStmt`) into its
389
+ // TypeScript equivalent.
390
+ // - If the Go `if` has an initialization statement (`exp.Init`), it's wrapped
391
+ // in a TypeScript block `{...}` before the `if` keyword, and the initializer
392
+ // is translated within this block. This emulates Go's `if` statement scope.
393
+ // - The condition (`exp.Cond`) is translated using `WriteValueExpr` and placed
394
+ // within parentheses `(...)`.
395
+ // - The `if` body (`exp.Body`) is translated as a block statement using
396
+ // `WriteStmtBlock`. If `exp.Body` is nil, an empty block `{}` is written.
397
+ // - The `else` branch (`exp.Else`) is handled:
398
+ // - If `exp.Else` is a block statement (`ast.BlockStmt`), it's written as `else { ...body_ts... }`.
399
+ // - If `exp.Else` is another `if` statement (`ast.IfStmt`), it's written as `else if (...) ...`,
400
+ // recursively calling `WriteStmtIf`.
401
+ //
402
+ // The function aims to produce idiomatic TypeScript `if/else if/else` structures.
403
+ func (s *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
404
+ if exp.Init != nil {
405
+ s.tsw.WriteLiterally("{") // Write opening brace
406
+ s.tsw.WriteLine("") // Add newline immediately after opening brace
407
+ s.tsw.Indent(1) // Indent for the initializer
408
+
409
+ if err := s.WriteStmt(exp.Init); err != nil { // Write the initializer
410
+ return err
411
+ }
412
+
413
+ // This defer handles closing the synthetic block for the initializer
414
+ defer func() {
415
+ s.tsw.Indent(-1)
416
+ s.tsw.WriteLiterally("}") // Write the closing brace at the now-correct indent level
417
+ s.tsw.WriteLine("") // Ensure a newline *after* this '}', critical for preventing '}}'
418
+ }()
419
+ }
420
+
421
+ s.tsw.WriteLiterally("if (")
422
+ if err := s.WriteValueExpr(exp.Cond); err != nil { // Condition is a value
423
+ return err
424
+ }
425
+ s.tsw.WriteLiterally(") ")
426
+
427
+ if exp.Body != nil {
428
+ if err := s.WriteStmtBlock(exp.Body, exp.Else != nil); err != nil {
429
+ return fmt.Errorf("failed to write if body block statement: %w", err)
430
+ }
431
+ } else {
432
+ // Handle nil body case using WriteStmtBlock with an empty block
433
+ if err := s.WriteStmtBlock(&ast.BlockStmt{}, exp.Else != nil); err != nil {
434
+ return fmt.Errorf("failed to write empty block statement in if statement: %w", err)
435
+ }
436
+ }
437
+
438
+ // handle else branch
439
+ if exp.Else != nil {
440
+ s.tsw.WriteLiterally(" else ")
441
+ switch elseStmt := exp.Else.(type) {
442
+ case *ast.BlockStmt:
443
+ // Always pass false for suppressNewline here
444
+ if err := s.WriteStmtBlock(elseStmt, false); err != nil {
445
+ return fmt.Errorf("failed to write else block statement in if statement: %w", err)
446
+ }
447
+ case *ast.IfStmt:
448
+ // Recursive call handles its own block formatting
449
+ if err := s.WriteStmtIf(elseStmt); err != nil {
450
+ return fmt.Errorf("failed to write else if statement in if statement: %w", err)
451
+ }
452
+ }
453
+ }
454
+ return nil
455
+ }
456
+
457
+ // WriteStmtReturn translates a Go `return` statement (`ast.ReturnStmt`) into
458
+ // its TypeScript equivalent.
459
+ // - It writes the `return` keyword.
460
+ // - If there are multiple return values (`len(exp.Results) > 1`), the translated
461
+ // results are wrapped in a TypeScript array literal `[...]`.
462
+ // - Each result expression in `exp.Results` is translated using `WriteValueExpr`.
463
+ // - If there are no results, it simply writes `return`.
464
+ //
465
+ // The statement is terminated with a newline.
466
+ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
467
+ c.tsw.WriteLiterally("return ")
468
+
469
+ // Check if it's a bare named return
470
+ nodeInfo := c.analysis.NodeData[exp]
471
+ if nodeInfo != nil && nodeInfo.IsBareReturn {
472
+ var namedReturns []string
473
+ if nodeInfo.EnclosingFuncDecl != nil {
474
+ if obj := c.pkg.TypesInfo.ObjectOf(nodeInfo.EnclosingFuncDecl.Name); obj != nil {
475
+ if funcInfo := c.analysis.FunctionData[obj]; funcInfo != nil {
476
+ namedReturns = funcInfo.NamedReturns
477
+ }
478
+ }
479
+ } else if nodeInfo.EnclosingFuncLit != nil {
480
+ if funcInfo := c.analysis.FuncLitData[nodeInfo.EnclosingFuncLit]; funcInfo != nil {
481
+ namedReturns = funcInfo.NamedReturns
482
+ }
483
+ }
484
+
485
+ if len(namedReturns) > 0 {
486
+ c.tsw.WriteLiterally("[")
487
+ for i, name := range namedReturns {
488
+ if i != 0 {
489
+ c.tsw.WriteLiterally(", ")
490
+ }
491
+ c.tsw.WriteLiterally(name)
492
+ }
493
+ c.tsw.WriteLiterally("]")
494
+ }
495
+ } else {
496
+ // Handle explicit return values
497
+ if len(exp.Results) > 1 {
498
+ c.tsw.WriteLiterally("[")
499
+ }
500
+ for i, res := range exp.Results {
501
+ if i != 0 {
502
+ c.tsw.WriteLiterally(", ")
503
+ }
504
+ if err := c.WriteValueExpr(res); err != nil { // Return results are values
505
+ return err
506
+ }
507
+ }
508
+ if len(exp.Results) > 1 {
509
+ c.tsw.WriteLiterally("]")
510
+ }
511
+ }
512
+ c.tsw.WriteLine("")
513
+ return nil
514
+ }
515
+
516
+ // WriteStmtBlock translates a Go block statement (`ast.BlockStmt`), typically
517
+ // `{ ...stmts... }`, into its TypeScript equivalent, carefully preserving
518
+ // comments and blank lines to maintain code readability and structure.
519
+ // - It writes an opening brace `{` and indents.
520
+ // - If the analysis (`c.analysis.NeedsDefer`) indicates that the block (or a
521
+ // function it's part of) contains `defer` statements, it injects a
522
+ // `using __defer = new $.DisposableStack();` (or `AsyncDisposableStack` if
523
+ // the context is async or contains async defers) at the beginning of the block.
524
+ // This `__defer` stack is used by `WriteStmtDefer` to register cleanup actions.
525
+ // - It iterates through the statements (`exp.List`) in the block:
526
+ // - Leading comments associated with each statement are written first, with
527
+ // blank lines preserved based on original source line numbers.
528
+ // - The statement itself is then translated using `WriteStmt`.
529
+ // - Inline comments (comments on the same line after a statement) are expected
530
+ // to be handled by the individual statement writers (e.g., `WriteStmtExpr`).
531
+ // - Trailing comments within the block (after the last statement but before the
532
+ // closing brace) are written.
533
+ // - Blank lines before the closing brace are preserved.
534
+ // - Finally, it unindents and writes the closing brace `}`.
535
+ //
536
+ // If `suppressNewline` is true, the final newline after the closing brace is omitted
537
+ // (used, for example, when an `if` block is followed by an `else`).
538
+ func (c *GoToTSCompiler) WriteStmtBlock(exp *ast.BlockStmt, suppressNewline bool) error {
539
+ if exp == nil {
540
+ c.tsw.WriteLiterally("{}")
541
+ if !suppressNewline {
542
+ c.tsw.WriteLine("")
543
+ }
544
+ return nil
545
+ }
546
+
547
+ // Opening brace
548
+ c.tsw.WriteLine("{")
549
+ c.tsw.Indent(1)
550
+
551
+ // Determine if there is any defer to an async function literal in this block
552
+ hasAsyncDefer := false
553
+ for _, stmt := range exp.List {
554
+ if deferStmt, ok := stmt.(*ast.DeferStmt); ok {
555
+ if funcLit, ok := deferStmt.Call.Fun.(*ast.FuncLit); ok {
556
+ if c.analysis.IsFuncLitAsync(funcLit) {
557
+ hasAsyncDefer = true
558
+ break
559
+ }
560
+ }
561
+ }
562
+ }
563
+
564
+ // Add using statement if needed, considering async function or async defer
565
+ if c.analysis.NeedsDefer(exp) {
566
+ if c.analysis.IsInAsyncFunction(exp) || hasAsyncDefer {
567
+ c.tsw.WriteLine("await using __defer = new $.AsyncDisposableStack();")
568
+ } else {
569
+ c.tsw.WriteLine("using __defer = new $.DisposableStack();")
570
+ }
571
+ }
572
+
573
+ // Prepare line info
574
+ var file *token.File
575
+ if c.pkg != nil && c.pkg.Fset != nil && exp.Lbrace.IsValid() {
576
+ file = c.pkg.Fset.File(exp.Lbrace)
577
+ }
578
+
579
+ // writeBlank emits a single blank line if gap > 1
580
+ writeBlank := func(prev, curr int) {
581
+ if file != nil && prev > 0 && curr > prev+1 {
582
+ c.tsw.WriteLine("")
583
+ }
584
+ }
585
+
586
+ // Track last printed line, start at opening brace
587
+ lastLine := 0
588
+ if file != nil {
589
+ lastLine = file.Line(exp.Lbrace)
590
+ }
591
+
592
+ // 1. For each statement: write its leading comments, blank space, then the stmt
593
+ for _, stmt := range exp.List {
594
+ // Get statement's end line and position for inline comment check
595
+ stmtEndLine := 0
596
+ stmtEndPos := token.NoPos
597
+ if file != nil && stmt.End().IsValid() {
598
+ stmtEndLine = file.Line(stmt.End())
599
+ stmtEndPos = stmt.End()
600
+ }
601
+
602
+ // Process leading comments for stmt
603
+ comments := c.analysis.Cmap.Filter(stmt).Comments()
604
+ for _, cg := range comments {
605
+ // Check if this comment group is an inline comment for the current statement
606
+ isInlineComment := false
607
+ if file != nil && cg.Pos().IsValid() && stmtEndPos.IsValid() {
608
+ commentStartLine := file.Line(cg.Pos())
609
+ // Inline if starts on same line as stmt end AND starts after stmt end position
610
+ if commentStartLine == stmtEndLine && cg.Pos() > stmtEndPos {
611
+ isInlineComment = true
612
+ }
613
+ }
614
+
615
+ // If it's NOT an inline comment for this statement, write it here
616
+ if !isInlineComment {
617
+ start := 0
618
+ if file != nil && cg.Pos().IsValid() {
619
+ start = file.Line(cg.Pos())
620
+ }
621
+ writeBlank(lastLine, start)
622
+ c.WriteDoc(cg) // WriteDoc will handle the actual comment text
623
+ if file != nil && cg.End().IsValid() {
624
+ lastLine = file.Line(cg.End())
625
+ }
626
+ }
627
+ // If it IS an inline comment, skip it. The statement writer will handle it.
628
+ }
629
+
630
+ // the statement itself
631
+ stmtStart := 0
632
+ if file != nil && stmt.Pos().IsValid() {
633
+ stmtStart = file.Line(stmt.Pos())
634
+ }
635
+ writeBlank(lastLine, stmtStart)
636
+ // Call the specific statement writer (e.g., WriteStmtAssign).
637
+ // It is responsible for handling its own inline comment.
638
+ if err := c.WriteStmt(stmt); err != nil {
639
+ return fmt.Errorf("failed to write statement in block: %w", err)
640
+ }
641
+
642
+ if file != nil && stmt.End().IsValid() {
643
+ // Update lastLine based on the statement's end, *including* potential inline comment handled by WriteStmt*
644
+ lastLine = file.Line(stmt.End())
645
+ }
646
+ }
647
+
648
+ // 2. Trailing comments on the block (after last stmt, before closing brace)
649
+ trailing := c.analysis.Cmap.Filter(exp).Comments()
650
+ for _, cg := range trailing {
651
+ start := 0
652
+ if file != nil && cg.Pos().IsValid() {
653
+ start = file.Line(cg.Pos())
654
+ }
655
+ // only emit if it follows the last content
656
+ if start > lastLine {
657
+ writeBlank(lastLine, start)
658
+ c.WriteDoc(cg)
659
+ if file != nil && cg.End().IsValid() {
660
+ lastLine = file.Line(cg.End())
661
+ }
662
+ }
663
+ }
664
+
665
+ // 3. Blank lines before closing brace
666
+ closing := 0
667
+ if file != nil && exp.Rbrace.IsValid() {
668
+ closing = file.Line(exp.Rbrace)
669
+ }
670
+ writeBlank(lastLine, closing)
671
+
672
+ // Closing brace
673
+ c.tsw.Indent(-1)
674
+ c.tsw.WriteLiterally("}")
675
+
676
+ if !suppressNewline {
677
+ c.tsw.WriteLine("")
678
+ }
679
+ return nil
680
+ }
681
+
682
+ // WriteStmtSwitch translates a Go `switch` statement into its TypeScript equivalent.
683
+ // - If the Go switch has an initialization statement (`exp.Init`), it's wrapped
684
+ // in a TypeScript block `{...}` before the `switch` keyword, and the
685
+ // initializer is translated within this block. This emulates Go's switch scope.
686
+ // - The switch condition (`exp.Tag`):
687
+ // - If `exp.Tag` is present, it's translated using `WriteValueExpr`.
688
+ // - If `exp.Tag` is nil (a "tagless" switch, like `switch { case cond1: ... }`),
689
+ // it's translated as `switch (true)` in TypeScript.
690
+ // - Each case clause (`ast.CaseClause`) in `exp.Body.List` is translated using
691
+ // `WriteCaseClause`.
692
+ //
693
+ // The overall structure is `[optional_init_block] switch (condition_ts) { ...cases_ts... }`.
694
+ func (c *GoToTSCompiler) WriteStmtSwitch(exp *ast.SwitchStmt) error {
695
+ // Handle optional initialization statement
696
+ if exp.Init != nil {
697
+ c.tsw.WriteLiterally("{")
698
+ c.tsw.Indent(1)
699
+ if err := c.WriteStmt(exp.Init); err != nil {
700
+ return fmt.Errorf("failed to write switch initialization statement: %w", err)
701
+ }
702
+ defer func() {
703
+ c.tsw.Indent(-1)
704
+ c.tsw.WriteLiterally("}")
705
+ }()
706
+ }
707
+
708
+ c.tsw.WriteLiterally("switch (")
709
+ // Handle the switch tag (the expression being switched on)
710
+ if exp.Tag != nil {
711
+ if err := c.WriteValueExpr(exp.Tag); err != nil {
712
+ return fmt.Errorf("failed to write switch tag expression: %w", err)
713
+ }
714
+ } else {
715
+ c.tsw.WriteLiterally("true") // Write 'true' for switch without expression
716
+ }
717
+ c.tsw.WriteLiterally(") {")
718
+ c.tsw.WriteLine("")
719
+ c.tsw.Indent(1)
720
+
721
+ // Handle case clauses
722
+ for _, stmt := range exp.Body.List {
723
+ if caseClause, ok := stmt.(*ast.CaseClause); ok {
724
+ if err := c.WriteCaseClause(caseClause); err != nil {
725
+ return fmt.Errorf("failed to write case clause in switch statement: %w", err)
726
+ }
727
+ } else {
728
+ c.tsw.WriteCommentLinef("unhandled statement in switch body: %T", stmt)
729
+ }
730
+ }
731
+
732
+ c.tsw.Indent(-1)
733
+ c.tsw.WriteLine("}")
734
+ return nil
735
+ }
736
+
737
+ // WriteStmtDefer translates a Go `defer` statement into TypeScript code that
738
+ // utilizes a disposable stack (`$.DisposableStack` or `$.AsyncDisposableStack`).
739
+ // The Go `defer` semantics (LIFO execution at function exit) are emulated by
740
+ // registering a cleanup function with this stack.
741
+ // - `defer funcCall()` becomes `__defer.defer(() => { funcCall_ts(); });`.
742
+ // - `defer func(){ ...body... }()` (an immediately-invoked function literal, IIFL)
743
+ // has its body inlined: `__defer.defer(() => { ...body_ts... });`.
744
+ // - If the deferred call is to an async function or an async function literal
745
+ // (determined by `c.analysis.IsInAsyncFunctionMap`), the registered callback
746
+ // is marked `async`: `__defer.defer(async () => { ... });`.
747
+ //
748
+ // The `__defer` variable is assumed to be declared at the beginning of the
749
+ // function scope (see `WriteStmtBlock` or `WriteFuncDeclAsMethod`) using
750
+ // `await using __defer = new $.AsyncDisposableStack();` for async functions/contexts
751
+ // or `using __defer = new $.DisposableStack();` for sync contexts.
752
+ func (c *GoToTSCompiler) WriteStmtDefer(exp *ast.DeferStmt) error {
753
+ // Determine if the deferred call is to an async function literal using analysis
754
+ isAsyncDeferred := false
755
+ if funcLit, ok := exp.Call.Fun.(*ast.FuncLit); ok {
756
+ isAsyncDeferred = c.analysis.IsFuncLitAsync(funcLit)
757
+ }
758
+
759
+ // Set async prefix based on pre-computed async status
760
+ asyncPrefix := ""
761
+ if isAsyncDeferred {
762
+ asyncPrefix = "async "
763
+ }
764
+
765
+ // Set stack variable based on whether we are in an async function
766
+ stackVar := "__defer"
767
+ c.tsw.WriteLiterallyf("%s.defer(%s() => {", stackVar, asyncPrefix)
768
+ c.tsw.Indent(1)
769
+ c.tsw.WriteLine("")
770
+
771
+ // Write the deferred call or inline the body when it's an immediately-invoked
772
+ // function literal (defer func(){ ... }()).
773
+ if funcLit, ok := exp.Call.Fun.(*ast.FuncLit); ok && len(exp.Call.Args) == 0 {
774
+ // Inline the function literal's body to avoid nested arrow invocation.
775
+ for _, stmt := range funcLit.Body.List {
776
+ if err := c.WriteStmt(stmt); err != nil {
777
+ return fmt.Errorf("failed to write statement in deferred function body: %w", err)
778
+ }
779
+ }
780
+ } else {
781
+ // Write the call expression as-is.
782
+ if err := c.WriteValueExpr(exp.Call); err != nil {
783
+ return fmt.Errorf("failed to write deferred call: %w", err)
784
+ }
785
+ c.tsw.WriteLine("")
786
+ }
787
+
788
+ c.tsw.Indent(-1)
789
+ c.tsw.WriteLine("});")
790
+
791
+ return nil
792
+ }