goscript 0.0.20 → 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,142 @@
1
+ package compiler
2
+
3
+ import "go/token"
4
+
5
+ // goToTypescriptPrimitives maps Go built-in primitive type names (as strings)
6
+ // to their corresponding TypeScript type names. This map is used by
7
+ // `GoBuiltinToTypescript` for direct type name translation.
8
+ //
9
+ // Key mappings include:
10
+ // - `bool` -> `boolean`
11
+ // - `string` -> `string`
12
+ // - `int`, `int8`, `int16`, `int32`, `rune` (alias for int32) -> `number`
13
+ // - `uint`, `uint8` (`byte`), `uint16`, `uint32` -> `number`
14
+ // - `int64`, `uint64` -> `bigint` (requires ES2020+ TypeScript target)
15
+ // - `float32`, `float64` -> `number`
16
+ //
17
+ // This mapping assumes a target environment similar to GOOS=js, GOARCH=wasm,
18
+ // where Go's `int` and `uint` are 32-bit and fit within TypeScript's `number`.
19
+ var goToTypescriptPrimitives = map[string]string{
20
+ // Boolean
21
+ "bool": "boolean",
22
+
23
+ // Strings
24
+ "string": "string",
25
+
26
+ // Signed Integers
27
+ "int": "number",
28
+ "int8": "number",
29
+ "int16": "number",
30
+ "int32": "number",
31
+ "rune": "number", // alias for int32
32
+ "int64": "bigint", // Requires TypeScript target >= ES2020
33
+
34
+ // Unsigned Integers
35
+ "uint": "number",
36
+ "uint8": "number", // byte is an alias for uint8
37
+ "byte": "number",
38
+ "uint16": "number",
39
+ "uint32": "number",
40
+ "uint64": "bigint", // Requires TypeScript target >= ES2020
41
+
42
+ // Floating Point Numbers
43
+ "float32": "number",
44
+ "float64": "number",
45
+ }
46
+
47
+ func isPrimitiveType(name string) bool {
48
+ _, ok := goToTypescriptPrimitives[name]
49
+ return ok
50
+ }
51
+
52
+ // GoBuiltinToTypescript translates a Go built-in primitive type name (string)
53
+ // to its TypeScript equivalent. It uses the `goToTypescriptPrimitives` map
54
+ // for the conversion.
55
+ // It returns the TypeScript type name and `true` if the Go type name is found
56
+ // in the map. Otherwise, it returns an empty string and `false`.
57
+ // This function only handles primitive types listed in the map; composite types
58
+ // or custom types are not processed here.
59
+ func GoBuiltinToTypescript(typeName string) (string, bool) {
60
+ val, ok := goToTypescriptPrimitives[typeName]
61
+ return val, ok
62
+ }
63
+
64
+ // tokenMap provides a mapping from Go `token.Token` types (representing operators
65
+ // and punctuation) to their corresponding string representations in TypeScript.
66
+ // This map is used by `TokenToTs` to translate Go operators during expression
67
+ // and statement compilation.
68
+ //
69
+ // Examples:
70
+ // - `token.ADD` (Go `+`) -> `"+"` (TypeScript `+`)
71
+ // - `token.LAND` (Go `&&`) -> `"&&"` (TypeScript `&&`)
72
+ // - `token.ASSIGN` (Go `=`) -> `"="` (TypeScript `=`)
73
+ // - `token.DEFINE` (Go `:=`) -> `"="` (TypeScript `=`, as `let` is handled separately)
74
+ //
75
+ // Some tokens like `token.ARROW` (channel send/receive) are handled specially
76
+ // in their respective expression/statement writers and might not be directly mapped here.
77
+ // Bitwise AND NOT (`&^=`) is also mapped but may require specific runtime support if not directly translatable.
78
+ var tokenMap = map[token.Token]string{
79
+ token.ADD: "+",
80
+ token.SUB: "-",
81
+ token.MUL: "*",
82
+ token.QUO: "/",
83
+ token.REM: "%",
84
+ token.AND: "&",
85
+ token.OR: "|",
86
+ token.XOR: "^",
87
+ token.SHL: "<<",
88
+ token.SHR: ">>",
89
+
90
+ token.ADD_ASSIGN: "+=",
91
+ token.SUB_ASSIGN: "-=",
92
+ token.MUL_ASSIGN: "*=",
93
+ token.QUO_ASSIGN: "/=",
94
+ token.REM_ASSIGN: "%=",
95
+
96
+ token.AND_ASSIGN: "&=",
97
+ token.OR_ASSIGN: "|=",
98
+ token.XOR_ASSIGN: "^=", // TODO: check if this works
99
+ token.SHL_ASSIGN: "<<=",
100
+ token.SHR_ASSIGN: ">>=",
101
+ token.AND_NOT_ASSIGN: "&^=",
102
+
103
+ token.LAND: "&&",
104
+ token.LOR: "||",
105
+ // token.ARROW: ""
106
+ token.INC: "++",
107
+ token.DEC: "--",
108
+ token.EQL: "==",
109
+ token.LSS: "<",
110
+ token.GTR: ">",
111
+ token.ASSIGN: "=",
112
+ token.NOT: "!",
113
+
114
+ token.NEQ: "!=",
115
+ token.LEQ: "<=",
116
+ token.GEQ: ">=",
117
+ token.DEFINE: "=", // :=
118
+ token.ELLIPSIS: "...", // TODO
119
+
120
+ token.LPAREN: "(",
121
+ token.LBRACK: "[",
122
+ token.LBRACE: "{",
123
+ token.COMMA: ",",
124
+ token.PERIOD: ".",
125
+
126
+ token.RPAREN: ")",
127
+ token.RBRACK: "]",
128
+ token.RBRACE: "}",
129
+ token.SEMICOLON: ";",
130
+ token.COLON: ":",
131
+ }
132
+
133
+ // TokenToTs converts a Go `token.Token` (representing an operator or punctuation)
134
+ // into its corresponding TypeScript string representation using the `tokenMap`.
135
+ // It returns the TypeScript string and `true` if the token is found in the map.
136
+ // Otherwise, it returns an empty string and `false`. This function is essential
137
+ // for translating expressions involving operators (e.g., arithmetic, logical,
138
+ // assignment operators).
139
+ func TokenToTs(tok token.Token) (string, bool) {
140
+ t, ok := tokenMap[tok]
141
+ return t, ok
142
+ }
@@ -5,153 +5,19 @@ import (
5
5
  "go/ast"
6
6
  "go/types"
7
7
  "strings"
8
-
9
- // types provides type information for Go types.
10
- "github.com/pkg/errors"
11
8
  )
