goscript 0.0.22 → 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.
@@ -1,10 +1,10 @@
1
1
  package compiler
2
2
 
3
3
  import (
4
+ "fmt"
4
5
  "go/ast"
5
6
  "go/token"
6
-
7
- "github.com/pkg/errors"
7
+ "go/types"
8
8
  )
9
9
 
10
10
  // WriteValueSpec translates a Go value specification (`ast.ValueSpec`),
@@ -44,36 +44,67 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
44
44
  name := a.Names[0]
45
45
  obj := c.pkg.TypesInfo.Defs[name]
46
46
  if obj == nil {
47
- return errors.Errorf("could not resolve type: %v", name)
47
+ return fmt.Errorf("could not resolve type: %v", name)
48
48
  }
49
49
 
50
50
  goType := obj.Type()
51
51
  needsBox := c.analysis.NeedsBoxed(obj) // Check if address is taken
52
52
 
53
+ hasInitializer := len(a.Values) > 0
54
+ var initializerExpr ast.Expr
55
+ if hasInitializer {
56
+ initializerExpr = a.Values[0]
57
+ }
58
+
59
+ // Check if the initializer will result in an $.arrayToSlice call in TypeScript
60
+ isSliceConversion := false
61
+ if hasInitializer {
62
+ // Case 1: Direct call to $.arrayToSlice in Go source (less common for typical array literals)
63
+ if callExpr, isCallExpr := initializerExpr.(*ast.CallExpr); isCallExpr {
64
+ if selExpr, isSelExpr := callExpr.Fun.(*ast.SelectorExpr); isSelExpr {
65
+ if pkgIdent, isPkgIdent := selExpr.X.(*ast.Ident); isPkgIdent && pkgIdent.Name == "$" {
66
+ if selExpr.Sel.Name == "arrayToSlice" {
67
+ isSliceConversion = true
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ // Case 2: Go array or slice literal, which will be compiled to $.arrayToSlice
74
+ // We also check if the original Go type is actually a slice or array.
75
+ if !isSliceConversion { // Only check if not already determined by Case 1
76
+ if _, isCompositeLit := initializerExpr.(*ast.CompositeLit); isCompositeLit {
77
+ switch goType.Underlying().(type) {
78
+ case *types.Slice, *types.Array:
79
+ isSliceConversion = true
80
+ }
81
+ }
82
+ }
83
+ }
84
+
53
85
  // Start declaration
54
86
  c.tsw.WriteLiterally("let ")
55
87
  c.tsw.WriteLiterally(name.Name)
56
- c.tsw.WriteLiterally(": ")
57
88
 
58
- // Write type annotation
59
- if needsBox {
60
- // If boxed, the variable holds Box<OriginalGoType>
61
- c.tsw.WriteLiterally("$.Box<")
62
- c.WriteGoType(goType) // Write the original Go type T
63
- c.tsw.WriteLiterally(">")
64
- } else {
65
- // If not boxed, the variable holds the translated Go type directly
66
- c.WriteGoType(goType)
89
+ if !isSliceConversion {
90
+ c.tsw.WriteLiterally(": ")
91
+ // Write type annotation
92
+ if needsBox {
93
+ // If boxed, the variable holds Box<OriginalGoType>
94
+ c.tsw.WriteLiterally("$.Box<")
95
+ c.WriteGoType(goType, GoTypeContextGeneral) // Write the original Go type T
96
+ c.tsw.WriteLiterally(">")
97
+ } else {
98
+ // If not boxed, the variable holds the translated Go type directly
99
+ c.WriteGoType(goType, GoTypeContextGeneral)
100
+ }
67
101
  }
68
102
 
69
103
  // Write initializer
70
104
  c.tsw.WriteLiterally(" = ")
71
- hasInitializer := len(a.Values) > 0
72
- var initializerExpr ast.Expr
73
- if hasInitializer {
74
- initializerExpr = a.Values[0]
75
105
 
76
- // Special case for nil pointer to struct type: (*struct{})(nil)
106
+ // Special case for nil pointer to struct type: (*struct{})(nil)
107
+ if hasInitializer {
77
108
  if callExpr, isCallExpr := initializerExpr.(*ast.CallExpr); isCallExpr {
78
109
  if starExpr, isStarExpr := callExpr.Fun.(*ast.StarExpr); isStarExpr {
79
110
  if _, isStructType := starExpr.X.(*ast.StructType); isStructType {
@@ -81,6 +112,7 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
81
112
  if len(callExpr.Args) == 1 {
82
113
  if nilIdent, isIdent := callExpr.Args[0].(*ast.Ident); isIdent && nilIdent.Name == "nil" {
83
114
  c.tsw.WriteLiterally("null")
115
+ c.tsw.WriteLine("") // Ensure newline after null
84
116
  return nil
85
117
  }
86
118
  }
package/compiler/spec.go CHANGED
@@ -253,11 +253,20 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
253
253
  impName = a.Name.Name
254
254
  }
255
255
 
256
- importPath := translateGoPathToTypescriptPath(goPath)
256
+ // All Go package imports are mapped to the @goscript/ scope.
257
+ // The TypeScript compiler will resolve these using tsconfig paths to either
258
+ // handwritten versions (in .goscript-assets) or transpiled versions (in goscript).
259
+ var tsImportPath string
260
+ if goPath == "github.com/aperturerobotics/goscript/builtin" {
261
+ tsImportPath = "@goscript/builtin/builtin.js"
262
+ } else {
263
+ tsImportPath = "@goscript/" + goPath
264
+ }
265
+
257
266
  c.analysis.Imports[impName] = &fileImport{
258
- importPath: importPath,
267
+ importPath: tsImportPath,
259
268
  importVars: make(map[string]struct{}),
260
269
  }
261
270
 
262
- c.tsw.WriteImport(impName, importPath+"/index.js")
271
+ c.tsw.WriteImport(impName, tsImportPath+"/index.js")
263
272
  }
@@ -5,6 +5,7 @@ import (
5
5
  "go/ast"
6
6
  "go/token"
7
7
  "go/types"
8
+ "strings"
8
9
 
9
10
  "github.com/pkg/errors"
10
11
  )
@@ -130,7 +131,7 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
130
131
  // Add type annotation if we have type information
131
132
  if i < len(resultTypes) {
132
133
  c.tsw.WriteLiterally(": ")
133
- c.WriteGoType(resultTypes[i].Type())
134
+ c.WriteGoType(resultTypes[i].Type(), GoTypeContextGeneral)
134
135
  }
135
136
 
136
137
  c.tsw.WriteLine("")
@@ -392,6 +393,8 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
392
393
  if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, false); err != nil {
393
394
  return err
394
395
  }
396
+ // Handle potential inline comment for multi-variable assignment
397
+ c.writeInlineComment(exp)
395
398
  c.tsw.WriteLine("") // Add newline after the statement
396
399
  return nil
397
400
  }
@@ -402,6 +405,8 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
402
405
  if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, addDeclaration); err != nil {
403
406
  return err
404
407
  }
408
+ // Handle potential inline comment for single assignment
409
+ c.writeInlineComment(exp)
405
410
  c.tsw.WriteLine("") // Add newline after the statement
406
411
  return nil
407
412
  }
@@ -409,3 +414,26 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
409
414
  // Should not reach here if LHS/RHS counts are valid and handled
410
415
  return fmt.Errorf("unhandled assignment case")
411
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
+ }
@@ -128,6 +128,11 @@ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
128
128
  c.tsw.WriteLine("}")
129
129
  return nil
130
130
  } else if basic.Info()&types.IsInteger != 0 {
131
+ // The value variable is not allowed ranging over an integer.
132
+ if exp.Value != nil {
133
+ return errors.Errorf("ranging over an integer supports key variable only (not value variable): %v", exp)
134
+ }
135
+
131
136
  // Handle ranging over an integer (Go 1.22+)
132
137
  // Determine the index variable name for the generated loop
133
138
  indexVarName := "_i" // Default name
@@ -141,20 +146,13 @@ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
141
146
  if err := c.WriteValueExpr(exp.X); err != nil { // This is N
142
147
  return fmt.Errorf("failed to write range loop integer expression: %w", err)
143
148
  }
144
- c.tsw.WriteLiterallyf("; %s++) {", indexVarName)
145
- c.tsw.Indent(1)
146
- c.tsw.WriteLine("")
147
-
148
- // The value variable is not allowed ranging over an integer.
149
- if exp.Value != nil {
150
- return errors.Errorf("ranging over an integer supports key variable only (not value variable): %v", exp)
151
- }
149
+ c.tsw.WriteLiterallyf("; %s++) ", indexVarName)
152
150
 
151
+ // write body
153
152
  if err := c.WriteStmtBlock(exp.Body, false); err != nil {
154
153
  return fmt.Errorf("failed to write range loop integer body: %w", err)
155
154
  }
156
- c.tsw.Indent(-1)
157
- c.tsw.WriteLine("}")
155
+
158
156
  return nil
159
157
  }
160
158
  }
@@ -164,7 +162,7 @@ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
164
162
  _, isArray := underlying.(*types.Array)
165
163
  if isArray || isSlice {
166
164
  // Determine the index variable name for the generated loop
167
- indexVarName := "i" // Default name
165
+ indexVarName := "_i" // Default name
168
166
  if exp.Key != nil {
169
167
  if keyIdent, ok := exp.Key.(*ast.Ident); ok && keyIdent.Name != "_" {
170
168
  indexVarName = keyIdent.Name
@@ -0,0 +1,211 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/token"
7
+
8
+ "github.com/pkg/errors"
9
+ )
10
+
11
+ // WriteStmtSelect translates a Go `select` statement into an asynchronous
12
+ // TypeScript operation using the `$.selectStatement` runtime helper.
13
+ // Go's `select` provides non-deterministic choice over channel operations.
14
+ // This is emulated by constructing an array of `SelectCase` objects, one for
15
+ // each `case` in the Go `select`, and passing it to `$.selectStatement`.
16
+ //
17
+ // Each `SelectCase` object includes:
18
+ // - `id`: A unique identifier for the case.
19
+ // - `isSend`: `true` for send operations (`case ch <- val:`), `false` for receives.
20
+ // - `channel`: The TypeScript channel object.
21
+ // - `value` (for sends): The value being sent.
22
+ // - `onSelected: async (result) => { ... }`: A callback executed when this case
23
+ // is chosen. `result` contains `{ value, ok }` for receives.
24
+ // - Inside `onSelected`, assignments for receive operations (e.g., `v := <-ch`,
25
+ // `v, ok := <-ch`) are handled by declaring/assigning variables from `result.value`
26
+ // and `result.ok`.
27
+ // - The original Go case body is then translated within this callback.
28
+ //
29
+ // A `default` case in Go `select` is translated to a `SelectCase` with `id: -1`
30
+ // and its body in the `onSelected` handler. The `$.selectStatement` helper
31
+ // is informed if a default case exists.
32
+ // The entire `$.selectStatement(...)` call is `await`ed because channel
33
+ // operations are asynchronous in the TypeScript model.
34
+ func (c *GoToTSCompiler) WriteStmtSelect(exp *ast.SelectStmt) error {
35
+ // This is our implementation of the select statement, which will use Promise.race
36
+ // to achieve the same semantics as Go's select statement.
37
+
38
+ // Variable to track whether we have a default case
39
+ hasDefault := false
40
+
41
+ // Start the selectStatement call and the array literal
42
+ c.tsw.WriteLiterally("await $.selectStatement(")
43
+ c.tsw.WriteLine("[") // Put bracket on new line
44
+ c.tsw.Indent(1)
45
+
46
+ // For each case clause, generate a SelectCase object directly into the array literal
47
+ for i, stmt := range exp.Body.List {
48
+ if commClause, ok := stmt.(*ast.CommClause); ok {
49
+ if commClause.Comm == nil {
50
+ // This is a default case
51
+ hasDefault = true
52
+ // Add a SelectCase object for the default case with a special ID
53
+ c.tsw.WriteLiterally("{") // Start object literal
54
+ c.tsw.Indent(1)
55
+ c.tsw.WriteLine("")
56
+ c.tsw.WriteLiterally("id: -1,") // Special ID for default case
57
+ c.tsw.WriteLine("")
58
+ c.tsw.WriteLiterally("isSend: false,") // Default case is neither send nor receive, but needs a value
59
+ c.tsw.WriteLine("")
60
+ c.tsw.WriteLiterally("channel: null,") // No channel for default case
61
+ c.tsw.WriteLine("")
62
+ c.tsw.WriteLiterally("onSelected: async (result) => {") // Mark as async because case body might contain await
63
+ c.tsw.Indent(1)
64
+ c.tsw.WriteLine("")
65
+ // Write the case body
66
+ for _, bodyStmt := range commClause.Body {
67
+ if err := c.WriteStmt(bodyStmt); err != nil {
68
+ return fmt.Errorf("failed to write statement in select default case body (onSelected): %w", err)
69
+ }
70
+ }
71
+ c.tsw.Indent(-1)
72
+ c.tsw.WriteLine("}") // Close onSelected handler
73
+ c.tsw.Indent(-1)
74
+ c.tsw.WriteLiterally("},") // Close SelectCase object and add comma
75
+ c.tsw.WriteLine("")
76
+
77
+ continue
78
+ }
79
+
80
+ // Generate a unique ID for this case
81
+ caseID := i
82
+
83
+ // Start writing the SelectCase object
84
+ c.tsw.WriteLiterally("{") // Start object literal
85
+ c.tsw.Indent(1)
86
+ c.tsw.WriteLine("")
87
+ c.tsw.WriteLiterallyf("id: %d,", caseID)
88
+ c.tsw.WriteLine("")
89
+
90
+ // Handle different types of comm statements
91
+ switch comm := commClause.Comm.(type) {
92
+ case *ast.AssignStmt:
93
+ // This is a receive operation with assignment: case v := <-ch: or case v, ok := <-ch:
94
+ if len(comm.Rhs) == 1 {
95
+ if unaryExpr, ok := comm.Rhs[0].(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
96
+ // It's a receive operation
97
+ c.tsw.WriteLiterally("isSend: false,")
98
+ c.tsw.WriteLine("")
99
+ c.tsw.WriteLiterally("channel: ")
100
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // The channel expression
101
+ return fmt.Errorf("failed to write channel expression in select receive case: %w", err)
102
+ }
103
+ c.tsw.WriteLiterally(",")
104
+ c.tsw.WriteLine("")
105
+ } else {
106
+ c.tsw.WriteCommentLinef("unhandled RHS in select assignment case: %T", comm.Rhs[0])
107
+ }
108
+ } else {
109
+ c.tsw.WriteCommentLinef("unhandled RHS count in select assignment case: %d", len(comm.Rhs))
110
+ }
111
+ case *ast.ExprStmt:
112
+ // This is a simple receive: case <-ch:
113
+ if unaryExpr, ok := comm.X.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
114
+ c.tsw.WriteLiterally("isSend: false,")
115
+ c.tsw.WriteLine("")
116
+ c.tsw.WriteLiterally("channel: ")
117
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // The channel expression
118
+ return fmt.Errorf("failed to write channel expression in select receive case: %w", err)
119
+ }
120
+ c.tsw.WriteLiterally(",")
121
+ c.tsw.WriteLine("")
122
+ } else {
123
+ c.tsw.WriteCommentLinef("unhandled expression in select case: %T", comm.X)
124
+ }
125
+ case *ast.SendStmt:
126
+ // This is a send operation: case ch <- v:
127
+ c.tsw.WriteLiterally("isSend: true,")
128
+ c.tsw.WriteLine("")
129
+ c.tsw.WriteLiterally("channel: ")
130
+ if err := c.WriteValueExpr(comm.Chan); err != nil { // The channel expression
131
+ return fmt.Errorf("failed to write channel expression in select send case: %w", err)
132
+ }
133
+ c.tsw.WriteLiterally(",")
134
+ c.tsw.WriteLine("")
135
+ c.tsw.WriteLiterally("value: ")
136
+ if err := c.WriteValueExpr(comm.Value); err != nil { // The value expression
137
+ return fmt.Errorf("failed to write value expression in select send case: %w", err)
138
+ }
139
+ c.tsw.WriteLiterally(",")
140
+ c.tsw.WriteLine("")
141
+ default:
142
+ c.tsw.WriteCommentLinef("unhandled comm statement in select case: %T", comm)
143
+ }
144
+
145
+ // Add the onSelected handler to execute the case body after the select resolves
146
+ c.tsw.WriteLiterally("onSelected: async (result) => {") // Mark as async because case body might contain await
147
+ c.tsw.Indent(1)
148
+ c.tsw.WriteLine("")
149
+
150
+ // Handle assignment for channel receives if needed (inside the onSelected handler)
151
+ if assignStmt, ok := commClause.Comm.(*ast.AssignStmt); ok {
152
+ // This is a receive operation with assignment
153
+ if len(assignStmt.Lhs) == 1 {
154
+ // Simple receive: case v := <-ch:
155
+ valIdent, ok := assignStmt.Lhs[0].(*ast.Ident)
156
+ if ok && valIdent.Name != "_" { // Check for blank identifier
157
+ c.tsw.WriteLiterally("const ")
158
+ c.WriteIdent(valIdent, false)
159
+ c.tsw.WriteLiterally(" = result.value")
160
+ c.tsw.WriteLine("")
161
+ }
162
+ } else if len(assignStmt.Lhs) == 2 {
163
+ // Receive with ok: case v, ok := <-ch:
164
+ valIdent, valOk := assignStmt.Lhs[0].(*ast.Ident)
165
+ okIdent, okOk := assignStmt.Lhs[1].(*ast.Ident)
166
+
167
+ if valOk && valIdent.Name != "_" {
168
+ c.tsw.WriteLiterally("const ")
169
+ c.WriteIdent(valIdent, false)
170
+ c.tsw.WriteLiterally(" = result.value")
171
+ c.tsw.WriteLine("")
172
+ }
173
+
174
+ if okOk && okIdent.Name != "_" {
175
+ c.tsw.WriteLiterally("const ")
176
+ c.WriteIdent(okIdent, false)
177
+ c.tsw.WriteLiterally(" = result.ok")
178
+ c.tsw.WriteLine("")
179
+ }
180
+ }
181
+ }
182
+ // Note: Simple receive (case <-ch:) and send (case ch <- v:) don't require assignment here,
183
+ // as the operation was already performed by selectReceive/selectSend and the result is in 'result'.
184
+
185
+ // Write the case body
186
+ for _, bodyStmt := range commClause.Body {
187
+ if err := c.WriteStmt(bodyStmt); err != nil {
188
+ return fmt.Errorf("failed to write statement in select case body (onSelected): %w", err)
189
+ }
190
+ }
191
+
192
+ c.tsw.Indent(-1)
193
+ c.tsw.WriteLine("}") // Close onSelected handler
194
+ c.tsw.Indent(-1)
195
+ c.tsw.WriteLiterally("},") // Close SelectCase object and add comma
196
+ c.tsw.WriteLine("")
197
+
198
+ } else {
199
+ return errors.Errorf("unknown statement in select body: %T", stmt)
200
+ }
201
+ }
202
+
203
+ // Close the array literal and the selectStatement call
204
+ c.tsw.Indent(-1)
205
+ c.tsw.WriteLiterally("], ")
206
+ c.tsw.WriteLiterallyf("%t", hasDefault)
207
+ c.tsw.WriteLiterally(")")
208
+ c.tsw.WriteLine("")
209
+
210
+ return nil
211
+ }
@@ -0,0 +1,147 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+
7
+ "github.com/pkg/errors"
8
+ )
9
+
10
+ // WriteStmtTypeSwitch translates a Go `type switch` statement (`ast.TypeSwitchStmt`)
11
+ // into its TypeScript equivalent using the `$.typeSwitch` helper.
12
+ func (c *GoToTSCompiler) WriteStmtTypeSwitch(stmt *ast.TypeSwitchStmt) error {
13
+ // Outer block for scoping Init variable
14
+ if stmt.Init != nil {
15
+ c.tsw.WriteLine("{")
16
+ c.tsw.Indent(1)
17
+ if err := c.WriteStmt(stmt.Init); err != nil {
18
+ return fmt.Errorf("failed to write type switch init statement: %w", err)
19
+ }
20
+ }
21
+
22
+ // Extract the subject expression and case variable identifier
23
+ var subjectExpr ast.Expr
24
+ var caseVarIdent *ast.Ident // The variable in 'v := x.(type)'
25
+
26
+ switch assignNode := stmt.Assign.(type) {
27
+ case *ast.AssignStmt: // v := x.(type)
28
+ if len(assignNode.Lhs) != 1 || len(assignNode.Rhs) != 1 {
29
+ return errors.Errorf("TypeSwitchStmt AssignStmt: expected 1 LHS and 1 RHS, got %d and %d", len(assignNode.Lhs), len(assignNode.Rhs))
30
+ }
31
+ ident, ok := assignNode.Lhs[0].(*ast.Ident)
32
+ if !ok {
33
+ return errors.Errorf("TypeSwitchStmt AssignStmt LHS is not *ast.Ident: %T", assignNode.Lhs[0])
34
+ }
35
+ caseVarIdent = ident
36
+ typeAssert, ok := assignNode.Rhs[0].(*ast.TypeAssertExpr)
37
+ if !ok {
38
+ return errors.Errorf("TypeSwitchStmt AssignStmt RHS is not *ast.TypeAssertExpr: %T", assignNode.Rhs[0])
39
+ }
40
+ if typeAssert.Type != nil {
41
+ return errors.Errorf("TypeSwitchStmt AssignStmt TypeAssertExpr.Type is not nil")
42
+ }
43
+ subjectExpr = typeAssert.X
44
+ case *ast.ExprStmt: // x.(type)
45
+ typeAssert, ok := assignNode.X.(*ast.TypeAssertExpr)
46
+ if !ok {
47
+ return errors.Errorf("TypeSwitchStmt ExprStmt.X is not *ast.TypeAssertExpr: %T", assignNode.X)
48
+ }
49
+ if typeAssert.Type != nil {
50
+ return errors.Errorf("TypeSwitchStmt ExprStmt TypeAssertExpr.Type is not nil")
51
+ }
52
+ subjectExpr = typeAssert.X
53
+ default:
54
+ return errors.Errorf("unknown Assign type in TypeSwitchStmt: %T", stmt.Assign)
55
+ }
56
+
57
+ // Build the array of case configurations for $.typeSwitch
58
+ c.tsw.WriteLiterally("$.typeSwitch(")
59
+ if err := c.WriteValueExpr(subjectExpr); err != nil {
60
+ c.tsw.Indent(-1)
61
+ c.tsw.WriteLine("} // End TypeSwitchStmt due to error in subject")
62
+ return fmt.Errorf("failed to write subject expression in type switch: %w", err)
63
+ }
64
+
65
+ // case list
66
+ c.tsw.WriteLiterally(", [")
67
+
68
+ stmtBodyList := stmt.Body.List
69
+ var defaultCaseBody []ast.Stmt
70
+
71
+ for i, caseClauseStmt := range stmtBodyList {
72
+ caseClause, ok := caseClauseStmt.(*ast.CaseClause)
73
+ if !ok {
74
+ return errors.Errorf("unexpected statement in TypeSwitchStmt Body: not *ast.CaseClause but %T", caseClauseStmt)
75
+ }
76
+
77
+ if len(caseClause.List) == 0 { // Default case
78
+ defaultCaseBody = caseClause.Body
79
+ continue // Process default case after type cases
80
+ }
81
+
82
+ // Type case(s)
83
+ if i != 0 {
84
+ c.tsw.WriteLiterally(",")
85
+ c.tsw.WriteLine("")
86
+ }
87
+
88
+ c.tsw.WriteLiterally("{ types: [")
89
+ for j, typeExpr := range caseClause.List {
90
+ if j > 0 {
91
+ c.tsw.WriteLiterally(", ")
92
+ }
93
+ c.writeTypeDescription(typeExpr) // Descriptor for $.is or $.typeAssert
94
+ }
95
+ c.tsw.WriteLiterally("], body: (")
96
+
97
+ // Add case variable if it exists and is not '_'
98
+ if caseVarIdent != nil && caseVarIdent.Name != "_" {
99
+ c.WriteIdent(caseVarIdent, false) // isDeclaration = false for the parameter
100
+ // Note: TypeScript type inference should handle the parameter type based on the case type(s)
101
+ }
102
+
103
+ c.tsw.WriteLiterally(") => {")
104
+
105
+ caseClauseBody := caseClause.Body
106
+ if len(caseClauseBody) != 0 {
107
+ c.tsw.Indent(1)
108
+ c.tsw.WriteLine("")
109
+ }
110
+
111
+ for _, bodyStmt := range caseClauseBody {
112
+ if err := c.WriteStmt(bodyStmt); err != nil {
113
+ return fmt.Errorf("failed to write statement in type switch case body: %w", err)
114
+ }
115
+ }
116
+
117
+ if len(caseClauseBody) != 0 {
118
+ c.tsw.Indent(-1)
119
+ }
120
+ c.tsw.WriteLiterally("}") // Close case body function
121
+ c.tsw.WriteLiterally("}") // Close case object
122
+ }
123
+
124
+ c.tsw.WriteLiterally("]") // Close cases array
125
+
126
+ // Add default case function if it exists
127
+ if len(defaultCaseBody) != 0 {
128
+ c.tsw.WriteLiterally(", () => {")
129
+ c.tsw.Indent(1)
130
+ c.tsw.WriteLine("")
131
+ for _, bodyStmt := range defaultCaseBody {
132
+ if err := c.WriteStmt(bodyStmt); err != nil {
133
+ return fmt.Errorf("failed to write statement in type switch default case body: %w", err)
134
+ }
135
+ }
136
+ c.tsw.Indent(-1)
137
+ c.tsw.WriteLiterally("}") // Close default case function
138
+ }
139
+
140
+ c.tsw.WriteLine(")") // Close $.typeSwitch call
141
+ if stmt.Init != nil {
142
+ c.tsw.Indent(-1)
143
+ c.tsw.WriteLine("}") // Close outer block
144
+ }
145
+
146
+ return nil
147
+ }