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.
- package/cmd/goscript/cmd_compile.go +2 -2
- package/compiler/analysis.go +229 -51
- package/compiler/assignment.go +412 -0
- package/compiler/compiler.go +185 -5885
- package/compiler/compiler_test.go +40 -8
- package/compiler/composite-lit.go +552 -0
- package/compiler/config.go +3 -0
- package/compiler/decl.go +259 -0
- package/compiler/expr-call.go +479 -0
- package/compiler/expr-selector.go +125 -0
- package/compiler/expr-star.go +90 -0
- package/compiler/expr-type.go +309 -0
- package/compiler/expr-value.go +89 -0
- package/compiler/expr.go +591 -0
- package/compiler/field.go +169 -0
- package/compiler/lit.go +131 -0
- package/compiler/primitive.go +148 -0
- package/compiler/{write-type-spec.go → spec-struct.go} +211 -204
- package/compiler/spec-value.go +226 -0
- package/compiler/spec.go +272 -0
- package/compiler/stmt-assign.go +439 -0
- package/compiler/stmt-for.go +178 -0
- package/compiler/stmt-range.go +235 -0
- package/compiler/stmt-select.go +211 -0
- package/compiler/stmt-type-switch.go +147 -0
- package/compiler/stmt.go +792 -0
- package/compiler/type-assert.go +209 -0
- package/compiler/type-info.go +141 -0
- package/compiler/type.go +618 -0
- package/go.mod +2 -1
- package/go.sum +4 -2
- package/package.json +6 -6
- package/builtin/builtin.go +0 -11
- package/builtin/builtin.ts +0 -2114
- package/dist/builtin/builtin.d.ts +0 -495
- package/dist/builtin/builtin.js +0 -1490
- package/dist/builtin/builtin.js.map +0 -1
- /package/compiler/{writer.go → code-writer.go} +0 -0
package/compiler/stmt.go
ADDED
|
@@ -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
|
+
}
|