12
9
 
13
- func (c *GoToTSCompiler) getEmbeddedFieldKeyName(fieldType types.Type) string {
14
- trueType := fieldType
15
- if ptr, isPtr := trueType.(*types.Pointer); isPtr {
16
- trueType = ptr.Elem()
17
- }
18
-
19
- if named, isNamed := trueType.(*types.Named); isNamed {
20
- return named.Obj().Name()
21
- } else {
22
- // Fallback for unnamed embedded types, though less common for structs
23
- fieldKeyName := strings.Title(trueType.String()) // Simple heuristic
24
- if dotIndex := strings.LastIndex(fieldKeyName, "."); dotIndex != -1 {
25
- fieldKeyName = fieldKeyName[dotIndex+1:]
26
- }
27
- return fieldKeyName
28
- }
29
- }
30
-
31
- func (c *GoToTSCompiler) writeGetterSetter(fieldName string, fieldType types.Type, doc, comment *ast.CommentGroup) {
32
- fieldTypeStr := c.getTypeString(fieldType)
33
-
34
- // Generate getter
35
- if doc != nil {
36
- c.WriteDoc(doc)
37
- }
38
- if comment != nil {
39
- c.WriteDoc(comment)
40
- }
41
- c.tsw.WriteLinef("public get %s(): %s {", fieldName, fieldTypeStr)
42
- c.tsw.Indent(1)
43
- c.tsw.WriteLinef("return this._fields.%s.value", fieldName)
44
- c.tsw.Indent(-1)
45
- c.tsw.WriteLine("}")
46
-
47
- // Generate setter (no comments)
48
- c.tsw.WriteLinef("public set %s(value: %s) {", fieldName, fieldTypeStr)
49
- c.tsw.Indent(1)
50
- c.tsw.WriteLinef("this._fields.%s.value = value", fieldName)
51
- c.tsw.Indent(-1)
52
- c.tsw.WriteLine("}")
53
- c.tsw.WriteLine("")
54
- }
55
-
56
- func (c *GoToTSCompiler) writeBoxedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
57
- c.tsw.WriteLiterally(fieldName)
58
- c.tsw.WriteLiterally(": $.box(")
59
-
60
- if isEmbedded {
61
- if _, isPtr := fieldType.(*types.Pointer); isPtr {
62
- c.tsw.WriteLiterallyf("init?.%s ?? null", fieldName)
63
- } else {
64
- typeForNew := fieldName
65
- c.tsw.WriteLiterallyf("new %s(init?.%s)", typeForNew, fieldName)
66
- }
67
- } else {
68
- isStructValueType := false
69
- var structTypeNameForClone string
70
- if named, ok := fieldType.(*types.Named); ok {
71
- if _, isStruct := named.Underlying().(*types.Struct); isStruct {
72
- isStructValueType = true
73
- structTypeNameForClone = named.Obj().Name()
74
- }
75
- }
76
-
77
- if isStructValueType {
78
- c.tsw.WriteLiterallyf("init?.%s?.clone() ?? new %s()", fieldName, structTypeNameForClone)
79
- } else {
80
- c.tsw.WriteLiterallyf("init?.%s ?? ", fieldName)
81
- c.WriteZeroValueForType(fieldType)
82
- }
83
- }
84
-
85
- c.tsw.WriteLiterally(")")
86
- }
87
-
88
- func (c *GoToTSCompiler) writeClonedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
89
- c.tsw.WriteLiterally(fieldName)
90
- c.tsw.WriteLiterally(": $.box(")
91
-
92
- if isEmbedded {
93
- isPointerToStruct := false
94
- trueType := fieldType
95
- if ptr, isPtr := trueType.(*types.Pointer); isPtr {
96
- trueType = ptr.Elem()
97
- isPointerToStruct = true
98
- }
99
-
100
- if named, isNamed := trueType.(*types.Named); isNamed {
101
- _, isUnderlyingStruct := named.Underlying().(*types.Struct)
102
- if isUnderlyingStruct && !isPointerToStruct { // Is a value struct
103
- c.tsw.WriteLiterallyf("this._fields.%s.value.clone()", fieldName)
104
- } else { // Is a pointer to a struct, or not a struct
105
- c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
106
- }
107
- } else {
108
- c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
109
- }
110
- } else {
111
- isValueTypeStruct := false
112
- if named, ok := fieldType.(*types.Named); ok {
113
- if _, isStruct := named.Underlying().(*types.Struct); isStruct {
114
- isValueTypeStruct = true
115
- }
116
- }
117
-
118
- if isValueTypeStruct {
119
- c.tsw.WriteLiterallyf("this._fields.%s.value?.clone() ?? null", fieldName)
120
- } else {
121
- c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
122
- }
123
- }
124
-
125
- c.tsw.WriteLiterally(")")
126
- }
127
-
128
- // WriteTypeSpec writes the type specification to the output.
129
- func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
130
- if a.Doc != nil {
131
- c.WriteDoc(a.Doc)
132
- }
133
- if a.Comment != nil {
134
- c.WriteDoc(a.Comment)
135
- }
136
-
137
- switch t := a.Type.(type) {
138
- case *ast.StructType:
139
- return c.WriteStructTypeSpec(a, t)
140
- case *ast.InterfaceType:
141
- return c.WriteInterfaceTypeSpec(a, t)
142
- default:
143
- // type alias
144
- c.tsw.WriteLiterally("type ")
145
- if err := c.WriteValueExpr(a.Name); err != nil {
146
- return err
147
- }
148
- c.tsw.WriteLiterally(" = ")
149
- c.WriteTypeExpr(a.Type) // The aliased type
150
- c.tsw.WriteLine(";")
151
- }
152
- return nil
153
- }
154
-
10
+ // WriteStructTypeSpec generates the TypeScript class definition for a Go struct type.
11
+ // It handles the generation of:
12
+ // - The class declaration.
13
+ // - Getters and setters for all fields (both direct and embedded).
14
+ // - The internal `_fields` property, which stores field values in `$.Box` containers
15
+ // to maintain Go's value semantics.
16
+ // - A constructor that initializes the `_fields` and allows partial initialization.
17
+ // - A `clone` method for creating a deep copy of the struct instance.
18
+ // - Methods defined directly on the struct.
19
+ // - Wrapper methods for promoted fields and methods from embedded structs,
20
+ // ensuring correct access and behavior.
155
21
  func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType) error {
156
22
  c.tsw.WriteLiterally("class ")
157
23
  if err := c.WriteValueExpr(a.Name); err != nil {
@@ -188,7 +54,7 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
188
54
  }
189
55
 
190
56
  // Generate getters and setters for EMBEDDED struct fields themselves
191
- for i := 0; i < underlyingStruct.NumFields(); i++ {
57
+ for i := range underlyingStruct.NumFields() {
192
58
  field := underlyingStruct.Field(i)
193
59
  if field.Anonymous() {
194
60
  fieldKeyName := c.getEmbeddedFieldKeyName(field.Type())
@@ -221,31 +87,35 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
221
87
  c.tsw.WriteLine("")
222
88
  c.tsw.WriteLinef("constructor(init?: Partial<%s>) {", flattenedInitType)
223
89
  c.tsw.Indent(1)
224
- c.tsw.WriteLine("this._fields = {")
225
- c.tsw.Indent(1)
90
+ c.tsw.WriteLiterally("this._fields = {")
226
91
 
227
92
  numFields := underlyingStruct.NumFields()
228
- for i := 0; i < numFields; i++ {
229
- field := underlyingStruct.Field(i)
230
- fieldType := field.Type()
231
- var fieldKeyName string
232
- if field.Anonymous() {
233
- fieldKeyName = c.getEmbeddedFieldKeyName(field.Type())
234
- } else {
235
- fieldKeyName = field.Name()
236
- }
93
+ if numFields != 0 {
94
+ c.tsw.WriteLine("")
95
+ c.tsw.Indent(1)
96
+
97
+ for i := range numFields {
98
+ field := underlyingStruct.Field(i)
99
+ fieldType := field.Type()
100
+ var fieldKeyName string
101
+ if field.Anonymous() {
102
+ fieldKeyName = c.getEmbeddedFieldKeyName(field.Type())
103
+ } else {
104
+ fieldKeyName = field.Name()
105
+ }
237
106
 
238
- c.writeBoxedFieldInitializer(fieldKeyName, fieldType, field.Anonymous())
107
+ c.writeBoxedFieldInitializer(fieldKeyName, fieldType, field.Anonymous())
239
108
 
240
- if i < numFields-1 {
241
- c.tsw.WriteLine(",")
242
- } else {
243
- c.tsw.WriteLine("")
109
+ if i < numFields-1 {
110
+ c.tsw.WriteLine(",")
111
+ } else {
112
+ c.tsw.WriteLine("")
113
+ }
244
114
  }
115
+ c.tsw.Indent(-1)
245
116
  }
246
-
247
- c.tsw.Indent(-1)
248
117
  c.tsw.WriteLine("}")
118
+
249
119
  c.tsw.Indent(-1)
250
120
  c.tsw.WriteLine("}")
251
121
  c.tsw.WriteLine("")
@@ -257,7 +127,7 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
257
127
  c.tsw.WriteLine("cloned._fields = {")
258
128
  c.tsw.Indent(1)
259
129
 
260
- for i := 0; i < numFields; i++ {
130
+ for i := range numFields {
261
131
  field := underlyingStruct.Field(i)
262
132
  fieldType := field.Type()
263
133
  var fieldKeyName string
@@ -307,7 +177,7 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
307
177
  seenPromotedFields := make(map[string]bool)
308
178
  directMethods := make(map[string]bool)
309
179
  // Populate directMethods (methods defined directly on this struct type)
310
- for i := 0; i < goStructType.NumMethods(); i++ {
180
+ for i := range goStructType.NumMethods() {
311
181
  method := goStructType.Method(i)
312
182
  sig := method.Type().(*types.Signature)
313
183
  if sig.Recv() != nil {
@@ -322,7 +192,7 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
322
192
  }
323
193
  }
324
194
 
325
- for i := 0; i < underlyingStruct.NumFields(); i++ {
195
+ for i := range underlyingStruct.NumFields() {
326
196
  field := underlyingStruct.Field(i)
327
197
  if !field.Anonymous() {
328
198
  continue
@@ -383,7 +253,7 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
383
253
 
384
254
  // Promoted methods
385
255
  embeddedMethodSet := types.NewMethodSet(embeddedFieldType) // Use original field type for method set
386
- for k := 0; k < embeddedMethodSet.Len(); k++ {
256
+ for k := range embeddedMethodSet.Len() {
387
257
  methodSelection := embeddedMethodSet.At(k)
388
258
  method := methodSelection.Obj().(*types.Func)
389
259
  methodName := method.Name()
@@ -456,51 +326,57 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
456
326
  }
457
327
  }
458
328
 
459
- // Add code to register the type with the runtime system
329
+ // Add code to register the type with the runtime type system
460
330
  c.tsw.WriteLine("")
461
331
  c.tsw.WriteLinef("// Register this type with the runtime type system")
462
332
  c.tsw.WriteLinef("static __typeInfo = $.registerStructType(")
463
333
  c.tsw.WriteLinef(" '%s',", className)
464
334
  c.tsw.WriteLinef(" new %s(),", className)
465
- c.tsw.WriteLinef(" new Set([%s]),", c.collectMethodNames(className)) // collectMethodNames should ideally consider promoted methods too
466
- c.tsw.WriteLinef(" %s", className)
467
- c.tsw.WriteLinef(");")
468
-
469
- c.tsw.Indent(-1)
470
- c.tsw.WriteLine("}")
471
- return nil
472
- }
473
-
474
- // WriteInterfaceTypeSpec writes the TypeScript type for a Go interface type.
475
- func (c *GoToTSCompiler) WriteInterfaceTypeSpec(a *ast.TypeSpec, t *ast.InterfaceType) error {
476
- c.tsw.WriteLiterally("type ")
477
- if err := c.WriteValueExpr(a.Name); err != nil {
478
- return err
479
- }
480
- c.tsw.WriteLiterally(" = ")
481
- // Get the types.Interface from the ast.InterfaceType.
482
- // For an interface definition like `type MyInterface interface { M() }`,
483
- // 't' is the *ast.InterfaceType representing `interface { M() }`.
484
- // TypesInfo.TypeOf(t) will give the *types.Interface.
485
- goType := c.pkg.TypesInfo.TypeOf(t)
486
- if goType == nil {
487
- return errors.Errorf("could not get type for interface AST node for %s", a.Name.Name)
488
- }
489
- ifaceType, ok := goType.(*types.Interface)
490
- if !ok {
491
- return errors.Errorf("expected *types.Interface, got %T for %s when processing interface literal", goType, a.Name.Name)
335
+ c.tsw.WriteLiterally(" [")
336
+ // Collect methods for the struct type
337
+ var structMethods []*types.Func
338
+ for i := range goStructType.NumMethods() {
339
+ method := goStructType.Method(i)
340
+ // Ensure it's a method directly on this type (not promoted here, promotion handled separately)
341
+ // Check if receiver is *T or T where T is goStructType
342
+ sig := method.Type().(*types.Signature)
343
+ recv := sig.Recv().Type()
344
+ if ptr, ok := recv.(*types.Pointer); ok {
345
+ recv = ptr.Elem()
346
+ }
347
+ if namedRecv, ok := recv.(*types.Named); ok && namedRecv.Obj() == goStructType.Obj() {
348
+ structMethods = append(structMethods, method)
349
+ }
492
350
  }
493
- c.WriteInterfaceType(ifaceType, t) // Pass the *ast.InterfaceType for comment fetching
351
+ c.writeMethodSignatures(structMethods)
352
+ c.tsw.WriteLiterally("],")
494
353
  c.tsw.WriteLine("")
495
354
 
496
- // Add code to register the interface with the runtime system
497
- interfaceName := a.Name.Name
355
+ c.tsw.WriteLinef(" %s,", className)
356
+ // Add field type information for type assertions
357
+ c.tsw.WriteLiterally(" {")
358
+ firstField := true
359
+ for i := 0; i < underlyingStruct.NumFields(); i++ {
360
+ field := underlyingStruct.Field(i)
361
+ var fieldKeyName string
362
+ if field.Anonymous() {
363
+ fieldKeyName = c.getEmbeddedFieldKeyName(field.Type())
364
+ } else {
365
+ fieldKeyName = field.Name()
366
+ }
367
+ // fieldTsType := c.getTypeString(field.Type())
368
+ if !firstField {
369
+ c.tsw.WriteLiterally(", ")
370
+ }
371
+ firstField = false
372
+ c.tsw.WriteLiterallyf("%q: ", fieldKeyName)
373
+ c.writeTypeInfoObject(field.Type()) // Use writeTypeInfoObject for field types
374
+ }
375
+ c.tsw.WriteLiterally("}")
498
376
  c.tsw.WriteLine("")
499
- c.tsw.WriteLinef("$.registerInterfaceType(")
500
- c.tsw.WriteLinef(" '%s',", interfaceName)
501
- c.tsw.WriteLinef(" null, // Zero value for interface is null")
502
- c.tsw.WriteLinef(" new Set([%s]),", c.collectInterfaceMethods(t))
503
377
  c.tsw.WriteLinef(");")
504
378
 
379
+ c.tsw.Indent(-1)
380
+ c.tsw.WriteLine("}")
505
381
  return nil
506
382
  }
@@ -0,0 +1,194 @@
1
+ package compiler
2
+
3
+ import (
4
+ "go/ast"
5
+ "go/token"
6
+
7
+ "github.com/pkg/errors"
8
+ )
9
+
10
+ // WriteValueSpec translates a Go value specification (`ast.ValueSpec`),
11
+ // which represents `var` or `const` declarations, into TypeScript `let`
12
+ // declarations.
13
+ //
14
+ // For single variable declarations (`var x T = val` or `var x = val` or `var x T`):
15
+ // - It determines if the variable `x` needs to be boxed (e.g., if its address is taken)
16
+ // using `c.analysis.NeedsBoxed(obj)`.
17
+ // - If boxed: `let x: $.Box<T_ts> = $.box(initializer_ts_or_zero_ts);`
18
+ // The type annotation is `$.Box<T_ts>`, and the initializer is wrapped in `$.box()`.
19
+ // - If not boxed: `let x: T_ts = initializer_ts_or_zero_ts;`
20
+ // The type annotation is `T_ts`. If the initializer is `&unboxedVar`, it becomes `$.box(unboxedVar_ts)`.
21
+ // If the RHS is a struct value, `.clone()` is applied to maintain Go's value semantics.
22
+ // - If no initializer is provided, the TypeScript zero value (from `WriteZeroValueForType`)
23
+ // is used.
24
+ // - Type `T` (or `T_ts`) is obtained from `obj.Type()` and translated via `WriteGoType`.
25
+ //
26
+ // For multiple variable declarations (`var a, b = val1, val2` or `a, b := val1, val2`):
27
+ // - It uses TypeScript array destructuring: `let [a, b] = [val1_ts, val2_ts];`.
28
+ // - If initialized from a single multi-return function call (`a, b := func()`),
29
+ // it becomes `let [a, b] = func_ts();`.
30
+ // - If no initializers are provided, it defaults to `let [a,b] = []` (with a TODO
31
+ // to assign correct individual zero values).
32
+ //
33
+ // Documentation comments associated with the `ValueSpec` are preserved.
34
+ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
35
+ if a.Doc != nil {
36
+ c.WriteDoc(a.Doc)
37
+ }
38
+ if a.Comment != nil {
39
+ c.WriteDoc(a.Comment)
40
+ }
41
+
42
+ // Handle single variable declaration
43
+ if len(a.Names) == 1 {
44
+ name := a.Names[0]
45
+ obj := c.pkg.TypesInfo.Defs[name]
46
+ if obj == nil {
47
+ return errors.Errorf("could not resolve type: %v", name)
48
+ }
49
+
50
+ goType := obj.Type()
51
+ needsBox := c.analysis.NeedsBoxed(obj) // Check if address is taken
52
+
53
+ // Start declaration
54
+ c.tsw.WriteLiterally("let ")
55
+ c.tsw.WriteLiterally(name.Name)
56
+ c.tsw.WriteLiterally(": ")
57
+
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)
67
+ }
68
+
69
+ // Write initializer
70
+ c.tsw.WriteLiterally(" = ")
71
+ hasInitializer := len(a.Values) > 0
72
+ var initializerExpr ast.Expr
73
+ if hasInitializer {
74
+ initializerExpr = a.Values[0]
75
+
76
+ // Special case for nil pointer to struct type: (*struct{})(nil)
77
+ if callExpr, isCallExpr := initializerExpr.(*ast.CallExpr); isCallExpr {
78
+ if starExpr, isStarExpr := callExpr.Fun.(*ast.StarExpr); isStarExpr {
79
+ if _, isStructType := starExpr.X.(*ast.StructType); isStructType {
80
+ // Check if the argument is nil
81
+ if len(callExpr.Args) == 1 {
82
+ if nilIdent, isIdent := callExpr.Args[0].(*ast.Ident); isIdent && nilIdent.Name == "nil" {
83
+ c.tsw.WriteLiterally("null")
84
+ return nil
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ if needsBox {
93
+ // Boxed variable: let v: Box<T> = $.box(init_or_zero);
94
+ c.tsw.WriteLiterally("$.box(")
95
+ if hasInitializer {
96
+ // Write the compiled initializer expression normally
97
+ if err := c.WriteValueExpr(initializerExpr); err != nil {
98
+ return err
99
+ }
100
+ } else {
101
+ // No initializer, box the zero value
102
+ c.WriteZeroValueForType(goType)
103
+ }
104
+ c.tsw.WriteLiterally(")")
105
+ } else {
106
+ // Unboxed variable: let v: T = init_or_zero;
107
+ if hasInitializer {
108
+ // Handle &v initializer specifically for unboxed variables
109
+ if unaryExpr, isUnary := initializerExpr.(*ast.UnaryExpr); isUnary && unaryExpr.Op == token.AND {
110
+ // Initializer is &v
111
+ // Check if v is boxed
112
+ needsBoxOperand := false
113
+ unaryExprXIdent, ok := unaryExpr.X.(*ast.Ident)
114
+ if ok {
115
+ innerObj := c.pkg.TypesInfo.Uses[unaryExprXIdent]
116
+ needsBoxOperand = innerObj != nil && c.analysis.NeedsBoxed(innerObj)
117
+ }
118
+
119
+ // If v is boxed, assign the box itself (v)
120
+ // If v is not boxed, assign $.box(v)
121
+ if needsBoxOperand {
122
+ // special handling: do not write .value here.
123
+ c.WriteIdent(unaryExprXIdent, false)
124
+ } else {
125
+ // &unboxedVar -> $.box(unboxedVar)
126
+ c.tsw.WriteLiterally("$.box(")
127
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Write 'v'
128
+ return err
129
+ }
130
+ c.tsw.WriteLiterally(")")
131
+ }
132
+ } else {
133
+ // Regular initializer, clone if needed
134
+ if shouldApplyClone(c.pkg, initializerExpr) {
135
+ if err := c.WriteValueExpr(initializerExpr); err != nil {
136
+ return err
137
+ }
138
+ c.tsw.WriteLiterally(".clone()")
139
+ } else {
140
+ if err := c.WriteValueExpr(initializerExpr); err != nil {
141
+ return err
142
+ }
143
+ }
144
+ }
145
+ } else {
146
+ // No initializer, use the zero value directly
147
+ c.WriteZeroValueForType(goType)
148
+ }
149
+ }
150
+ c.tsw.WriteLine("") // Finish the declaration line
151
+ return nil
152
+ }
153
+
154
+ // --- Multi-variable declaration (existing logic seems okay, but less common for pointers) ---
155
+ c.tsw.WriteLiterally("let ")
156
+ c.tsw.WriteLiterally("[") // Use array destructuring for multi-assign
157
+ for i, name := range a.Names {
158
+ if i != 0 {
159
+ c.tsw.WriteLiterally(", ")
160
+ }
161
+ c.tsw.WriteLiterally(name.Name)
162
+ // TODO: Add type annotations for multi-var declarations if possible/needed
163
+ }
164
+ c.tsw.WriteLiterally("]")
165
+ if len(a.Values) > 0 {
166
+ // TODO: handle other kinds of assignment += -= etc.
167
+ c.tsw.WriteLiterally(" = ")
168
+ if len(a.Values) == 1 && len(a.Names) > 1 {
169
+ // Assign from a single multi-return value
170
+ if err := c.WriteValueExpr(a.Values[0]); err != nil {
171
+ return err
172
+ }
173
+ } else {
174
+ // Assign from multiple values
175
+ c.tsw.WriteLiterally("[")
176
+ for i, val := range a.Values {
177
+ if i != 0 {
178
+ c.tsw.WriteLiterally(", ")
179
+ }
180
+ if err := c.WriteValueExpr(val); err != nil { // Initializers are values
181
+ return err
182
+ }
183
+ }
184
+ c.tsw.WriteLiterally("]")
185
+ }
186
+ } else {
187
+ // No initializer, assign default values (complex for multi-assign)
188
+ // For simplicity, assign default array based on context (needs improvement)
189
+ c.tsw.WriteLiterally(" = []") // Placeholder
190
+ // TODO: Assign correct zero values based on types
191
+ }
192
+ c.tsw.WriteLine("") // Use WriteLine instead of WriteLine(";")
193
+ return nil
194
+ }