goscript 0.0.84 → 0.1.0

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.
Files changed (188) hide show
  1. package/README.md +13 -1
  2. package/cmd/goscript/cmd_compile.go +70 -69
  3. package/cmd/goscript/cmd_compile_test.go +79 -0
  4. package/cmd/goscript/main.go +10 -5
  5. package/compiler/compile-request.go +218 -0
  6. package/compiler/compiler.go +16 -1336
  7. package/compiler/compliance_test.go +196 -0
  8. package/compiler/config.go +6 -13
  9. package/compiler/diagnostic.go +70 -0
  10. package/compiler/index.test.ts +28 -28
  11. package/compiler/index.ts +40 -72
  12. package/compiler/lowered-program.go +132 -0
  13. package/compiler/lowering.go +3576 -0
  14. package/compiler/override-registry.go +422 -0
  15. package/compiler/override-registry_test.go +207 -0
  16. package/compiler/package-graph.go +231 -0
  17. package/compiler/package-graph_test.go +281 -0
  18. package/compiler/result.go +13 -0
  19. package/compiler/runtime-contract.go +279 -0
  20. package/compiler/runtime-contract_test.go +90 -0
  21. package/compiler/semantic-model-types.go +110 -0
  22. package/compiler/semantic-model.go +922 -0
  23. package/compiler/semantic-model_test.go +416 -0
  24. package/compiler/service.go +133 -0
  25. package/compiler/skeleton_test.go +1145 -0
  26. package/compiler/typescript-emitter.go +663 -0
  27. package/compiler/wasm/compile.go +2 -3
  28. package/compiler/wasm/compile_test.go +29 -0
  29. package/compiler/wasm_api.go +10 -159
  30. package/dist/compiler/index.d.ts +1 -3
  31. package/dist/compiler/index.js +31 -55
  32. package/dist/compiler/index.js.map +1 -1
  33. package/dist/gs/builtin/builtin.d.ts +13 -0
  34. package/dist/gs/builtin/builtin.js +23 -0
  35. package/dist/gs/builtin/builtin.js.map +1 -1
  36. package/dist/gs/builtin/channel.d.ts +3 -3
  37. package/dist/gs/builtin/channel.js.map +1 -1
  38. package/dist/gs/builtin/hostio.d.ts +15 -1
  39. package/dist/gs/builtin/hostio.js +134 -49
  40. package/dist/gs/builtin/hostio.js.map +1 -1
  41. package/dist/gs/builtin/index.d.ts +1 -0
  42. package/dist/gs/builtin/index.js +1 -0
  43. package/dist/gs/builtin/index.js.map +1 -1
  44. package/dist/gs/builtin/slice.d.ts +1 -1
  45. package/dist/gs/builtin/slice.js.map +1 -1
  46. package/dist/gs/builtin/type.d.ts +11 -0
  47. package/dist/gs/builtin/type.js +55 -1
  48. package/dist/gs/builtin/type.js.map +1 -1
  49. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  50. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  51. package/dist/gs/bytes/reader.gs.js.map +1 -1
  52. package/dist/gs/context/context.js.map +1 -1
  53. package/dist/gs/crypto/rand/index.d.ts +5 -0
  54. package/dist/gs/crypto/rand/index.js +77 -0
  55. package/dist/gs/crypto/rand/index.js.map +1 -0
  56. package/dist/gs/encoding/json/index.d.ts +3 -0
  57. package/dist/gs/encoding/json/index.js +160 -0
  58. package/dist/gs/encoding/json/index.js.map +1 -0
  59. package/dist/gs/fmt/fmt.js.map +1 -1
  60. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  61. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  62. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  63. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  64. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  65. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  66. package/dist/gs/go/scanner/index.d.ts +29 -0
  67. package/dist/gs/go/scanner/index.js +120 -0
  68. package/dist/gs/go/scanner/index.js.map +1 -0
  69. package/dist/gs/go/token/index.d.ts +31 -0
  70. package/dist/gs/go/token/index.js +82 -0
  71. package/dist/gs/go/token/index.js.map +1 -0
  72. package/dist/gs/internal/abi/index.js.map +1 -1
  73. package/dist/gs/io/fs/fs.js.map +1 -1
  74. package/dist/gs/io/fs/readdir.js.map +1 -1
  75. package/dist/gs/io/fs/readfile.js.map +1 -1
  76. package/dist/gs/io/fs/stat.js.map +1 -1
  77. package/dist/gs/io/fs/sub.js.map +1 -1
  78. package/dist/gs/io/io.js.map +1 -1
  79. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  80. package/dist/gs/os/error.gs.js +2 -4
  81. package/dist/gs/os/error.gs.js.map +1 -1
  82. package/dist/gs/os/exec.gs.js.map +1 -1
  83. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  84. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  85. package/dist/gs/os/root_js.gs.js.map +1 -1
  86. package/dist/gs/os/tempfile.gs.js +66 -9
  87. package/dist/gs/os/tempfile.gs.js.map +1 -1
  88. package/dist/gs/os/types.gs.js.map +1 -1
  89. package/dist/gs/os/types_js.gs.js +9 -9
  90. package/dist/gs/os/types_js.gs.js.map +1 -1
  91. package/dist/gs/os/types_unix.gs.js.map +1 -1
  92. package/dist/gs/path/filepath/match.js.map +1 -1
  93. package/dist/gs/path/match.js.map +1 -1
  94. package/dist/gs/path/path.js.map +1 -1
  95. package/dist/gs/reflect/index.d.ts +2 -2
  96. package/dist/gs/reflect/index.js +1 -1
  97. package/dist/gs/reflect/index.js.map +1 -1
  98. package/dist/gs/reflect/map.js.map +1 -1
  99. package/dist/gs/reflect/type.d.ts +2 -1
  100. package/dist/gs/reflect/type.js +85 -14
  101. package/dist/gs/reflect/type.js.map +1 -1
  102. package/dist/gs/reflect/types.js.map +1 -1
  103. package/dist/gs/reflect/visiblefields.js.map +1 -1
  104. package/dist/gs/runtime/runtime.js.map +1 -1
  105. package/dist/gs/sort/sort.gs.js.map +1 -1
  106. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  107. package/dist/gs/strconv/quote.gs.js.map +1 -1
  108. package/dist/gs/strings/builder.js.map +1 -1
  109. package/dist/gs/strings/reader.js.map +1 -1
  110. package/dist/gs/strings/replace.js.map +1 -1
  111. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  112. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  113. package/dist/gs/sync/sync.d.ts +1 -0
  114. package/dist/gs/sync/sync.js +12 -0
  115. package/dist/gs/sync/sync.js.map +1 -1
  116. package/dist/gs/time/time.js.map +1 -1
  117. package/dist/gs/unicode/unicode.js.map +1 -1
  118. package/go.mod +2 -2
  119. package/gs/builtin/builtin.ts +27 -0
  120. package/gs/builtin/hostio.test.ts +177 -0
  121. package/gs/builtin/hostio.ts +171 -56
  122. package/gs/builtin/index.ts +1 -0
  123. package/gs/builtin/runtime-contract.test.ts +230 -0
  124. package/gs/builtin/type.ts +84 -1
  125. package/gs/crypto/rand/index.test.ts +32 -0
  126. package/gs/crypto/rand/index.ts +90 -0
  127. package/gs/crypto/rand/meta.json +5 -0
  128. package/gs/encoding/json/index.test.ts +65 -0
  129. package/gs/encoding/json/index.ts +186 -0
  130. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  131. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  132. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  133. package/gs/go/scanner/index.test.ts +50 -0
  134. package/gs/go/scanner/index.ts +157 -0
  135. package/gs/go/token/index.test.ts +21 -0
  136. package/gs/go/token/index.ts +120 -0
  137. package/gs/os/file_unix_js.test.ts +50 -0
  138. package/gs/os/meta.json +1 -2
  139. package/gs/os/tempfile.gs.test.ts +85 -0
  140. package/gs/os/tempfile.gs.ts +71 -11
  141. package/gs/os/types_js.gs.ts +9 -9
  142. package/gs/reflect/index.ts +1 -1
  143. package/gs/reflect/type.ts +106 -17
  144. package/gs/reflect/typefor.test.ts +75 -0
  145. package/gs/sync/sync.test.ts +24 -0
  146. package/gs/sync/sync.ts +12 -0
  147. package/package.json +13 -13
  148. package/compiler/analysis.go +0 -3475
  149. package/compiler/analysis_test.go +0 -338
  150. package/compiler/assignment.go +0 -580
  151. package/compiler/builtin_test.go +0 -92
  152. package/compiler/code-writer.go +0 -115
  153. package/compiler/compiler_test.go +0 -149
  154. package/compiler/composite-lit.go +0 -779
  155. package/compiler/config_test.go +0 -62
  156. package/compiler/constraint.go +0 -86
  157. package/compiler/decl.go +0 -801
  158. package/compiler/expr-call-async.go +0 -188
  159. package/compiler/expr-call-builtins.go +0 -208
  160. package/compiler/expr-call-helpers.go +0 -382
  161. package/compiler/expr-call-make.go +0 -318
  162. package/compiler/expr-call-type-conversion.go +0 -520
  163. package/compiler/expr-call.go +0 -413
  164. package/compiler/expr-selector.go +0 -343
  165. package/compiler/expr-star.go +0 -82
  166. package/compiler/expr-type.go +0 -442
  167. package/compiler/expr-value.go +0 -89
  168. package/compiler/expr.go +0 -773
  169. package/compiler/field.go +0 -183
  170. package/compiler/gs_dependencies_test.go +0 -298
  171. package/compiler/lit.go +0 -322
  172. package/compiler/output.go +0 -72
  173. package/compiler/primitive.go +0 -149
  174. package/compiler/protobuf.go +0 -697
  175. package/compiler/sanitize.go +0 -100
  176. package/compiler/spec-struct.go +0 -995
  177. package/compiler/spec-value.go +0 -540
  178. package/compiler/spec.go +0 -725
  179. package/compiler/stmt-assign.go +0 -664
  180. package/compiler/stmt-for.go +0 -266
  181. package/compiler/stmt-range.go +0 -475
  182. package/compiler/stmt-select.go +0 -262
  183. package/compiler/stmt-type-switch.go +0 -147
  184. package/compiler/stmt.go +0 -1308
  185. package/compiler/type-assert.go +0 -386
  186. package/compiler/type-info.go +0 -156
  187. package/compiler/type-utils.go +0 -207
  188. package/compiler/type.go +0 -892
