goscript 0.0.21 → 0.0.22

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,263 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/types"
7
+ "strings"
8
+
9
+ // types provides type information for Go types.
10
+ "github.com/pkg/errors"
11
+ )
12
+
13
+ // WriteSpec is a dispatcher function that translates a Go specification node
14
+ // (`ast.Spec`) into its TypeScript equivalent. It handles different types of
15
+ // specifications found within `GenDecl` (general declarations):
16
+ // - `ast.ImportSpec` (import declarations): Delegates to `WriteImportSpec`.
17
+ // - `ast.ValueSpec` (variable or constant declarations): Delegates to `WriteValueSpec`.
18
+ // - `ast.TypeSpec` (type definitions like structs, interfaces): Delegates to `WriteTypeSpec`.
19
+ // If an unknown specification type is encountered, it returns an error.
20
+ func (c *GoToTSCompiler) WriteSpec(a ast.Spec) error {
21
+ switch d := a.(type) {
22
+ case *ast.ImportSpec:
23
+ c.WriteImportSpec(d)
24
+ case *ast.ValueSpec:
25
+ if err := c.WriteValueSpec(d); err != nil {
26
+ return err
27
+ }
28
+ case *ast.TypeSpec:
29
+ if err := c.WriteTypeSpec(d); err != nil {
30
+ return err
31
+ }
32
+ default:
33
+ return fmt.Errorf("unknown spec type: %T", a)
34
+ }
35
+ return nil
36
+ }
37
+
38
+ func (c *GoToTSCompiler) getEmbeddedFieldKeyName(fieldType types.Type) string {
39
+ trueType := fieldType
40
+ if ptr, isPtr := trueType.(*types.Pointer); isPtr {
41
+ trueType = ptr.Elem()
42
+ }
43
+
44
+ if named, isNamed := trueType.(*types.Named); isNamed {
45
+ return named.Obj().Name()
46
+ } else {
47
+ // Fallback for unnamed embedded types, though less common for structs
48
+ fieldKeyName := strings.Title(trueType.String()) // Simple heuristic
49
+ if dotIndex := strings.LastIndex(fieldKeyName, "."); dotIndex != -1 {
50
+ fieldKeyName = fieldKeyName[dotIndex+1:]
51
+ }
52
+ return fieldKeyName
53
+ }
54
+ }
55
+
56
+ func (c *GoToTSCompiler) writeGetterSetter(fieldName string, fieldType types.Type, doc, comment *ast.CommentGroup) {
57
+ fieldTypeStr := c.getTypeString(fieldType)
58
+
59
+ // Generate getter
60
+ if doc != nil {
61
+ c.WriteDoc(doc)
62
+ }
63
+ if comment != nil {
64
+ c.WriteDoc(comment)
65
+ }
66
+ c.tsw.WriteLinef("public get %s(): %s {", fieldName, fieldTypeStr)
67
+ c.tsw.Indent(1)
68
+ c.tsw.WriteLinef("return this._fields.%s.value", fieldName)
69
+ c.tsw.Indent(-1)
70
+ c.tsw.WriteLine("}")
71
+
72
+ // Generate setter (no comments)
73
+ c.tsw.WriteLinef("public set %s(value: %s) {", fieldName, fieldTypeStr)
74
+ c.tsw.Indent(1)
75
+ c.tsw.WriteLinef("this._fields.%s.value = value", fieldName)
76
+ c.tsw.Indent(-1)
77
+ c.tsw.WriteLine("}")
78
+ c.tsw.WriteLine("")
79
+ }
80
+
81
+ func (c *GoToTSCompiler) writeBoxedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
82
+ c.tsw.WriteLiterally(fieldName)
83
+ c.tsw.WriteLiterally(": $.box(")
84
+
85
+ if isEmbedded {
86
+ if _, isPtr := fieldType.(*types.Pointer); isPtr {
87
+ c.tsw.WriteLiterallyf("init?.%s ?? null", fieldName)
88
+ } else {
89
+ typeForNew := fieldName
90
+ c.tsw.WriteLiterallyf("new %s(init?.%s)", typeForNew, fieldName)
91
+ }
92
+ } else {
93
+ isStructValueType := false
94
+ var structTypeNameForClone string
95
+ if named, ok := fieldType.(*types.Named); ok {
96
+ if _, isStruct := named.Underlying().(*types.Struct); isStruct {
97
+ isStructValueType = true
98
+ structTypeNameForClone = named.Obj().Name()
99
+ }
100
+ }
101
+
102
+ if isStructValueType {
103
+ c.tsw.WriteLiterallyf("init?.%s?.clone() ?? new %s()", fieldName, structTypeNameForClone)
104
+ } else {
105
+ c.tsw.WriteLiterallyf("init?.%s ?? ", fieldName)
106
+ c.WriteZeroValueForType(fieldType)
107
+ }
108
+ }
109
+
110
+ c.tsw.WriteLiterally(")")
111
+ }
112
+
113
+ func (c *GoToTSCompiler) writeClonedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
114
+ c.tsw.WriteLiterally(fieldName)
115
+ c.tsw.WriteLiterally(": $.box(")
116
+
117
+ if isEmbedded {
118
+ isPointerToStruct := false
119
+ trueType := fieldType
120
+ if ptr, isPtr := trueType.(*types.Pointer); isPtr {
121
+ trueType = ptr.Elem()
122
+ isPointerToStruct = true
123
+ }
124
+
125
+ if named, isNamed := trueType.(*types.Named); isNamed {
126
+ _, isUnderlyingStruct := named.Underlying().(*types.Struct)
127
+ if isUnderlyingStruct && !isPointerToStruct { // Is a value struct
128
+ c.tsw.WriteLiterallyf("this._fields.%s.value.clone()", fieldName)
129
+ } else { // Is a pointer to a struct, or not a struct
130
+ c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
131
+ }
132
+ } else {
133
+ c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
134
+ }
135
+ } else {
136
+ isValueTypeStruct := false
137
+ if named, ok := fieldType.(*types.Named); ok {
138
+ if _, isStruct := named.Underlying().(*types.Struct); isStruct {
139
+ isValueTypeStruct = true
140
+ }
141
+ }
142
+
143
+ if isValueTypeStruct {
144
+ c.tsw.WriteLiterallyf("this._fields.%s.value?.clone() ?? null", fieldName)
145
+ } else {
146
+ c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
147
+ }
148
+ }
149
+
150
+ c.tsw.WriteLiterally(")")
151
+ }
152
+
153
+ // WriteTypeSpec writes the type specification to the output.
154
+ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
155
+ if a.Doc != nil {
156
+ c.WriteDoc(a.Doc)
157
+ }
158
+ if a.Comment != nil {
159
+ c.WriteDoc(a.Comment)
160
+ }
161
+
162
+ switch t := a.Type.(type) {
163
+ case *ast.StructType:
164
+ return c.WriteStructTypeSpec(a, t)
165
+ case *ast.InterfaceType:
166
+ return c.WriteInterfaceTypeSpec(a, t)
167
+ default:
168
+ // type alias
169
+ c.tsw.WriteLiterally("type ")
170
+ if err := c.WriteValueExpr(a.Name); err != nil {
171
+ return err
172
+ }
173
+ c.tsw.WriteLiterally(" = ")
174
+ c.WriteTypeExpr(a.Type) // The aliased type
175
+ c.tsw.WriteLine(";")
176
+ }
177
+ return nil
178
+ }
179
+
180
+ // WriteInterfaceTypeSpec writes the TypeScript type for a Go interface type.
181
+ func (c *GoToTSCompiler) WriteInterfaceTypeSpec(a *ast.TypeSpec, t *ast.InterfaceType) error {
182
+ c.tsw.WriteLiterally("type ")
183
+ if err := c.WriteValueExpr(a.Name); err != nil {
184
+ return err
185
+ }
186
+ c.tsw.WriteLiterally(" = ")
187
+ // Get the types.Interface from the ast.InterfaceType.
188
+ // For an interface definition like `type MyInterface interface { M() }`,
189
+ // 't' is the *ast.InterfaceType representing `interface { M() }`.
190
+ // TypesInfo.TypeOf(t) will give the *types.Interface.
191
+ goType := c.pkg.TypesInfo.TypeOf(t)
192
+ if goType == nil {
193
+ return errors.Errorf("could not get type for interface AST node for %s", a.Name.Name)
194
+ }
195
+ ifaceType, ok := goType.(*types.Interface)
196
+ if !ok {
197
+ return errors.Errorf("expected *types.Interface, got %T for %s when processing interface literal", goType, a.Name.Name)
198
+ }
199
+ c.WriteInterfaceType(ifaceType, t) // Pass the *ast.InterfaceType for comment fetching
200
+ c.tsw.WriteLine("")
201
+
202
+ // Add code to register the interface with the runtime system
203
+ interfaceName := a.Name.Name
204
+ c.tsw.WriteLine("")
205
+ c.tsw.WriteLinef("$.registerInterfaceType(")
206
+ c.tsw.WriteLinef(" '%s',", interfaceName)
207
+ c.tsw.WriteLinef(" null, // Zero value for interface is null")
208
+
209
+ // Collect methods for the interface type
210
+ var interfaceMethods []*types.Func
211
+ if ifaceType != nil { // ifaceType is *types.Interface
212
+ for i := range ifaceType.NumExplicitMethods() {
213
+ interfaceMethods = append(interfaceMethods, ifaceType.ExplicitMethod(i))
214
+ }
215
+ // TODO: Handle embedded interface methods if necessary for full signature collection.
216
+ // For now, explicit methods are covered.
217
+ }
218
+ c.tsw.WriteLiterally(" [")
219
+ c.writeMethodSignatures(interfaceMethods)
220
+ c.tsw.WriteLiterally("]")
221
+ c.tsw.WriteLine("")
222
+
223
+ c.tsw.WriteLinef(");")
224
+
225
+ return nil
226
+ }
227
+
228
+ // WriteImportSpec translates a Go import specification (`ast.ImportSpec`)
229
+ // into a TypeScript import statement.
230
+ //
231
+ // It extracts the Go import path (e.g., `"path/to/pkg"`) and determines the
232
+ // import alias/name for TypeScript. If the Go import has an explicit name
233
+ // (e.g., `alias "path/to/pkg"`), that alias is used. Otherwise, the package
234
+ // name is derived from the Go path.
235
+ //
236
+ // The Go path is then translated to a TypeScript module path using
237
+ // `translateGoPathToTypescriptPath`.
238
+ //
239
+ // Finally, it writes a TypeScript import statement like `import * as alias from "typescript/path/to/pkg";`
240
+ // and records the import details in `c.analysis.Imports` for later use (e.g.,
241
+ // resolving qualified identifiers).
242
+ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
243
+ if a.Doc != nil {
244
+ c.WriteDoc(a.Doc)
245
+ }
246
+ if a.Comment != nil {
247
+ c.WriteDoc(a.Comment)
248
+ }
249
+
250
+ goPath := a.Path.Value[1 : len(a.Path.Value)-1]
251
+ impName := packageNameFromGoPath(goPath)
252
+ if a.Name != nil && a.Name.Name != "" {
253
+ impName = a.Name.Name
254
+ }
255
+
256
+ importPath := translateGoPathToTypescriptPath(goPath)
257
+ c.analysis.Imports[impName] = &fileImport{
258
+ importPath: importPath,
259
+ importVars: make(map[string]struct{}),
260
+ }
261
+
262
+ c.tsw.WriteImport(impName, importPath+"/index.js")
263
+ }
@@ -0,0 +1,411 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/token"
7
+ "go/types"
8
+
9
+ "github.com/pkg/errors"
10
+ )
11
+
12
+ // WriteStmtAssign translates a Go assignment statement (`ast.AssignStmt`) into
13
+ // its TypeScript equivalent. It handles various forms of Go assignments:
14
+ //
15
+ // 1. **Multi-variable assignment from a single function call** (e.g., `a, b := fn()`):
16
+ // - Uses `writeMultiVarAssignFromCall` to generate `let [a, b] = fn_ts();`.
17
+ //
18
+ // 2. **Type assertion with comma-ok** (e.g., `val, ok := expr.(Type)`):
19
+ // - Uses `writeTypeAssertion` to generate `let { value: val, ok: ok } = $.typeAssert<Type_ts>(expr_ts, 'TypeName');`.
20
+ //
21
+ // 3. **Map lookup with comma-ok** (e.g., `val, ok := myMap[key]`):
22
+ // - Uses `writeMapLookupWithExists` to generate separate assignments for `val`
23
+ // (using `myMap_ts.get(key_ts) ?? zeroValue`) and `ok` (using `myMap_ts.has(key_ts)`).
24
+ //
25
+ // 4. **Channel receive with comma-ok** (e.g., `val, ok := <-ch`):
26
+ // - Uses `writeChannelReceiveWithOk` to generate `let { value: val, ok: ok } = await ch_ts.receiveWithOk();`.
27
+ //
28
+ // 5. **Discarded channel receive** (e.g., `<-ch` on RHS, no LHS vars):
29
+ // - Translates to `await ch_ts.receive();`.
30
+ //
31
+ // 6. **Single assignment** (e.g., `x = y`, `x := y`, `*p = y`, `x[i] = y`):
32
+ // - Uses `writeAssignmentCore` which handles:
33
+ // - Blank identifier `_` on LHS (evaluates RHS for side effects).
34
+ // - Assignment to dereferenced pointer `*p = val` -> `p_ts!.value = val_ts`.
35
+ // - Short declaration `x := y`: `let x = y_ts;`. If `x` is boxed, `let x: $.Box<T> = $.box(y_ts);`.
36
+ // - Regular assignment `x = y`, including compound assignments like `x += y`.
37
+ // - Assignment to map index `m[k] = v` using `$.mapSet`.
38
+ // - Struct value assignment `s1 = s2` becomes `s1 = s2.clone()` if `s2` is a struct.
39
+ //
40
+ // 7. **Multi-variable assignment with multiple RHS values** (e.g., `a, b = x, y`):
41
+ // - Uses `writeAssignmentCore` to generate `[a,b] = [x_ts, y_ts];` (or `let [a,b] = ...` for `:=`).
42
+ //
43
+ // The function ensures that the number of LHS and RHS expressions matches for
44
+ // most cases, erroring if they don't, except for specifically handled patterns
45
+ // like multi-assign from single call or discarded channel receive.
46
+ // It correctly applies `let` for `:=` (define) tokens and handles boxing and
47
+ // cloning semantics based on type information and analysis.
48
+ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
49
+ // writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
50
+ writeMultiVarAssignFromCall := func(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
51
+ // For token.DEFINE (:=), we need to check if any of the variables are already declared
52
+ // In Go, := can be used for redeclaration if at least one variable is new
53
+ if tok == token.DEFINE {
54
+ // For token.DEFINE (:=), we need to handle variable declarations differently
55
+ // In Go, := can redeclare existing variables if at least one variable is new
56
+
57
+ // First, identify which variables are new vs existing
58
+ newVars := make([]bool, len(lhs))
59
+ anyNewVars := false
60
+ allNewVars := true
61
+
62
+ // For multi-variable assignments with :=, we need to determine which variables
63
+ // are already in scope and which are new declarations
64
+ for i, lhsExpr := range lhs {
65
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
66
+ // In Go, variables declared with := can be redeclared if at least one is new
67
+ // For TypeScript, we need to separately declare new variables
68
+
69
+ // Check if this variable is already in scope
70
+ // - If the variable is used elsewhere before this point, it's existing
71
+ // - Otherwise, it's a new variable being declared
72
+ isNew := true
73
+
74
+ // Check if the variable is used elsewhere in the code
75
+ if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
76
+ // If it's in Uses, it's referenced elsewhere, so it exists
77
+ isNew = false
78
+ allNewVars = false
79
+ }
80
+
81
+ newVars[i] = isNew
82
+ if isNew {
83
+ anyNewVars = true
84
+ }
85
+ }
86
+ }
87
+
88
+ // Get function return types if available
89
+ var resultTypes []*types.Var
90
+ if callExpr.Fun != nil {
91
+ if funcType, ok := c.pkg.TypesInfo.TypeOf(callExpr.Fun).Underlying().(*types.Signature); ok {
92
+ if funcType.Results() != nil && funcType.Results().Len() > 0 {
93
+ for i := 0; i < funcType.Results().Len(); i++ {
94
+ resultTypes = append(resultTypes, funcType.Results().At(i))
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ if allNewVars && anyNewVars {
101
+ c.tsw.WriteLiterally("let [")
102
+
103
+ for i, lhsExpr := range lhs {
104
+ if i != 0 {
105
+ c.tsw.WriteLiterally(", ")
106
+ }
107
+
108
+ if ident, ok := lhsExpr.(*ast.Ident); ok {
109
+ if ident.Name == "_" {
110
+ // For underscore variables, use empty slots in destructuring pattern
111
+ } else {
112
+ c.WriteIdent(ident, false)
113
+ }
114
+ } else {
115
+ c.WriteValueExpr(lhsExpr)
116
+ }
117
+ }
118
+ c.tsw.WriteLiterally("] = ")
119
+ c.WriteValueExpr(callExpr)
120
+ c.tsw.WriteLine("")
121
+ return nil
122
+ } else if anyNewVars {
123
+ // If only some variables are new, declare them separately before the assignment
124
+ // Declare each new variable with appropriate type
125
+ for i, lhsExpr := range lhs {
126
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && newVars[i] {
127
+ c.tsw.WriteLiterally("let ")
128
+ c.WriteIdent(ident, false)
129
+
130
+ // Add type annotation if we have type information
131
+ if i < len(resultTypes) {
132
+ c.tsw.WriteLiterally(": ")
133
+ c.WriteGoType(resultTypes[i].Type())
134
+ }
135
+
136
+ c.tsw.WriteLine("")
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ // First, collect all the selector expressions to identify variables that need to be initialized
143
+ hasSelectors := false
144
+ for _, lhsExpr := range lhs {
145
+ if _, ok := lhsExpr.(*ast.SelectorExpr); ok {
146
+ hasSelectors = true
147
+ break
148
+ }
149
+ }
150
+
151
+ // If we have selector expressions, we need to ensure variables are initialized
152
+ // before the destructuring assignment
153
+ if hasSelectors {
154
+ c.tsw.WriteLiterally("{")
155
+ c.tsw.WriteLine("")
156
+
157
+ // Write a temporary variable to hold the function call result
158
+ c.tsw.WriteLiterally(" const _tmp = ")
159
+ if err := c.WriteValueExpr(callExpr); err != nil {
160
+ return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
161
+ }
162
+ c.tsw.WriteLine("")
163
+
164
+ for i, lhsExpr := range lhs {
165
+ // Skip underscore variables
166
+ if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name == "_" {
167
+ continue
168
+ }
169
+
170
+ // Write the LHS with indentation
171
+ c.tsw.WriteLiterally(" ")
172
+ if ident, ok := lhsExpr.(*ast.Ident); ok {
173
+ c.WriteIdent(ident, false)
174
+ } else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
175
+ if err := c.WriteValueExpr(selectorExpr); err != nil {
176
+ return fmt.Errorf("failed to write selector expression in LHS: %w", err)
177
+ }
178
+ } else {
179
+ return errors.Errorf("unhandled LHS expression in assignment: %T", lhsExpr)
180
+ }
181
+
182
+ // Write the assignment
183
+ c.tsw.WriteLiterallyf(" = _tmp[%d]", i)
184
+ // Always add a newline after each assignment
185
+ c.tsw.WriteLine("")
186
+ }
187
+
188
+ // Close the block scope
189
+ c.tsw.WriteLiterally("}")
190
+ c.tsw.WriteLine("")
191
+
192
+ return nil
193
+ }
194
+
195
+ // For simple cases without selector expressions, use array destructuring
196
+ c.tsw.WriteLiterally("[")
197
+
198
+ for i, lhsExpr := range lhs {
199
+ if i != 0 {
200
+ c.tsw.WriteLiterally(", ")
201
+ }
202
+
203
+ if ident, ok := lhsExpr.(*ast.Ident); ok {
204
+ // For underscore variables, use empty slots in destructuring pattern
205
+ if ident.Name != "_" {
206
+ c.WriteIdent(ident, false)
207
+ }
208
+ } else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
209
+ // Handle selector expressions (e.g., a.b) by using WriteValueExpr
210
+ if err := c.WriteValueExpr(selectorExpr); err != nil {
211
+ return fmt.Errorf("failed to write selector expression in LHS: %w", err)
212
+ }
213
+ } else {
214
+ // Should not happen for valid Go code in this context, but handle defensively
215
+ return errors.Errorf("unhandled LHS expression in destructuring: %T", lhsExpr)
216
+ }
217
+ }
218
+ c.tsw.WriteLiterally("] = ")
219
+
220
+ // Write the right-hand side (the function call)
221
+ if err := c.WriteValueExpr(callExpr); err != nil {
222
+ return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
223
+ }
224
+
225
+ c.tsw.WriteLine("")
226
+ return nil
227
+ }
228
+
229
+ // writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
230
+ // Note: We don't use WriteIndexExpr here because we need to handle .has() and .get() separately
231
+ writeMapLookupWithExists := func(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
232
+ // First check that we have exactly two LHS expressions (value and exists)
233
+ if len(lhs) != 2 {
234
+ return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
235
+ }
236
+
237
+ // Check for blank identifiers and get variable names
238
+ valueIsBlank := false
239
+ existsIsBlank := false
240
+ var valueName string
241
+ var existsName string
242
+
243
+ if valIdent, ok := lhs[0].(*ast.Ident); ok {
244
+ if valIdent.Name == "_" {
245
+ valueIsBlank = true
246
+ } else {
247
+ valueName = valIdent.Name
248
+ }
249
+ } else {
250
+ return fmt.Errorf("unhandled LHS expression type for value in map comma-ok: %T", lhs[0])
251
+ }
252
+
253
+ if existsIdent, ok := lhs[1].(*ast.Ident); ok {
254
+ if existsIdent.Name == "_" {
255
+ existsIsBlank = true
256
+ } else {
257
+ existsName = existsIdent.Name
258
+ }
259
+ } else {
260
+ return fmt.Errorf("unhandled LHS expression type for exists in map comma-ok: %T", lhs[1])
261
+ }
262
+
263
+ // Declare variables if using := and not blank
264
+ if tok == token.DEFINE {
265
+ if !valueIsBlank {
266
+ c.tsw.WriteLiterally("let ")
267
+ c.tsw.WriteLiterally(valueName)
268
+ // TODO: Add type annotation based on map value type
269
+ c.tsw.WriteLine("")
270
+ }
271
+ if !existsIsBlank {
272
+ c.tsw.WriteLiterally("let ")
273
+ c.tsw.WriteLiterally(existsName)
274
+ c.tsw.WriteLiterally(": boolean") // exists is always boolean
275
+ c.tsw.WriteLine("")
276
+ }
277
+ }
278
+
279
+ // Assign 'exists'
280
+ if !existsIsBlank {
281
+ c.tsw.WriteLiterally(existsName)
282
+ c.tsw.WriteLiterally(" = ")
283
+ c.tsw.WriteLiterally("$.mapHas(")
284
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
285
+ return err
286
+ }
287
+ c.tsw.WriteLiterally(", ")
288
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
289
+ return err
290
+ }
291
+ c.tsw.WriteLiterally(")")
292
+ c.tsw.WriteLine("")
293
+ }
294
+
295
+ // Assign 'value'
296
+ if !valueIsBlank {
297
+ c.tsw.WriteLiterally(valueName)
298
+ c.tsw.WriteLiterally(" = ")
299
+ c.tsw.WriteLiterally("$.mapGet(")
300
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
301
+ return err
302
+ }
303
+ c.tsw.WriteLiterally(", ")
304
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
305
+ return err
306
+ }
307
+ c.tsw.WriteLiterally(", ")
308
+ // Write the zero value for the map's value type
309
+ if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
310
+ if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
311
+ c.WriteZeroValueForType(mapType.Elem())
312
+ } else {
313
+ // Fallback zero value if type info is missing or not a map
314
+ c.tsw.WriteLiterally("null")
315
+ }
316
+ } else {
317
+ c.tsw.WriteLiterally("null")
318
+ }
319
+ c.tsw.WriteLiterally(")")
320
+ c.tsw.WriteLine("")
321
+ } else if existsIsBlank {
322
+ // If both are blank, still evaluate for side effects (though .has/.get are usually pure)
323
+ // We add a ; otherwise TypeScript thinks we are invoking a function.
324
+ c.tsw.WriteLiterally(";(") // Wrap in parens to make it an expression statement
325
+ c.tsw.WriteLiterally("$.mapHas(")
326
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
327
+ return err
328
+ }
329
+ c.tsw.WriteLiterally(", ")
330
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
331
+ return err
332
+ }
333
+ c.tsw.WriteLiterally("), ") // Evaluate .has
334
+ c.tsw.WriteLiterally("$.mapGet(")
335
+ if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
336
+ return err
337
+ }
338
+ c.tsw.WriteLiterally(", ")
339
+ if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
340
+ return err
341
+ }
342
+ c.tsw.WriteLiterally(", null))") // Evaluate .get with null as default
343
+ c.tsw.WriteLine("")
344
+ }
345
+
346
+ return nil
347
+ }
348
+
349
+ // Handle multi-variable assignment from a single expression.
350
+ if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
351
+ rhsExpr := exp.Rhs[0]
352
+ if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
353
+ return c.writeTypeAssert(exp.Lhs, typeAssertExpr, exp.Tok)
354
+ } else if indexExpr, ok := rhsExpr.(*ast.IndexExpr); ok {
355
+ // Check if this is a map lookup (comma-ok idiom)
356
+ if len(exp.Lhs) == 2 {
357
+ // Get the type of the indexed expression
358
+ if c.pkg != nil && c.pkg.TypesInfo != nil {
359
+ tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]
360
+ if ok {
361
+ // Check if it's a map type
362
+ if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
363
+ return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
364
+ }
365
+ }
366
+ }
367
+ }
368
+ } else if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
369
+ // Handle val, ok := <-channel
370
+ if len(exp.Lhs) == 2 {
371
+ return c.writeChannelReceiveWithOk(exp.Lhs, unaryExpr, exp.Tok)
372
+ }
373
+ // If LHS count is not 2, fall through to error or other handling
374
+ } else if callExpr, ok := rhsExpr.(*ast.CallExpr); ok {
375
+ return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
376
+ }
377
+ // If none of the specific multi-assign patterns match, fall through to the error check below
378
+ }
379
+
380
+ // Ensure LHS and RHS have the same length for valid Go code in these cases
381
+ if len(exp.Lhs) != len(exp.Rhs) {
382
+ return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
383
+ }
384
+
385
+ // Handle multi-variable assignment (e.g., swaps) using writeAssignmentCore
386
+ if len(exp.Lhs) > 1 {
387
+ // Need to handle := for multi-variable declarations
388
+ if exp.Tok == token.DEFINE {
389
+ c.tsw.WriteLiterally("let ") // Use let for multi-variable declarations
390
+ }
391
+ // For multi-variable assignments, we've already added the "let" if needed
392
+ if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, false); err != nil {
393
+ return err
394
+ }
395
+ c.tsw.WriteLine("") // Add newline after the statement
396
+ return nil
397
+ }
398
+
399
+ // Handle single assignment using writeAssignmentCore
400
+ if len(exp.Lhs) == 1 {
401
+ addDeclaration := exp.Tok == token.DEFINE
402
+ if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, addDeclaration); err != nil {
403
+ return err
404
+ }
405
+ c.tsw.WriteLine("") // Add newline after the statement
406
+ return nil
407
+ }
408
+
409
+ // Should not reach here if LHS/RHS counts are valid and handled
410
+ return fmt.Errorf("unhandled assignment case")
411
+ }