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,439 @@
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
+ // WriteStmtAssign translates a Go assignment statement (`ast.AssignStmt`) into
14
+ // its TypeScript equivalent. It handles various forms of Go assignments:
15
+ //
16
+ // 1. **Multi-variable assignment from a single function call** (e.g., `a, b := fn()`):
17
+ // - Uses `writeMultiVarAssignFromCall` to generate `let [a, b] = fn_ts();`.
18
+ //
19
+ // 2. **Type assertion with comma-ok** (e.g., `val, ok := expr.(Type)`):
20
+ // - Uses `writeTypeAssertion` to generate `let { value: val, ok: ok } = $.typeAssert<Type_ts>(expr_ts, 'TypeName');`.
21
+ //
22
+ // 3. **Map lookup with comma-ok** (e.g., `val, ok := myMap[key]`):
23
+ // - Uses `writeMapLookupWithExists` to generate separate assignments for `val`
24
+ // (using `myMap_ts.get(key_ts) ?? zeroValue`) and `ok` (using `myMap_ts.has(key_ts)`).
25
+ //
26
+ // 4. **Channel receive with comma-ok** (e.g., `val, ok := <-ch`):
27
+ // - Uses `writeChannelReceiveWithOk` to generate `let { value: val, ok: ok } = await ch_ts.receiveWithOk();`.
28
+ //
29
+ // 5. **Discarded channel receive** (e.g., `<-ch` on RHS, no LHS vars):
30
+ // - Translates to `await ch_ts.receive();`.
31
+ //
32
+ // 6. **Single assignment** (e.g., `x = y`, `x := y`, `*p = y`, `x[i] = y`):
33
+ // - Uses `writeAssignmentCore` which handles:
34
+ // - Blank identifier `_` on LHS (evaluates RHS for side effects).
35
+ // - Assignment to dereferenced pointer `*p = val` -> `p_ts!.value = val_ts`.
36
+ // - Short declaration `x := y`: `let x = y_ts;`. If `x` is boxed, `let x: $.Box<T> = $.box(y_ts);`.
37
+ // - Regular assignment `x = y`, including compound assignments like `x += y`.
38
+ // - Assignment to map index `m[k] = v` using `$.mapSet`.
39
+ // - Struct value assignment `s1 = s2` becomes `s1 = s2.clone()` if `s2` is a struct.
40
+ //
41
+ // 7. **Multi-variable assignment with multiple RHS values** (e.g., `a, b = x, y`):
42
+ // - Uses `writeAssignmentCore` to generate `[a,b] = [x_ts, y_ts];` (or `let [a,b] = ...` for `:=`).
43
+ //
44
+ // The function ensures that the number of LHS and RHS expressions matches for
45
+ // most cases, erroring if they don't, except for specifically handled patterns
46
+ // like multi-assign from single call or discarded channel receive.
47
+ // It correctly applies `let` for `:=` (define) tokens and handles boxing and
48
+ // cloning semantics based on type information and analysis.
49
+ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
50
+ // writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
51
+ writeMultiVarAssignFromCall := func(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
52
+ // For token.DEFINE (:=), we need to check if any of the variables are already declared
53
+ // In Go, := can be used for redeclaration if at least one variable is new
54
+ if tok == token.DEFINE {
55
+ // For token.DEFINE (:=), we need to handle variable declarations differently
56
+ // In Go, := can redeclare existing variables if at least one variable is new
57
+
58
+ // First, identify which variables are new vs existing
59
+ newVars := make([]bool, len(lhs))
60
+ anyNewVars := false
61
+ allNewVars := true
62
+
63
+ // For multi-variable assignments with :=, we need to determine which variables
64
+ // are already in scope and which are new declarations
65
+ for i, lhsExpr := range lhs {
66
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
67
+ // In Go, variables declared with := can be redeclared if at least one is new
68
+ // For TypeScript, we need to separately declare new variables
69
+
70
+ // Check if this variable is already in scope
71
+ // - If the variable is used elsewhere before this point, it's existing
72
+ // - Otherwise, it's a new variable being declared
73
+ isNew := true
74
+
75
+ // Check if the variable is used elsewhere in the code
76
+ if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
77
+ // If it's in Uses, it's referenced elsewhere, so it exists
78
+ isNew = false
79
+ allNewVars = false
80
+ }
81
+
82
+ newVars[i] = isNew
83
+ if isNew {
84
+ anyNewVars = true
85
+ }
86
+ }
87
+ }
88
+
89
+ // Get function return types if available
90
+ var resultTypes []*types.Var
91
+ if callExpr.Fun != nil {
92
+ if funcType, ok := c.pkg.TypesInfo.TypeOf(callExpr.Fun).Underlying().(*types.Signature); ok {
93
+ if funcType.Results() != nil && funcType.Results().Len() > 0 {
94
+ for i := 0; i < funcType.Results().Len(); i++ {
95
+ resultTypes = append(resultTypes, funcType.Results().At(i))
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ if allNewVars && anyNewVars {
102
+ c.tsw.WriteLiterally("let [")
103
+
104
+ for i, lhsExpr := range lhs {
105
+ if i != 0 {
106
+ c.tsw.WriteLiterally(", ")
107
+ }
108
+
109
+ if ident, ok := lhsExpr.(*ast.Ident); ok {
110
+ if ident.Name == "_" {
111
+ // For underscore variables, use empty slots in destructuring pattern
112
+ } else {
113
+ c.WriteIdent(ident, false)
114
+ }
115
+ } else {
116
+ c.WriteValueExpr(lhsExpr)
117
+ }
118
+ }
119
+ c.tsw.WriteLiterally("] = ")
120
+ c.WriteValueExpr(callExpr)
121
+ c.tsw.WriteLine("")
122
+ return nil
123
+ } else if anyNewVars {
124
+ // If only some variables are new, declare them separately before the assignment
125
+ // Declare each new variable with appropriate type
126
+ for i, lhsExpr := range lhs {
127
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && newVars[i] {
128
+ c.tsw.WriteLiterally("let ")
129
+ c.WriteIdent(ident, false)
130
+
131
+ // Add type annotation if we have type information
132
+ if i < len(resultTypes) {
133
+ c.tsw.WriteLiterally(": ")
134
+ c.WriteGoType(resultTypes[i].Type(), GoTypeContextGeneral)
135
+ }
136
+
137
+ c.tsw.WriteLine("")
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ // First, collect all the selector expressions to identify variables that need to be initialized
144
+ hasSelectors := false
145
+ for _, lhsExpr := range lhs {
146
+ if _, ok := lhsExpr.(*ast.SelectorExpr); ok {
147
+ hasSelectors = true
148
+ break
149
+ }
150
+ }
151
+
152
+ // If we have selector expressions, we need to ensure variables are initialized
153
+ // before the destructuring assignment
154
+ if hasSelectors {
155
+ c.tsw.WriteLiterally("{")
156
+ c.tsw.WriteLine("")
157
+
158
+ // Write a temporary variable to hold the function call result
159
+ c.tsw.WriteLiterally(" const _tmp = ")
160
+ if err := c.WriteValueExpr(callExpr); err != nil {
161
+ return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
162
+ }
163
+ c.tsw.WriteLine("")
164
+
165
+ for i, lhsExpr := range lhs {
166
+ // Skip underscore variables
167
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name == "_" {
168
+ continue
169
+ }
170
+
171
+ // Write the LHS with indentation
172
+ c.tsw.WriteLiterally(" ")
173
+ if ident, ok := lhsExpr.(*ast.Ident); ok {
174
+ c.WriteIdent(ident, false)
175
+ } else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
176
+ if err := c.WriteValueExpr(selectorExpr); err != nil {
177
+ return fmt.Errorf("failed to write selector expression in LHS: %w", err)
178
+ }
179
+ } else {
180
+ return errors.Errorf("unhandled LHS expression in assignment: %T", lhsExpr)
181
+ }
182
+
183
+ // Write the assignment
184
+ c.tsw.WriteLiterallyf(" = _tmp[%d]", i)
185
+ // Always add a newline after each assignment
186
+ c.tsw.WriteLine("")
187
+ }
188
+
189
+ // Close the block scope
190
+ c.tsw.WriteLiterally("}")
191
+ c.tsw.WriteLine("")
192
+
193
+ return nil
194
+ }
195
+
196
+ // For simple cases without selector expressions, use array destructuring
197
+ c.tsw.WriteLiterally("[")
198
+
199
+ for i, lhsExpr := range lhs {
200
+ if i != 0 {
201
+ c.tsw.WriteLiterally(", ")
202
+ }
203
+
204
+ if ident, ok := lhsExpr.(*ast.Ident); ok {
205
+ // For underscore variables, use empty slots in destructuring pattern
206
+ if ident.Name != "_" {
207
+ c.WriteIdent(ident, false)
208
+ }
209
+ } else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
210
+ // Handle selector expressions (e.g., a.b) by using WriteValueExpr
211
+ if err := c.WriteValueExpr(selectorExpr); err != nil {
212
+ return fmt.Errorf("failed to write selector expression in LHS: %w", err)
213
+ }
214
+ } else {
215
+ // Should not happen for valid Go code in this context, but handle defensively
216
+ return errors.Errorf("unhandled LHS expression in destructuring: %T", lhsExpr)
217
+ }
218
+ }
219
+ c.tsw.WriteLiterally("] = ")
220
+
221
+ // Write the right-hand side (the function call)
222
+ if err := c.WriteValueExpr(callExpr); err != nil {
223
+ return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
224
+ }
225
+
226
+ c.tsw.WriteLine("")
227
+ return nil
228
+ }
229
+
230
+ // writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
231
+ // Note: We don't use WriteIndexExpr here because we need to handle .has() and .get() separately
232
+ writeMapLookupWithExists := func(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
233
+ // First check that we have exactly two LHS expressions (value and exists)
234
+ if len(lhs) != 2 {
235
+ return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
236
+ }
237
+
238
+ // Check for blank identifiers and get variable names
239
+ valueIsBlank := false
240
+ existsIsBlank := false
241
+ var valueName string
242
+ var existsName string
243
+
244
+ if valIdent, ok := lhs[0].(*ast.Ident); ok {
245
+ if valIdent.Name == "_" {
246
+ valueIsBlank = true
247
+ } else {
248
+ valueName = valIdent.Name
249
+ }
250
+ } else {
251
+ return fmt.Errorf("unhandled LHS expression type for value in map comma-ok: %T", lhs[0])
252
+ }
253
+
254
+ if existsIdent, ok := lhs[1].(*ast.Ident); ok {
255
+ if existsIdent.Name == "_" {
256
+ existsIsBlank = true
257
+ } else {
258
+ existsName = existsIdent.Name
259
+ }
260
+ } else {
261
+ return fmt.Errorf("unhandled LHS expression type for exists in map comma-ok: %T", lhs[1])
262
+ }
263
+
264
+ // Declare variables if using := and not blank
265
+ if tok == token.DEFINE {
266
+ if !valueIsBlank {
267
+ c.tsw.WriteLiterally("let ")
268
+ c.tsw.WriteLiterally(valueName)
269
+ // TODO: Add type annotation based on map value type
270
+ c.tsw.WriteLine("")
271
+ }
272
+ if !existsIsBlank {
273
+ c.tsw.WriteLiterally("let ")
274
+ c.tsw.WriteLiterally(existsName)
275
+ c.tsw.WriteLiterally(": boolean") // exists is always boolean
276
+ c.tsw.WriteLine("")
277
+ }
278
+ }
279
+
280
+ // Assign 'exists'
281
+ if !existsIsBlank {
282
+ c.tsw.WriteLiterally(existsName)
283
+ c.tsw.WriteLiterally(" = ")
284
+ c.tsw.WriteLiterally("$.mapHas(")
285
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
286
+ return err
287
+ }
288
+ c.tsw.WriteLiterally(", ")
289
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
290
+ return err
291
+ }
292
+ c.tsw.WriteLiterally(")")
293
+ c.tsw.WriteLine("")
294
+ }
295
+
296
+ // Assign 'value'
297
+ if !valueIsBlank {
298
+ c.tsw.WriteLiterally(valueName)
299
+ c.tsw.WriteLiterally(" = ")
300
+ c.tsw.WriteLiterally("$.mapGet(")
301
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
302
+ return err
303
+ }
304
+ c.tsw.WriteLiterally(", ")
305
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
306
+ return err
307
+ }
308
+ c.tsw.WriteLiterally(", ")
309
+ // Write the zero value for the map's value type
310
+ if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
311
+ if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
312
+ c.WriteZeroValueForType(mapType.Elem())
313
+ } else {
314
+ // Fallback zero value if type info is missing or not a map
315
+ c.tsw.WriteLiterally("null")
316
+ }
317
+ } else {
318
+ c.tsw.WriteLiterally("null")
319
+ }
320
+ c.tsw.WriteLiterally(")")
321
+ c.tsw.WriteLine("")
322
+ } else if existsIsBlank {
323
+ // If both are blank, still evaluate for side effects (though .has/.get are usually pure)
324
+ // We add a ; otherwise TypeScript thinks we are invoking a function.
325
+ c.tsw.WriteLiterally(";(") // Wrap in parens to make it an expression statement
326
+ c.tsw.WriteLiterally("$.mapHas(")
327
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
328
+ return err
329
+ }
330
+ c.tsw.WriteLiterally(", ")
331
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
332
+ return err
333
+ }
334
+ c.tsw.WriteLiterally("), ") // Evaluate .has
335
+ c.tsw.WriteLiterally("$.mapGet(")
336
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
337
+ return err
338
+ }
339
+ c.tsw.WriteLiterally(", ")
340
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
341
+ return err
342
+ }
343
+ c.tsw.WriteLiterally(", null))") // Evaluate .get with null as default
344
+ c.tsw.WriteLine("")
345
+ }
346
+
347
+ return nil
348
+ }
349
+
350
+ // Handle multi-variable assignment from a single expression.
351
+ if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
352
+ rhsExpr := exp.Rhs[0]
353
+ if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
354
+ return c.writeTypeAssert(exp.Lhs, typeAssertExpr, exp.Tok)
355
+ } else if indexExpr, ok := rhsExpr.(*ast.IndexExpr); ok {
356
+ // Check if this is a map lookup (comma-ok idiom)
357
+ if len(exp.Lhs) == 2 {
358
+ // Get the type of the indexed expression
359
+ if c.pkg != nil && c.pkg.TypesInfo != nil {
360
+ tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]
361
+ if ok {
362
+ // Check if it's a map type
363
+ if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
364
+ return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
365
+ }
366
+ }
367
+ }
368
+ }
369
+ } else if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
370
+ // Handle val, ok := <-channel
371
+ if len(exp.Lhs) == 2 {
372
+ return c.writeChannelReceiveWithOk(exp.Lhs, unaryExpr, exp.Tok)
373
+ }
374
+ // If LHS count is not 2, fall through to error or other handling
375
+ } else if callExpr, ok := rhsExpr.(*ast.CallExpr); ok {
376
+ return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
377
+ }
378
+ // If none of the specific multi-assign patterns match, fall through to the error check below
379
+ }
380
+
381
+ // Ensure LHS and RHS have the same length for valid Go code in these cases
382
+ if len(exp.Lhs) != len(exp.Rhs) {
383
+ return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
384
+ }
385
+
386
+ // Handle multi-variable assignment (e.g., swaps) using writeAssignmentCore
387
+ if len(exp.Lhs) > 1 {
388
+ // Need to handle := for multi-variable declarations
389
+ if exp.Tok == token.DEFINE {
390
+ c.tsw.WriteLiterally("let ") // Use let for multi-variable declarations
391
+ }
392
+ // For multi-variable assignments, we've already added the "let" if needed
393
+ if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, false); err != nil {
394
+ return err
395
+ }
396
+ // Handle potential inline comment for multi-variable assignment
397
+ c.writeInlineComment(exp)
398
+ c.tsw.WriteLine("") // Add newline after the statement
399
+ return nil
400
+ }
401
+
402
+ // Handle single assignment using writeAssignmentCore
403
+ if len(exp.Lhs) == 1 {
404
+ addDeclaration := exp.Tok == token.DEFINE
405
+ if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, addDeclaration); err != nil {
406
+ return err
407
+ }
408
+ // Handle potential inline comment for single assignment
409
+ c.writeInlineComment(exp)
410
+ c.tsw.WriteLine("") // Add newline after the statement
411
+ return nil
412
+ }
413
+
414
+ // Should not reach here if LHS/RHS counts are valid and handled
415
+ return fmt.Errorf("unhandled assignment case")
416
+ }
417
+
418
+ // writeInlineComment checks for and writes any inline comments associated with the given AST node.
419
+ // It is intended to be called immediately after writing the main statement/expression.
420
+ func (c *GoToTSCompiler) writeInlineComment(node ast.Node) {
421
+ if c.pkg == nil || c.pkg.Fset == nil || !node.End().IsValid() {
422
+ return
423
+ }
424
+
425
+ file := c.pkg.Fset.File(node.End())
426
+ if file == nil {
427
+ return
428
+ }
429
+
430
+ endLine := file.Line(node.End())
431
+ // Check comments associated *directly* with the node
432
+ for _, cg := range c.analysis.Cmap[node] {
433
+ if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > node.End() {
434
+ commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
435
+ c.tsw.WriteLiterally(" // " + commentText)
436
+ return // Only write the first inline comment found
437
+ }
438
+ }
439
+ }
@@ -0,0 +1,178 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/token"
7
+
8
+ "github.com/pkg/errors"
9
+ )
10
+
11
+ // WriteStmtFor translates a Go `for` statement (`ast.ForStmt`) into a
12
+ // TypeScript `for` loop.
13
+ // The structure is `for (init_ts; cond_ts; post_ts) { body_ts }`.
14
+ // - The initialization part (`exp.Init`) is translated using `WriteStmtForInit`.
15
+ // - The condition part (`exp.Cond`) is translated using `WriteValueExpr`. If nil,
16
+ // the condition part in TypeScript is empty (resulting in an infinite loop
17
+ // unless broken out of).
18
+ // - The post-iteration part (`exp.Post`) is translated using `WriteStmtForPost`.
19
+ // - The loop body (`exp.Body`) is translated as a block statement using `WriteStmtBlock`.
20
+ //
21
+ // This function covers standard Go `for` loops (three-part loops, condition-only
22
+ // loops, and infinite loops). `for...range` loops are handled by `WriteStmtRange`.
23
+ func (c *GoToTSCompiler) WriteStmtFor(exp *ast.ForStmt) error {
24
+ c.tsw.WriteLiterally("for (")
25
+ if exp.Init != nil {
26
+ if err := c.WriteStmtForInit(exp.Init); err != nil { // Use WriteStmtForInit
27
+ return fmt.Errorf("failed to write for loop initialization: %w", err)
28
+ }
29
+ }
30
+ c.tsw.WriteLiterally("; ")
31
+ if exp.Cond != nil {
32
+ if err := c.WriteValueExpr(exp.Cond); err != nil { // Condition is a value
33
+ return fmt.Errorf("failed to write for loop condition: %w", err)
34
+ }
35
+ }
36
+ c.tsw.WriteLiterally("; ")
37
+ if exp.Post != nil {
38
+ if err := c.WriteStmtForPost(exp.Post); err != nil { // Use WriteStmtForPost
39
+ return fmt.Errorf("failed to write for loop post statement: %w", err)
40
+ }
41
+ }
42
+ c.tsw.WriteLiterally(") ")
43
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
44
+ return fmt.Errorf("failed to write for loop body: %w", err)
45
+ }
46
+ return nil
47
+ }
48
+
49
+ // WriteStmtForInit translates the initialization part of a Go `for` loop header
50
+ // (e.g., `i := 0` or `i = 0` in `for i := 0; ...`) into its TypeScript equivalent.
51
+ // - If `stmt` is an `ast.AssignStmt`:
52
+ // - For short variable declarations (`:=`) with multiple variables (e.g., `i, j := 0, 10`),
53
+ // it generates `let i = 0, j = 10`. Each LHS variable is paired with its
54
+ // corresponding RHS value; if RHS values are insufficient, remaining LHS
55
+ // variables are initialized with their zero value using `WriteZeroValue`.
56
+ // - For other assignments (single variable `:=`, or regular `=`), it uses
57
+ // `writeAssignmentCore`. If it's `:=`, `let` is prepended.
58
+ // - If `stmt` is an `ast.ExprStmt` (less common in `for` inits), it translates
59
+ // the expression using `WriteValueExpr`.
60
+ //
61
+ // Unhandled statement types in the init part result in a comment.
62
+ func (c *GoToTSCompiler) WriteStmtForInit(stmt ast.Stmt) error {
63
+ switch s := stmt.(type) {
64
+ case *ast.AssignStmt:
65
+ // Handle assignment in init (e.g., i := 0 or i = 0)
66
+ // For TypeScript for-loop init, we need to handle multi-variable declarations differently
67
+ if s.Tok == token.DEFINE && len(s.Lhs) > 1 && len(s.Rhs) > 0 {
68
+ // For loop initialization with multiple variables (e.g., let i = 0, j = 10)
69
+ c.tsw.WriteLiterally("let ")
70
+
71
+ // Handle each LHS variable with its corresponding RHS value
72
+ for i, lhs := range s.Lhs {
73
+ if i > 0 {
74
+ c.tsw.WriteLiterally(", ")
75
+ }
76
+
77
+ // Write the LHS variable name
78
+ if err := c.WriteValueExpr(lhs); err != nil {
79
+ return err
80
+ }
81
+
82
+ // Write the corresponding RHS, or a default if not enough RHS values
83
+ c.tsw.WriteLiterally(" = ")
84
+ if i < len(s.Rhs) {
85
+ // If there's a corresponding RHS value
86
+ if err := c.WriteValueExpr(s.Rhs[i]); err != nil {
87
+ return err
88
+ }
89
+ } else {
90
+ // No corresponding RHS
91
+ return errors.Errorf("no corresponding rhs to lhs: %v", s)
92
+ }
93
+ }
94
+ } else {
95
+ // Regular single variable or assignment (not declaration)
96
+ if s.Tok == token.DEFINE {
97
+ c.tsw.WriteLiterally("let ")
98
+ }
99
+ // Use existing assignment core logic
100
+ if err := c.writeAssignmentCore(s.Lhs, s.Rhs, s.Tok, false); err != nil {
101
+ return err
102
+ }
103
+ }
104
+ return nil
105
+ case *ast.ExprStmt:
106
+ // Handle expression statement in init
107
+ return c.WriteValueExpr(s.X)
108
+ default:
109
+ return errors.Errorf("unhandled for loop init statement: %T", stmt)
110
+ }
111
+ }
112
+
113
+ // WriteStmtForPost translates the post-iteration part of a Go `for` loop header
114
+ // (e.g., `i++` or `i, j = i+1, j-1` in `for ...; i++`) into its TypeScript
115
+ // equivalent.
116
+ // - If `stmt` is an `ast.IncDecStmt` (e.g., `i++`), it writes `i_ts++`.
117
+ // - If `stmt` is an `ast.AssignStmt`:
118
+ // - For multiple variable assignments (e.g., `i, j = i+1, j-1`), it generates
119
+ // TypeScript array destructuring: `[i_ts, j_ts] = [i_ts+1, j_ts-1]`.
120
+ // - For single variable assignments, it uses `writeAssignmentCore`.
121
+ // - If `stmt` is an `ast.ExprStmt` (less common), it translates the expression
122
+ // using `WriteValueExpr`.
123
+ //
124
+ // Unhandled statement types in the post part result in a comment.
125
+ func (c *GoToTSCompiler) WriteStmtForPost(stmt ast.Stmt) error {
126
+ switch s := stmt.(type) {
127
+ case *ast.IncDecStmt:
128
+ // Handle increment/decrement (e.g., i++)
129
+ if err := c.WriteValueExpr(s.X); err != nil { // The expression (e.g., i)
130
+ return err
131
+ }
132
+ tokStr, ok := TokenToTs(s.Tok)
133
+ if !ok {
134
+ return errors.Errorf("unknown incdec token: %v", s.Tok)
135
+ }
136
+ c.tsw.WriteLiterally(tokStr) // The token (e.g., ++)
137
+ return nil
138
+ case *ast.AssignStmt:
139
+ // For multiple variable assignment in post like i, j = i+1, j-1
140
+ // we need to use destructuring in TypeScript like [i, j] = [i+1, j-1]
141
+ if len(s.Lhs) > 1 && len(s.Rhs) > 0 {
142
+ // Write LHS as array destructuring
143
+ c.tsw.WriteLiterally("[")
144
+ for i, lhs := range s.Lhs {
145
+ if i > 0 {
146
+ c.tsw.WriteLiterally(", ")
147
+ }
148
+ if err := c.WriteValueExpr(lhs); err != nil {
149
+ return err
150
+ }
151
+ }
152
+ c.tsw.WriteLiterally("] = [")
153
+
154
+ // Write RHS as array
155
+ for i, rhs := range s.Rhs {
156
+ if i > 0 {
157
+ c.tsw.WriteLiterally(", ")
158
+ }
159
+ if err := c.WriteValueExpr(rhs); err != nil {
160
+ return err
161
+ }
162
+ }
163
+ c.tsw.WriteLiterally("]")
164
+ } else {
165
+ // Regular single variable assignment
166
+ // No declaration handling needed in for loop post statements
167
+ if err := c.writeAssignmentCore(s.Lhs, s.Rhs, s.Tok, false); err != nil {
168
+ return err
169
+ }
170
+ }
171
+ return nil
172
+ case *ast.ExprStmt:
173
+ // Handle expression statement in post
174
+ return c.WriteValueExpr(s.X)
175
+ default:
176
+ return errors.Errorf("unhandled for loop post statement: %T", stmt)
177
+ }
178
+ }