@@ -1,664 +0,0 @@
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 variable referenced, `let x: $.VarRef<T> = $.varRef(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 varRefing and
48
- // cloning semantics based on type information and analysis.
49
- func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
50
- // Handle multi-variable assignment from a single expression.
51
- if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
52
- rhsExpr := exp.Rhs[0]
53
-
54
- // Check for protobuf method calls first
55
- if callExpr, ok := rhsExpr.(*ast.CallExpr); ok {
56
- // Handle protobuf MarshalVT: data, err := msg.MarshalVT()
57
- if len(exp.Lhs) == 2 && c.isProtobufMethodCall(callExpr, "MarshalVT") {
58
- err := c.writeProtobufMarshalAssignment(exp.Lhs, callExpr, exp.Tok)
59
- if err != nil {
60
- return err
61
- }
62
- return nil
63
- }
64
- // Handle protobuf MarshalJSON: data, err := msg.MarshalJSON()
65
- if len(exp.Lhs) == 2 && c.isProtobufMethodCall(callExpr, "MarshalJSON") {
66
- err := c.writeProtobufMarshalJSONAssignment(exp.Lhs, callExpr, exp.Tok)
67
- if err != nil {
68
- return err
69
- }
70
- return nil
71
- }
72
- // Handle general function calls that return multiple values
73
- return c.writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
74
- }
75
-
76
- if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
77
- return c.writeTypeAssert(exp.Lhs, typeAssertExpr, exp.Tok)
78
- } else if indexExpr, ok := rhsExpr.(*ast.IndexExpr); ok {
79
- // Check if this is a map lookup (comma-ok idiom)
80
- if len(exp.Lhs) == 2 {
81
- // Get the type of the indexed expression
82
- if c.pkg != nil && c.pkg.TypesInfo != nil {
83
- v, ok := c.pkg.TypesInfo.Types[indexExpr.X]
84
- if ok {
85
- // Check if it's a concrete map type
86
- if _, isMap := v.Type.Underlying().(*types.Map); isMap {
87
- return c.writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
88
- }
89
- // Check if it's a type parameter constrained to be a map type
90
- if typeParam, isTypeParam := v.Type.(*types.TypeParam); isTypeParam {
91
- constraint := typeParam.Constraint()
92
- if constraint != nil {
93
- underlying := constraint.Underlying()
94
- if iface, isInterface := underlying.(*types.Interface); isInterface {
95
- if hasMapConstraint(iface) {
96
- return c.writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
97
- }
98
- }
99
- }
100
- }
101
- }
102
- }
103
- }
104
- } else if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
105
- // Handle val, ok := <-channel
106
- if len(exp.Lhs) == 2 {
107
- return c.writeChannelReceiveWithOk(exp.Lhs, unaryExpr, exp.Tok)
108
- }
109
- // If LHS count is not 2, fall through to error or other handling
110
- }
111
- // If none of the specific multi-assign patterns match, fall through to the error check below
112
- }
113
-
114
- // Check for single-variable protobuf method calls before general assignment handling
115
- if len(exp.Lhs) == 1 && len(exp.Rhs) == 1 {
116
- if callExpr, ok := exp.Rhs[0].(*ast.CallExpr); ok {
117
- // Handle protobuf UnmarshalVT: err = out.UnmarshalVT(data)
118
- if c.isProtobufMethodCall(callExpr, "UnmarshalVT") {
119
- return c.writeProtobufUnmarshalAssignment(exp.Lhs, callExpr)
120
- }
121
- // Handle protobuf UnmarshalJSON: err = out.UnmarshalJSON(data)
122
- if c.isProtobufMethodCall(callExpr, "UnmarshalJSON") {
123
- return c.writeProtobufUnmarshalJSONAssignment(exp.Lhs, callExpr, exp.Tok)
124
- }
125
- }
126
- }
127
-
128
- // Ensure LHS and RHS have the same length for valid Go code in these cases
129
- if len(exp.Lhs) != len(exp.Rhs) {
130
- // Special case: allow multiple LHS with single RHS if RHS can produce multiple values
131
- // This handles cases like: x, y := getValue() where getValue() returns multiple values
132
- // or other expressions that can produce multiple values
133
- if len(exp.Rhs) == 1 {
134
- // Allow single RHS expressions that can produce multiple values:
135
- // - Function calls that return multiple values
136
- // - Type assertions with comma-ok
137
- // - Map lookups with comma-ok
138
- // - Channel receives with comma-ok
139
- // The Go type checker should have already verified this is valid
140
- rhsExpr := exp.Rhs[0]
141
- switch rhsExpr.(type) {
142
- case *ast.CallExpr, *ast.TypeAssertExpr, *ast.IndexExpr, *ast.UnaryExpr:
143
- // These expression types can potentially produce multiple values
144
- // Let the general assignment logic handle them
145
- default:
146
- return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
147
- }
148
- } else {
149
- return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
150
- }
151
- }
152
-
153
- // Handle multi-variable assignment (e.g., swaps) using writeAssignmentCore
154
- if len(exp.Lhs) > 1 {
155
- // Need to handle := for multi-variable declarations
156
- if exp.Tok == token.DEFINE {
157
- c.tsw.WriteLiterally("let ") // Use let for multi-variable declarations
158
- }
159
- // For multi-variable assignments, we've already added the "let" if needed
160
- if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, false); err != nil {
161
- return err
162
- }
163
- // Handle potential inline comment for multi-variable assignment
164
- c.writeInlineComment(exp)
165
- c.tsw.WriteLine("") // Add newline after the statement
166
- return nil
167
- }
168
-
169
- // Handle single assignment using writeAssignmentCore
170
- if len(exp.Lhs) == 1 {
171
- // Check for type shadowing (e.g., field := field{...})
172
- // In this case, we need to rename the variable to avoid TypeScript shadowing
173
- if nodeInfo := c.analysis.NodeData[exp]; nodeInfo != nil && nodeInfo.ShadowingInfo != nil {
174
- if lhsIdent, ok := exp.Lhs[0].(*ast.Ident); ok && lhsIdent.Name != "_" {
175
- if renamedVar, hasTypeShadow := nodeInfo.ShadowingInfo.TypeShadowedVars[lhsIdent.Name]; hasTypeShadow {
176
- if err := c.writeTypeShadowedAssignment(exp, lhsIdent.Name, renamedVar); err != nil {
177
- return err
178
- }
179
- c.writeInlineComment(exp)
180
- c.tsw.WriteLine("")
181
- return nil
182
- }
183
- }
184
- }
185
-
186
- addDeclaration := exp.Tok == token.DEFINE
187
- if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, addDeclaration); err != nil {
188
- return err
189
- }
190
- // Handle potential inline comment for single assignment
191
- c.writeInlineComment(exp)
192
- c.tsw.WriteLine("") // Add newline after the statement
193
- return nil
194
- }
195
-
196
- // Should not reach here if LHS/RHS counts are valid and handled
197
- return fmt.Errorf("unhandled assignment case")
198
- }
199
-
200
- // writeInlineComment checks for and writes any inline comments associated with the given AST node.
201
- // It is intended to be called immediately after writing the main statement/expression.
202
- func (c *GoToTSCompiler) writeInlineComment(node ast.Node) {
203
- if c.pkg == nil || c.pkg.Fset == nil || !node.End().IsValid() {
204
- return
205
- }
206
-
207
- file := c.pkg.Fset.File(node.End())
208
- if file == nil {
209
- return
210
- }
211
-
212
- endLine := file.Line(node.End())
213
- // Check comments associated *directly* with the node
214
- for _, cg := range c.analysis.Cmap[node] {
215
- if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > node.End() {
216
- commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
217
- c.tsw.WriteLiterally(" // " + commentText)
218
- return // Only write the first inline comment found
219
- }
220
- }
221
- }
222
-
223
- // writeLHSTarget writes an LHS target expression for assignment contexts.
224
- // It preserves the exact behavior used in WriteStmtAssign for selector, star, and index expressions,
225
- // and avoids adding .value on identifiers.
226
- func (c *GoToTSCompiler) writeLHSTarget(lhsExpr ast.Expr) error {
227
- switch t := lhsExpr.(type) {
228
- case *ast.Ident:
229
- // Caller should have handled blank identifiers; write name without .value
230
- c.WriteIdent(t, false)
231
- return nil
232
- case *ast.SelectorExpr:
233
- if err := c.WriteValueExpr(t); err != nil {
234
- return fmt.Errorf("failed to write selector expression in LHS: %w", err)
235
- }
236
- return nil
237
- case *ast.StarExpr:
238
- // Handle pointer dereference assignment: *p = value becomes p!.value = value
239
- // Write the pointer variable directly without using WriteValueExpr when it's an identifier
240
- switch operand := t.X.(type) {
241
- case *ast.Ident:
242
- c.WriteIdent(operand, false)
243
- default:
244
- if err := c.WriteValueExpr(t.X); err != nil {
245
- return fmt.Errorf("failed to write star expression X in LHS: %w", err)
246
- }
247
- }
248
- c.tsw.WriteLiterally("!.value")
249
- return nil
250
- case *ast.IndexExpr:
251
- if err := c.WriteValueExpr(t); err != nil {
252
- return fmt.Errorf("failed to write index expression in LHS: %w", err)
253
- }
254
- return nil
255
- default:
256
- return errors.Errorf("unhandled LHS expression in assignment: %T", lhsExpr)
257
- }
258
- }
259
-
260
- // lhsHasComplexTargets returns true if any LHS expression is a selector, star (dereference), or index expression.
261
- func (c *GoToTSCompiler) lhsHasComplexTargets(lhs []ast.Expr) bool {
262
- for _, e := range lhs {
263
- switch e.(type) {
264
- case *ast.SelectorExpr, *ast.StarExpr, *ast.IndexExpr:
265
- return true
266
- }
267
- }
268
- return false
269
- }
270
-
271
- // writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
272
- func (c *GoToTSCompiler) writeMultiVarAssignFromCall(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
273
- // For token.DEFINE (:=), we need to check if any of the variables are already declared
274
- // In Go, := can be used for redeclaration if at least one variable is new
275
- if tok == token.DEFINE {
276
- // For token.DEFINE (:=), we need to handle variable declarations differently
277
- // In Go, := can redeclare existing variables if at least one is new
278
-
279
- // First, identify which variables are new vs existing
280
- newVars := make([]bool, len(lhs))
281
- anyNewVars := false
282
- allNewVars := true
283
-
284
- // For multi-variable assignments with :=, we need to determine which variables
285
- // are already in scope and which are new declarations
286
- for i, lhsExpr := range lhs {
287
- if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
288
- // In Go, variables declared with := can be redeclared if at least one is new
289
- // For TypeScript, we need to separately declare new variables
290
-
291
- // Check if this variable is already in scope
292
- // - If the variable is used elsewhere before this point, it's existing
293
- // - Otherwise, it's a new variable being declared
294
- isNew := true
295
-
296
- // Check if the variable is used elsewhere in the code
297
- if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
298
- // If it's in Uses, it's referenced elsewhere, so it exists
299
- isNew = false
300
- allNewVars = false
301
- }
302
-
303
- newVars[i] = isNew
304
- if isNew {
305
- anyNewVars = true
306
- }
307
- }
308
- }
309
-
310
- // Get function return types if available
311
- var resultTypes []*types.Var
312
- if callExpr.Fun != nil {
313
- if funType := c.pkg.TypesInfo.TypeOf(callExpr.Fun); funType != nil {
314
- if funcType, ok := funType.Underlying().(*types.Signature); ok {
315
- if funcType.Results() != nil && funcType.Results().Len() > 0 {
316
- for v := range funcType.Results().Variables() {
317
- resultTypes = append(resultTypes, v)
318
- }
319
- }
320
- }
321
- }
322
- }
323
-
324
- if allNewVars && anyNewVars {
325
- // Check if any variable needs VarRef - if so, we need a different approach
326
- anyNeedsVarRef := false
327
- needsVarRefVars := make([]bool, len(lhs))
328
- for i, lhsExpr := range lhs {
329
- if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
330
- if obj := c.pkg.TypesInfo.Defs[ident]; obj != nil {
331
- if c.analysis.NeedsVarRef(obj) {
332
- needsVarRefVars[i] = true
333
- anyNeedsVarRef = true
334
- }
335
- }
336
- }
337
- }
338
-
339
- if anyNeedsVarRef {
340
- // Use temp variables for destructuring, then wrap in VarRef as needed
341
- c.tsw.WriteLiterally("let [")
342
- for i, lhsExpr := range lhs {
343
- if i != 0 {
344
- c.tsw.WriteLiterally(", ")
345
- }
346
- if ident, ok := lhsExpr.(*ast.Ident); ok {
347
- if ident.Name == "_" {
348
- // Empty slot for blank identifier
349
- } else if needsVarRefVars[i] {
350
- c.tsw.WriteLiterally("_varref_tmp_")
351
- c.tsw.WriteLiterally(ident.Name)
352
- } else {
353
- c.WriteIdent(ident, false)
354
- }
355
- } else {
356
- c.WriteValueExpr(lhsExpr)
357
- }
358
- }
359
- c.tsw.WriteLiterally("] = ")
360
- c.WriteValueExpr(callExpr)
361
- c.tsw.WriteLine("")
362
-
363
- // Now declare the VarRef-wrapped variables
364
- for i, lhsExpr := range lhs {
365
- if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && needsVarRefVars[i] {
366
- c.tsw.WriteLiterally("let ")
367
- c.WriteIdent(ident, false)
368
- c.tsw.WriteLiterally(" = $.varRef(_varref_tmp_")
369
- c.tsw.WriteLiterally(ident.Name)
370
- // Add non-null assertion to handle cases where the tuple type includes null
371
- c.tsw.WriteLiterally("!)")
372
- c.tsw.WriteLine("")
373
- }
374
- }
375
- return nil
376
- }
377
-
378
- c.tsw.WriteLiterally("let [")
379
-
380
- for i, lhsExpr := range lhs {
381
- if i != 0 {
382
- c.tsw.WriteLiterally(", ")
383
- }
384
-
385
- if ident, ok := lhsExpr.(*ast.Ident); ok {
386
- if ident.Name == "_" {
387
- // For underscore variables, use empty slots in destructuring pattern
388
- } else {
389
- c.WriteIdent(ident, false)
390
- }
391
- } else {
392
- c.WriteValueExpr(lhsExpr)
393
- }
394
- }
395
- c.tsw.WriteLiterally("] = ")
396
- c.WriteValueExpr(callExpr)
397
- c.tsw.WriteLine("")
398
- return nil
399
- } else if anyNewVars {
400
- // If only some variables are new, declare them separately before the assignment
401
- // Declare each new variable with appropriate type
402
- for i, lhsExpr := range lhs {
403
- if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && newVars[i] {
404
- c.tsw.WriteLiterally("let ")
405
- c.WriteIdent(ident, false)
406
- // Add type annotation if we have type information
407
- if i < len(resultTypes) {
408
- c.tsw.WriteLiterally(": ")
409
- c.WriteGoType(resultTypes[i].Type(), GoTypeContextGeneral)
410
- }
411
- c.tsw.WriteLine("")
412
- }
413
- }
414
- }
415
- }
416
-
417
- // First, collect all the selector expressions to identify variables that need to be initialized
418
- hasSelectors := c.lhsHasComplexTargets(lhs)
419
-
420
- // If we have selector expressions, we need to ensure variables are initialized
421
- // before the destructuring assignment
422
- if hasSelectors {
423
- c.tsw.WriteLiterally("{")
424
- c.tsw.WriteLine("")
425
-
426
- // Write a temporary variable to hold the function call result
427
- c.tsw.WriteLiterally(" const _tmp = ")
428
- if err := c.WriteValueExpr(callExpr); err != nil {
429
- return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
430
- }
431
- c.tsw.WriteLine("")
432
-
433
- for i, lhsExpr := range lhs {
434
- // Skip underscore variables
435
- if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name == "_" {
436
- continue
437
- }
438
-
439
- // Write the LHS with indentation
440
- c.tsw.WriteLiterally(" ")
441
- if err := c.writeLHSTarget(lhsExpr); err != nil {
442
- return err
443
- }
444
-
445
- // Write the assignment
446
- c.tsw.WriteLiterallyf(" = _tmp[%d]", i)
447
- // Always add a newline after each assignment
448
- c.tsw.WriteLine("")
449
- }
450
-
451
- // Close the block scope
452
- c.tsw.WriteLiterally("}")
453
- c.tsw.WriteLine("")
454
-
455
- return nil
456
- }
457
-
458
- // For simple cases without selector expressions, use array destructuring
459
- // Add semicolon before destructuring assignment to prevent TypeScript
460
- // from interpreting it as array access on the previous line
461
- if tok != token.DEFINE {
462
- c.tsw.WriteLiterally(";")
463
- }
464
- c.tsw.WriteLiterally("[")
465
-
466
- // Find the last non-blank identifier to avoid trailing commas
467
- lastNonBlankIndex := -1
468
- for i := len(lhs) - 1; i >= 0; i-- {
469
- if ident, ok := lhs[i].(*ast.Ident); !ok || ident.Name != "_" {
470
- lastNonBlankIndex = i
471
- break
472
- }
473
- }
474
-
475
- for i, lhsExpr := range lhs {
476
- // Write comma before non-first elements
477
- if i > 0 {
478
- c.tsw.WriteLiterally(", ")
479
- }
480
-
481
- if ident, ok := lhsExpr.(*ast.Ident); ok {
482
- // For underscore variables, use empty slots in destructuring pattern
483
- if ident.Name != "_" {
484
- c.WriteIdent(ident, false)
485
- }
486
- // For blank identifiers, we write nothing (empty slot)
487
- } else {
488
- if err := c.writeLHSTarget(lhsExpr); err != nil {
489
- return err
490
- }
491
- }
492
-
493
- // Stop writing if we've reached the last non-blank element
494
- if i == lastNonBlankIndex {
495
- break
496
- }
497
- }
498
- c.tsw.WriteLiterally("] = ")
499
-
500
- c.WriteValueExpr(callExpr)
501
-
502
- c.tsw.WriteLine("")
503
- return nil
504
- }
505
-
506
- // writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
507
- // Uses array destructuring with the tuple-returning $.mapGet function
508
- func (c *GoToTSCompiler) writeMapLookupWithExists(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
509
- // First check that we have exactly two LHS expressions (value and exists)
510
- if len(lhs) != 2 {
511
- return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
512
- }
513
-
514
- // Check for blank identifiers
515
- valueIsBlank := false
516
- existsIsBlank := false
517
-
518
- if valIdent, ok := lhs[0].(*ast.Ident); ok && valIdent.Name == "_" {
519
- valueIsBlank = true
520
- }
521
- if existsIdent, ok := lhs[1].(*ast.Ident); ok && existsIdent.Name == "_" {
522
- existsIsBlank = true
523
- }
524
-
525
- // Use array destructuring with mapGet tuple return
526
- if tok == token.DEFINE {
527
- c.tsw.WriteLiterally("let ")
528
- } else {
529
- // Add semicolon before destructuring assignment to prevent TypeScript
530
- // from interpreting it as array access on the previous line
531
- c.tsw.WriteLiterally(";")
532
- }
533
-
534
- c.tsw.WriteLiterally("[")
535
-
536
- // Write LHS variables, handling blanks
537
- if !valueIsBlank {
538
- if err := c.WriteValueExpr(lhs[0]); err != nil {
539
- return err
540
- }
541
- }
542
- // Note: for blank identifiers, we just omit the variable name entirely
543
-
544
- c.tsw.WriteLiterally(", ")
545
-
546
- if !existsIsBlank {
547
- if err := c.WriteValueExpr(lhs[1]); err != nil {
548
- return err
549
- }
550
- }
551
- // Note: for blank identifiers, we just omit the variable name entirely
552
-
553
- c.tsw.WriteLiterally("] = $.mapGet(")
554
-
555
- // Write map expression
556
- if err := c.WriteValueExpr(indexExpr.X); err != nil {
557
- return err
558
- }
559
-
560
- c.tsw.WriteLiterally(", ")
561
-
562
- // Write key expression
563
- if err := c.WriteValueExpr(indexExpr.Index); err != nil {
564
- return err
565
- }
566
-
567
- c.tsw.WriteLiterally(", ")
568
-
569
- // Write the zero value for the map's value type
570
- if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
571
- if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
572
- c.WriteZeroValueForType(mapType.Elem())
573
- } else if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
574
- // Handle type parameter constrained to be a map type
575
- constraint := typeParam.Constraint()
576
- if constraint != nil {
577
- underlying := constraint.Underlying()
578
- if iface, isInterface := underlying.(*types.Interface); isInterface {
579
- if hasMapConstraint(iface) {
580
- // Get the value type from the constraint
581
- mapValueType := getMapValueTypeFromConstraint(iface)
582
- if mapValueType != nil {
583
- c.WriteZeroValueForType(mapValueType)
584
- } else {
585
- c.tsw.WriteLiterally("null")
586
- }
587
- } else {
588
- c.tsw.WriteLiterally("null")
589
- }
590
- } else {
591
- c.tsw.WriteLiterally("null")
592
- }
593
- } else {
594
- c.tsw.WriteLiterally("null")
595
- }
596
- } else {
597
- // Fallback zero value if type info is missing or not a map
598
- c.tsw.WriteLiterally("null")
599
- }
600
- } else {
601
- c.tsw.WriteLiterally("null")
602
- }
603
-
604
- c.tsw.WriteLiterally(")")
605
- c.tsw.WriteLine("")
606
-
607
- return nil
608
- }
609
-
610
- // writeTypeShadowedAssignment handles the case where a variable name shadows a type name
611
- // used in its initialization (e.g., field := field{...}).
612
- // In TypeScript, `let field = new field({...})` fails because the variable shadows the class
613
- // before initialization due to the Temporal Dead Zone (TDZ). The TDZ extends from the
614
- // start of the block scope to the point of initialization, so even capturing the type
615
- // reference before the `let` declaration doesn't work - they're in the same block.
616
- //
617
- // We solve this by renaming the variable to avoid the conflict entirely:
618
- //
619
- // let field_ = $.markAsStructValue(new field({...}));
620
- //
621
- // Then we need to track that all subsequent references to `field` should use `field_`.
622
- // This is stored in the analysis NodeInfo.IdentifierMapping.
623
- func (c *GoToTSCompiler) writeTypeShadowedAssignment(exp *ast.AssignStmt, origName, renamedVar string) error {
624
- if len(exp.Lhs) != 1 || len(exp.Rhs) != 1 {
625
- return fmt.Errorf("type shadowing assignment must have exactly 1 LHS and 1 RHS")
626
- }
627
-
628
- lhsIdent, ok := exp.Lhs[0].(*ast.Ident)
629
- if !ok {
630
- return fmt.Errorf("type shadowing assignment LHS must be an identifier")
631
- }
632
-
633
- // Check if this variable needs VarRef
634
- obj := c.objectOfIdent(lhsIdent)
635
- needsVarRef := obj != nil && c.analysis.NeedsVarRef(obj)
636
-
637
- // Store the mapping so that subsequent references to this variable use the renamed version
638
- if obj != nil {
639
- c.renamedVars[obj] = renamedVar
640
- }
641
-
642
- if needsVarRef {
643
- // For VarRef'd variables:
644
- // let field_ = $.varRef($.markAsStructValue(new field({...})))
645
- c.tsw.WriteLiterally("let ")
646
- c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedVar))
647
- c.tsw.WriteLiterally(" = $.varRef(")
648
- if err := c.WriteValueExpr(exp.Rhs[0]); err != nil {
649
- return err
650
- }
651
- c.tsw.WriteLiterally(")")
652
- } else {
653
- // For non-VarRef variables:
654
- // let field_ = $.markAsStructValue(new field({...}))
655
- c.tsw.WriteLiterally("let ")
656
- c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedVar))
657
- c.tsw.WriteLiterally(" = ")
658
- if err := c.WriteValueExpr(exp.Rhs[0]); err != nil {
659
- return err
660
- }
661
- }
662
-
663
- return nil
664
- }