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.
- package/builtin/builtin.ts +298 -33
- package/compiler/assignment.go +407 -0
- package/compiler/compiler.go +160 -5883
- package/compiler/compiler_test.go +4 -0
- package/compiler/composite-lit.go +537 -0
- package/compiler/config.go +3 -0
- package/compiler/decl.go +223 -0
- package/compiler/expr-call.go +423 -0
- package/compiler/expr-selector.go +105 -0
- package/compiler/expr-star.go +90 -0
- package/compiler/expr-type.go +182 -0
- package/compiler/expr-value.go +119 -0
- package/compiler/expr.go +356 -0
- package/compiler/field.go +169 -0
- package/compiler/lit.go +99 -0
- package/compiler/primitive.go +142 -0
- package/compiler/{write-type-spec.go → spec-struct.go} +79 -203
- package/compiler/spec-value.go +194 -0
- package/compiler/spec.go +263 -0
- package/compiler/stmt-assign.go +411 -0
- package/compiler/stmt-for.go +178 -0
- package/compiler/stmt-range.go +237 -0
- package/compiler/stmt.go +907 -0
- package/compiler/type-assert.go +463 -0
- package/compiler/type-info.go +141 -0
- package/compiler/type.go +556 -0
- package/dist/builtin/builtin.d.ts +24 -6
- package/dist/builtin/builtin.js +215 -19
- package/dist/builtin/builtin.js.map +1 -1
- package/go.mod +2 -1
- package/go.sum +4 -2
- package/package.json +2 -2
- /package/compiler/{writer.go → code-writer.go} +0 -0
package/compiler/expr.go
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
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
|
+
// WriteIndexExpr translates a Go index expression (a[b]) to its TypeScript equivalent.
|
|
13
|
+
func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
|
|
14
|
+
// Handle map access: use Map.get() instead of brackets for reading values
|
|
15
|
+
if tv, ok := c.pkg.TypesInfo.Types[exp.X]; ok {
|
|
16
|
+
// Check if it's a map type
|
|
17
|
+
if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
18
|
+
c.tsw.WriteLiterally("$.mapGet(")
|
|
19
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
20
|
+
return err
|
|
21
|
+
}
|
|
22
|
+
c.tsw.WriteLiterally(", ")
|
|
23
|
+
if err := c.WriteValueExpr(exp.Index); err != nil {
|
|
24
|
+
return err
|
|
25
|
+
}
|
|
26
|
+
c.tsw.WriteLiterally(", ")
|
|
27
|
+
|
|
28
|
+
// Generate the zero value as the default value for mapGet
|
|
29
|
+
c.WriteZeroValueForType(mapType.Elem())
|
|
30
|
+
c.tsw.WriteLiterally(")")
|
|
31
|
+
return nil
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Regular array/slice access: use brackets
|
|
36
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
37
|
+
return err
|
|
38
|
+
}
|
|
39
|
+
c.tsw.WriteLiterally("![") // non-null assertion
|
|
40
|
+
if err := c.WriteValueExpr(exp.Index); err != nil {
|
|
41
|
+
return err
|
|
42
|
+
}
|
|
43
|
+
c.tsw.WriteLiterally("]")
|
|
44
|
+
return nil
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// WriteTypeAssertExpr translates a Go type assertion expression (e.g., `x.(T)`)
|
|
48
|
+
// into a TypeScript call to `$.typeAssert<T_ts>(x_ts, 'TypeName').value`.
|
|
49
|
+
// The `$.typeAssert` runtime function handles the actual type check and panic
|
|
50
|
+
// if the assertion fails. The `.value` access is used because in an expression
|
|
51
|
+
// context, we expect the asserted value directly. The `TypeName` string is used
|
|
52
|
+
// by the runtime for error messages.
|
|
53
|
+
func (c *GoToTSCompiler) WriteTypeAssertExpr(exp *ast.TypeAssertExpr) error {
|
|
54
|
+
// Generate a call to $.typeAssert
|
|
55
|
+
c.tsw.WriteLiterally("$.typeAssert<")
|
|
56
|
+
c.WriteTypeExpr(exp.Type) // Write the asserted type for the generic
|
|
57
|
+
c.tsw.WriteLiterally(">(")
|
|
58
|
+
if err := c.WriteValueExpr(exp.X); err != nil { // The interface expression
|
|
59
|
+
return fmt.Errorf("failed to write interface expression in type assertion expression: %w", err)
|
|
60
|
+
}
|
|
61
|
+
c.tsw.WriteLiterally(", ")
|
|
62
|
+
|
|
63
|
+
// Write the type description instead of just the type name
|
|
64
|
+
// This ensures we generate proper type info objects for all types
|
|
65
|
+
|
|
66
|
+
// Unwrap parenthesized expressions to handle cases like r.((<-chan T))
|
|
67
|
+
typeExpr := exp.Type
|
|
68
|
+
for {
|
|
69
|
+
if parenExpr, ok := typeExpr.(*ast.ParenExpr); ok {
|
|
70
|
+
typeExpr = parenExpr.X
|
|
71
|
+
} else {
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
c.writeTypeDescription(typeExpr)
|
|
77
|
+
|
|
78
|
+
c.tsw.WriteLiterally(")") // Just close the parenthesis, don't access .value directly
|
|
79
|
+
return nil
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// isPointerComparison checks if a binary expression `exp` involves comparing
|
|
83
|
+
// two pointer types. It uses `go/types` information to determine the types
|
|
84
|
+
// of the left (X) and right (Y) operands of the binary expression.
|
|
85
|
+
// Returns `true` if both operands are determined to be pointer types,
|
|
86
|
+
// `false` otherwise. This is used to apply specific comparison semantics
|
|
87
|
+
// for pointers (e.g., comparing the box objects directly).
|
|
88
|
+
func (c *GoToTSCompiler) isPointerComparison(exp *ast.BinaryExpr) bool {
|
|
89
|
+
leftType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
90
|
+
rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
|
|
91
|
+
if leftType != nil && rightType != nil {
|
|
92
|
+
if _, leftIsPtr := leftType.(*types.Pointer); leftIsPtr {
|
|
93
|
+
if _, rightIsPtr := rightType.(*types.Pointer); rightIsPtr {
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// getTypeNameString returns a string representation of a Go type expression (`ast.Expr`).
|
|
102
|
+
// It handles simple identifiers (e.g., `MyType`) and selector expressions
|
|
103
|
+
// (e.g., `pkg.Type`). For more complex or unrecognized type expressions,
|
|
104
|
+
// it returns "unknown". This string is primarily used for runtime error messages,
|
|
105
|
+
// such as in type assertions.
|
|
106
|
+
func (c *GoToTSCompiler) getTypeNameString(typeExpr ast.Expr) string {
|
|
107
|
+
switch t := typeExpr.(type) {
|
|
108
|
+
case *ast.Ident:
|
|
109
|
+
return t.Name
|
|
110
|
+
case *ast.SelectorExpr:
|
|
111
|
+
// For imported types like pkg.Type
|
|
112
|
+
if ident, ok := t.X.(*ast.Ident); ok {
|
|
113
|
+
return fmt.Sprintf("%s.%s", ident.Name, t.Sel.Name)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Default case, use a placeholder for complex types
|
|
117
|
+
return "unknown"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// WriteBinaryExpr translates a Go binary expression (`ast.BinaryExpr`) into its
|
|
121
|
+
// TypeScript equivalent.
|
|
122
|
+
// It handles several cases:
|
|
123
|
+
// - Channel send (`ch <- val`): Becomes `await ch.send(val)`.
|
|
124
|
+
// - Nil comparison for pointers (`ptr == nil` or `ptr != nil`): Compares the
|
|
125
|
+
// pointer (which may be a box object or `null`) directly to `null` using
|
|
126
|
+
// the translated operator (`==` or `!=`).
|
|
127
|
+
// - Pointer comparison (non-nil, `ptr1 == ptr2` or `ptr1 != ptr2`): Compares
|
|
128
|
+
// the box objects directly using strict equality (`===` or `!==`).
|
|
129
|
+
// - Bitwise operations (`&`, `|`, `^`, `<<`, `>>`, `&^`): The expression is wrapped
|
|
130
|
+
// in parentheses `()` to ensure correct precedence in TypeScript, and operators
|
|
131
|
+
// are mapped (e.g., `&^` might need special handling or is mapped to a runtime helper).
|
|
132
|
+
// - Other binary operations (arithmetic, logical, comparison): Operands are
|
|
133
|
+
// translated using `WriteValueExpr`, and the operator is mapped to its TypeScript
|
|
134
|
+
// equivalent using `TokenToTs`.
|
|
135
|
+
//
|
|
136
|
+
// Unhandled operators result in a comment and a placeholder.
|
|
137
|
+
func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
138
|
+
// Handle special cases like channel send
|
|
139
|
+
if exp.Op == token.ARROW {
|
|
140
|
+
// Channel send: ch <- val becomes await ch.send(val)
|
|
141
|
+
c.tsw.WriteLiterally("await ")
|
|
142
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
143
|
+
return fmt.Errorf("failed to write channel send target: %w", err)
|
|
144
|
+
}
|
|
145
|
+
c.tsw.WriteLiterally(".send(")
|
|
146
|
+
if err := c.WriteValueExpr(exp.Y); err != nil {
|
|
147
|
+
return fmt.Errorf("failed to write channel send value: %w", err)
|
|
148
|
+
}
|
|
149
|
+
c.tsw.WriteLiterally(")")
|
|
150
|
+
return nil
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check if this is a nil comparison for a pointer
|
|
154
|
+
isNilComparison := false
|
|
155
|
+
var ptrExpr ast.Expr
|
|
156
|
+
if (exp.Op == token.EQL || exp.Op == token.NEQ) && c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
157
|
+
if leftIdent, ok := exp.Y.(*ast.Ident); ok && leftIdent.Name == "nil" {
|
|
158
|
+
leftType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
159
|
+
if _, isPtr := leftType.(*types.Pointer); isPtr {
|
|
160
|
+
isNilComparison = true
|
|
161
|
+
ptrExpr = exp.X
|
|
162
|
+
}
|
|
163
|
+
} else if rightIdent, ok := exp.X.(*ast.Ident); ok && rightIdent.Name == "nil" {
|
|
164
|
+
rightType := c.pkg.TypesInfo.TypeOf(exp.Y)
|
|
165
|
+
if _, isPtr := rightType.(*types.Pointer); isPtr {
|
|
166
|
+
isNilComparison = true
|
|
167
|
+
ptrExpr = exp.Y
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if isNilComparison {
|
|
173
|
+
// Compare the box object directly to null
|
|
174
|
+
if err := c.WriteValueExpr(ptrExpr); err != nil {
|
|
175
|
+
return fmt.Errorf("failed to write pointer expression in nil comparison: %w", err)
|
|
176
|
+
}
|
|
177
|
+
c.tsw.WriteLiterally(" ")
|
|
178
|
+
tokStr, ok := TokenToTs(exp.Op)
|
|
179
|
+
if !ok {
|
|
180
|
+
return errors.Errorf("unhandled binary op: %s", exp.Op.String())
|
|
181
|
+
}
|
|
182
|
+
c.tsw.WriteLiterally(tokStr)
|
|
183
|
+
c.tsw.WriteLiterally(" null")
|
|
184
|
+
return nil
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check if this is a pointer comparison (non-nil)
|
|
188
|
+
// Compare the box objects directly using === or !==
|
|
189
|
+
if c.isPointerComparison(exp) {
|
|
190
|
+
c.tsw.WriteLiterally("(") // Wrap comparison
|
|
191
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
192
|
+
return fmt.Errorf("failed to write binary expression left operand: %w", err)
|
|
193
|
+
}
|
|
194
|
+
c.tsw.WriteLiterally(" ")
|
|
195
|
+
// Use === for == and !== for !=
|
|
196
|
+
tokStr := ""
|
|
197
|
+
switch exp.Op {
|
|
198
|
+
case token.EQL:
|
|
199
|
+
tokStr = "==="
|
|
200
|
+
case token.NEQ:
|
|
201
|
+
tokStr = "!=="
|
|
202
|
+
default:
|
|
203
|
+
return errors.Errorf("unhandled pointer comparison op: %s", exp.Op.String())
|
|
204
|
+
}
|
|
205
|
+
c.tsw.WriteLiterally(tokStr)
|
|
206
|
+
c.tsw.WriteLiterally(" ")
|
|
207
|
+
if err := c.WriteValueExpr(exp.Y); err != nil {
|
|
208
|
+
return fmt.Errorf("failed to write binary expression right operand: %w", err)
|
|
209
|
+
}
|
|
210
|
+
c.tsw.WriteLiterally(")") // Close wrap
|
|
211
|
+
return nil
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check if the operator is a bitwise operator
|
|
215
|
+
isBitwise := false
|
|
216
|
+
switch exp.Op {
|
|
217
|
+
case token.AND, token.OR, token.XOR, token.SHL, token.SHR, token.AND_NOT:
|
|
218
|
+
isBitwise = true
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if isBitwise {
|
|
222
|
+
c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
226
|
+
return fmt.Errorf("failed to write binary expression left operand: %w", err)
|
|
227
|
+
}
|
|
228
|
+
c.tsw.WriteLiterally(" ")
|
|
229
|
+
tokStr, ok := TokenToTs(exp.Op)
|
|
230
|
+
if !ok {
|
|
231
|
+
return errors.Errorf("unhandled binary op: %s", exp.Op.String())
|
|
232
|
+
}
|
|
233
|
+
c.tsw.WriteLiterally(tokStr)
|
|
234
|
+
c.tsw.WriteLiterally(" ")
|
|
235
|
+
if err := c.WriteValueExpr(exp.Y); err != nil {
|
|
236
|
+
return fmt.Errorf("failed to write binary expression right operand: %w", err)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if isBitwise {
|
|
240
|
+
c.tsw.WriteLiterally(")") // Add closing parenthesis for bitwise operations
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return nil
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// WriteUnaryExpr translates a Go unary expression (`ast.UnaryExpr`) into its
|
|
247
|
+
// TypeScript equivalent.
|
|
248
|
+
// It handles several unary operations:
|
|
249
|
+
// - Channel receive (`<-ch`): Becomes `await ch.receive()`.
|
|
250
|
+
// - Address-of (`&var`):
|
|
251
|
+
// - If `var` is a boxed variable (its address was taken), `&var` evaluates
|
|
252
|
+
// to the box itself (i.e., `varName` in TypeScript, which holds the box).
|
|
253
|
+
// - Otherwise (e.g., `&unboxedVar`, `&MyStruct{}`, `&FuncCall()`), it evaluates
|
|
254
|
+
// the operand `var`. The resulting TypeScript value (e.g., a new object instance)
|
|
255
|
+
// acts as the "pointer". Boxing decisions for such pointers are handled at
|
|
256
|
+
// the assignment site.
|
|
257
|
+
// - Other unary operators (`+`, `-`, `!`, `^`): Mapped to their TypeScript
|
|
258
|
+
// equivalents (e.g., `+`, `-`, `!`, `~` for bitwise NOT). Parentheses are added
|
|
259
|
+
// around the operand if it's a binary or unary expression to maintain precedence.
|
|
260
|
+
//
|
|
261
|
+
// Unhandled operators result in a comment and an attempt to write the operator
|
|
262
|
+
// token directly. Postfix operators (`++`, `--`) are expected to be handled by
|
|
263
|
+
// their statement contexts (e.g., `IncDecStmt`).
|
|
264
|
+
func (c *GoToTSCompiler) WriteUnaryExpr(exp *ast.UnaryExpr) error {
|
|
265
|
+
if exp.Op == token.ARROW {
|
|
266
|
+
// Channel receive: <-ch becomes await ch.receive()
|
|
267
|
+
c.tsw.WriteLiterally("await ")
|
|
268
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
269
|
+
return fmt.Errorf("failed to write channel receive operand: %w", err)
|
|
270
|
+
}
|
|
271
|
+
c.tsw.WriteLiterally(".receive()")
|
|
272
|
+
return nil
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if exp.Op == token.AND { // Address-of operator (&)
|
|
276
|
+
// If the operand is an identifier for a variable that is boxed,
|
|
277
|
+
// the result of & is the box itself.
|
|
278
|
+
if ident, ok := exp.X.(*ast.Ident); ok {
|
|
279
|
+
var obj types.Object
|
|
280
|
+
obj = c.pkg.TypesInfo.Uses[ident]
|
|
281
|
+
if obj == nil {
|
|
282
|
+
obj = c.pkg.TypesInfo.Defs[ident]
|
|
283
|
+
}
|
|
284
|
+
if obj != nil && c.analysis.NeedsBoxed(obj) {
|
|
285
|
+
// &boxedVar -> boxedVar (the box itself)
|
|
286
|
+
c.tsw.WriteLiterally(ident.Name) // Write the identifier name (which holds the box)
|
|
287
|
+
return nil
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Otherwise (&unboxedVar, &CompositeLit{}, &FuncCall(), etc.),
|
|
292
|
+
// the address-of operator in Go, when used to create a pointer,
|
|
293
|
+
// translates to simply evaluating the operand in TypeScript.
|
|
294
|
+
// The resulting value (e.g., a new object instance) acts as the "pointer".
|
|
295
|
+
// Boxing decisions are handled at the assignment site based on the LHS variable.
|
|
296
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
297
|
+
return fmt.Errorf("failed to write &-operand: %w", err)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return nil
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Handle other unary operators (+, -, !, ^)
|
|
304
|
+
tokStr, ok := TokenToTs(exp.Op)
|
|
305
|
+
if !ok {
|
|
306
|
+
return errors.Errorf("unhandled unary op: %s", exp.Op.String())
|
|
307
|
+
}
|
|
308
|
+
c.tsw.WriteLiterally(tokStr)
|
|
309
|
+
|
|
310
|
+
// Add space if operator is not postfix (e.g., !)
|
|
311
|
+
if exp.Op != token.INC && exp.Op != token.DEC {
|
|
312
|
+
// Check if operand needs parentheses (e.g., !(-a))
|
|
313
|
+
// Basic check: if operand is binary or unary, add parens
|
|
314
|
+
needsParens := false
|
|
315
|
+
switch exp.X.(type) {
|
|
316
|
+
case *ast.BinaryExpr, *ast.UnaryExpr:
|
|
317
|
+
needsParens = true
|
|
318
|
+
}
|
|
319
|
+
if needsParens {
|
|
320
|
+
c.tsw.WriteLiterally("(")
|
|
321
|
+
}
|
|
322
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
323
|
+
return fmt.Errorf("failed to write unary expression operand: %w", err)
|
|
324
|
+
}
|
|
325
|
+
if needsParens {
|
|
326
|
+
c.tsw.WriteLiterally(")")
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
// Postfix operators (++, --) - operand written first by caller (e.g., IncDecStmt)
|
|
330
|
+
// This function shouldn't be called directly for ++/-- in expression context in valid Go?
|
|
331
|
+
// If it is, write the operand.
|
|
332
|
+
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
333
|
+
return fmt.Errorf("failed to write unary expression operand for postfix op: %w", err)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return nil
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// WriteKeyValueExpr translates a Go key-value pair expression (`ast.KeyValueExpr`),
|
|
341
|
+
// typically found within composite literals (for structs, maps, or arrays with
|
|
342
|
+
// indexed elements), into its TypeScript object property equivalent: `key: value`.
|
|
343
|
+
// Both the key and the value expressions are recursively translated using
|
|
344
|
+
// `WriteValueExpr`. The original Go casing for keys is preserved.
|
|
345
|
+
// For example, `MyField: 123` in Go becomes `MyField: 123` in TypeScript.
|
|
346
|
+
func (c *GoToTSCompiler) WriteKeyValueExpr(exp *ast.KeyValueExpr) error {
|
|
347
|
+
// Keep original Go casing for keys
|
|
348
|
+
if err := c.WriteValueExpr(exp.Key); err != nil {
|
|
349
|
+
return fmt.Errorf("failed to write key-value expression key: %w", err)
|
|
350
|
+
}
|
|
351
|
+
c.tsw.WriteLiterally(": ")
|
|
352
|
+
if err := c.WriteValueExpr(exp.Value); err != nil {
|
|
353
|
+
return fmt.Errorf("failed to write key-value expression value: %w", err)
|
|
354
|
+
}
|
|
355
|
+
return nil
|
|
356
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import "go/ast"
|
|
4
|
+
|
|
5
|
+
// WriteFieldList translates a Go field list (`ast.FieldList`), which can represent
|
|
6
|
+
// function parameters, function results, or struct fields, into its TypeScript equivalent.
|
|
7
|
+
// - If `isArguments` is true (for function parameters/results):
|
|
8
|
+
// It iterates through `a.List`, writing each field as `name: type`. Parameter
|
|
9
|
+
// names and types are written using `WriteField` and `WriteGoType` respectively.
|
|
10
|
+
// Multiple parameters are comma-separated.
|
|
11
|
+
// - If `isArguments` is false (for struct fields):
|
|
12
|
+
// It writes an opening brace `{`, indents, then writes each field definition
|
|
13
|
+
// using `WriteField`, followed by a closing brace `}`. If the field list is
|
|
14
|
+
// empty or nil, it simply writes `{}`.
|
|
15
|
+
//
|
|
16
|
+
// This function is a key part of generating TypeScript type signatures for functions
|
|
17
|
+
// and interfaces, as well as struct type definitions.
|
|
18
|
+
func (c *GoToTSCompiler) WriteFieldList(a *ast.FieldList, isArguments bool) {
|
|
19
|
+
if !isArguments && (a == nil || a.NumFields() == 0) {
|
|
20
|
+
c.tsw.WriteLiterally("{}")
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if !isArguments && a.Opening.IsValid() {
|
|
25
|
+
c.tsw.WriteLine("{")
|
|
26
|
+
c.tsw.Indent(1)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check if this is a variadic function parameter list
|
|
30
|
+
isVariadic := false
|
|
31
|
+
if isArguments && a != nil && len(a.List) > 0 {
|
|
32
|
+
lastParam := a.List[len(a.List)-1]
|
|
33
|
+
if _, ok := lastParam.Type.(*ast.Ellipsis); ok {
|
|
34
|
+
isVariadic = true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if isArguments && isVariadic {
|
|
39
|
+
// Handle non-variadic parameters first
|
|
40
|
+
for i, field := range a.List[:len(a.List)-1] {
|
|
41
|
+
if i > 0 {
|
|
42
|
+
c.tsw.WriteLiterally(", ")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Handle multiple parameter names for the same type
|
|
46
|
+
for j, name := range field.Names {
|
|
47
|
+
if j > 0 {
|
|
48
|
+
c.tsw.WriteLiterally(", ")
|
|
49
|
+
}
|
|
50
|
+
c.tsw.WriteLiterally(name.Name)
|
|
51
|
+
c.tsw.WriteLiterally(": ")
|
|
52
|
+
typ := c.pkg.TypesInfo.TypeOf(field.Type)
|
|
53
|
+
c.WriteGoType(typ)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle the variadic parameter
|
|
58
|
+
lastParam := a.List[len(a.List)-1]
|
|
59
|
+
if len(a.List) > 1 {
|
|
60
|
+
c.tsw.WriteLiterally(", ")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for i, name := range lastParam.Names {
|
|
64
|
+
if i > 0 {
|
|
65
|
+
c.tsw.WriteLiterally(", ")
|
|
66
|
+
}
|
|
67
|
+
c.tsw.WriteLiterally("...")
|
|
68
|
+
c.tsw.WriteLiterally(name.Name)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
c.tsw.WriteLiterally(": ")
|
|
72
|
+
if ellipsis, ok := lastParam.Type.(*ast.Ellipsis); ok {
|
|
73
|
+
c.WriteTypeExpr(ellipsis.Elt)
|
|
74
|
+
c.tsw.WriteLiterally("[]")
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// Handle regular parameter list for function declarations
|
|
78
|
+
for i, field := range a.List {
|
|
79
|
+
if i > 0 && isArguments {
|
|
80
|
+
c.tsw.WriteLiterally(", ")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if isArguments {
|
|
84
|
+
// For function parameters with multiple names, write each with its type
|
|
85
|
+
for j, name := range field.Names {
|
|
86
|
+
if j > 0 {
|
|
87
|
+
c.tsw.WriteLiterally(", ")
|
|
88
|
+
}
|
|
89
|
+
c.tsw.WriteLiterally(name.Name)
|
|
90
|
+
c.tsw.WriteLiterally(": ")
|
|
91
|
+
typ := c.pkg.TypesInfo.TypeOf(field.Type)
|
|
92
|
+
c.WriteGoType(typ) // Use WriteGoType for parameter type
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// For struct fields and other non-argument fields
|
|
96
|
+
c.WriteField(field, false)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if !isArguments && a.Closing.IsValid() {
|
|
102
|
+
c.tsw.Indent(-1)
|
|
103
|
+
c.tsw.WriteLine("}")
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// WriteField translates a single Go field (`ast.Field`) from a field list
|
|
108
|
+
// (e.g., in a struct type or function signature) into its TypeScript representation.
|
|
109
|
+
// - If `isArguments` is false (struct field):
|
|
110
|
+
// - Documentation comments (`field.Doc`, `field.Comment`) are preserved.
|
|
111
|
+
// - If the field is anonymous (embedded), it's skipped as promotions are handled
|
|
112
|
+
// elsewhere (e.g., during struct class generation).
|
|
113
|
+
// - For named fields, it writes `public fieldName: FieldType_ts`. The field name
|
|
114
|
+
// retains its Go casing. The type is translated using `WriteGoType`.
|
|
115
|
+
// - Go struct tags (`field.Tag`) are written as a trailing comment.
|
|
116
|
+
//
|
|
117
|
+
// - If `isArguments` is true (function parameter):
|
|
118
|
+
// - It writes the parameter name (retaining Go casing). The type is handled
|
|
119
|
+
// by the caller (`WriteFieldList`).
|
|
120
|
+
//
|
|
121
|
+
// This function is used by `WriteFieldList` to process individual items within
|
|
122
|
+
// parameter lists and struct field definitions.
|
|
123
|
+
func (c *GoToTSCompiler) WriteField(field *ast.Field, isArguments bool) {
|
|
124
|
+
if !isArguments {
|
|
125
|
+
if field.Doc != nil {
|
|
126
|
+
c.WriteDoc(field.Doc)
|
|
127
|
+
}
|
|
128
|
+
if field.Comment != nil {
|
|
129
|
+
c.WriteDoc(field.Comment)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check if this is an embedded field (anonymous field)
|
|
134
|
+
if len(field.Names) == 0 && !isArguments {
|
|
135
|
+
// This is an embedded field, so we're adding promotions instead of declaring it directly
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for i, name := range field.Names {
|
|
140
|
+
if i > 0 && isArguments {
|
|
141
|
+
c.tsw.WriteLiterally(", ")
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// argument names: keep original casing, no access modifier
|
|
145
|
+
if isArguments {
|
|
146
|
+
c.tsw.WriteLiterally(name.Name)
|
|
147
|
+
// Argument type is handled in WriteFieldList, so continue
|
|
148
|
+
continue
|
|
149
|
+
} else {
|
|
150
|
+
// All struct fields are public in TypeScript, keeping original Go casing
|
|
151
|
+
c.tsw.WriteLiterally("public ")
|
|
152
|
+
c.tsw.WriteLiterally(name.Name)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// write type for struct fields (not arguments)
|
|
156
|
+
c.tsw.WriteLiterally(": ")
|
|
157
|
+
typ := c.pkg.TypesInfo.TypeOf(field.Type)
|
|
158
|
+
c.WriteGoType(typ) // Use WriteGoType for field type
|
|
159
|
+
|
|
160
|
+
if !isArguments {
|
|
161
|
+
// write tag comment if any for struct fields
|
|
162
|
+
if field.Tag != nil {
|
|
163
|
+
c.tsw.WriteCommentLinef("tag: %s", field.Tag.Value)
|
|
164
|
+
} else {
|
|
165
|
+
c.tsw.WriteLine("") // No semicolon
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
package/compiler/lit.go
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"go/ast"
|
|
5
|
+
"go/token"
|
|
6
|
+
"strconv"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// WriteBasicLit translates a Go basic literal (`ast.BasicLit`) into its
|
|
10
|
+
// TypeScript equivalent.
|
|
11
|
+
// - Character literals (e.g., `'a'`, `'\n'`) are translated to their numeric
|
|
12
|
+
// Unicode code point (e.g., `97`, `10`). Escape sequences are handled.
|
|
13
|
+
// - Integer, float, imaginary, and string literals are written directly as their
|
|
14
|
+
// `exp.Value` string, which typically corresponds to valid TypeScript syntax
|
|
15
|
+
// (e.g., `123`, `3.14`, `"hello"`). Imaginary literals might need special
|
|
16
|
+
// handling if they are to be fully supported beyond direct string output.
|
|
17
|
+
func (c *GoToTSCompiler) WriteBasicLit(exp *ast.BasicLit) {
|
|
18
|
+
if exp.Kind == token.CHAR {
|
|
19
|
+
// Go char literal 'x' is a rune (int32). Translate to its numeric code point.
|
|
20
|
+
// Use strconv.UnquoteChar to handle escape sequences correctly.
|
|
21
|
+
val, _, _, err := strconv.UnquoteChar(exp.Value[1:len(exp.Value)-1], '\'')
|
|
22
|
+
if err != nil {
|
|
23
|
+
c.tsw.WriteCommentInlinef("error parsing char literal %s: %v", exp.Value, err)
|
|
24
|
+
c.tsw.WriteLiterally("0") // Default to 0 on error
|
|
25
|
+
} else {
|
|
26
|
+
c.tsw.WriteLiterallyf("%d", val)
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
// Other literals (INT, FLOAT, STRING, IMAG)
|
|
30
|
+
c.tsw.WriteLiterally(exp.Value)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// WriteFuncLitValue translates a Go function literal (`ast.FuncLit`) into a
|
|
35
|
+
// TypeScript arrow function.
|
|
36
|
+
// The translation results in: `[async] (param1: type1, ...) : returnType => { ...body... }`.
|
|
37
|
+
// - The `async` keyword is prepended if `c.analysis.IsFuncLitAsync(exp)`
|
|
38
|
+
// indicates the function literal contains asynchronous operations.
|
|
39
|
+
// - Parameters are translated using `WriteFieldList`.
|
|
40
|
+
// - The return type is determined similarly to `WriteFuncType`:
|
|
41
|
+
// - `void` for no results.
|
|
42
|
+
// - `resultType` for a single unnamed result.
|
|
43
|
+
// - `[typeA, typeB]` for multiple or named results.
|
|
44
|
+
// - Wrapped in `Promise<>` if `async`.
|
|
45
|
+
// - The function body (`exp.Body`) is translated using `WriteStmt`.
|
|
46
|
+
func (c *GoToTSCompiler) WriteFuncLitValue(exp *ast.FuncLit) error {
|
|
47
|
+
// Determine if the function literal should be async
|
|
48
|
+
isAsync := c.analysis.IsFuncLitAsync(exp)
|
|
49
|
+
|
|
50
|
+
if isAsync {
|
|
51
|
+
c.tsw.WriteLiterally("async ")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Write arrow function: (params) => { body }
|
|
55
|
+
c.tsw.WriteLiterally("(")
|
|
56
|
+
|
|
57
|
+
// Use WriteFieldList which now handles variadic parameters
|
|
58
|
+
c.WriteFieldList(exp.Type.Params, true) // true = arguments
|
|
59
|
+
|
|
60
|
+
c.tsw.WriteLiterally(")")
|
|
61
|
+
|
|
62
|
+
// Handle return type for function literals
|
|
63
|
+
if exp.Type.Results != nil && len(exp.Type.Results.List) > 0 {
|
|
64
|
+
c.tsw.WriteLiterally(": ")
|
|
65
|
+
if isAsync {
|
|
66
|
+
c.tsw.WriteLiterally("Promise<")
|
|
67
|
+
}
|
|
68
|
+
if len(exp.Type.Results.List) == 1 && len(exp.Type.Results.List[0].Names) == 0 {
|
|
69
|
+
c.WriteTypeExpr(exp.Type.Results.List[0].Type)
|
|
70
|
+
} else {
|
|
71
|
+
c.tsw.WriteLiterally("[")
|
|
72
|
+
for i, field := range exp.Type.Results.List {
|
|
73
|
+
if i > 0 {
|
|
74
|
+
c.tsw.WriteLiterally(", ")
|
|
75
|
+
}
|
|
76
|
+
c.WriteTypeExpr(field.Type)
|
|
77
|
+
}
|
|
78
|
+
c.tsw.WriteLiterally("]")
|
|
79
|
+
}
|
|
80
|
+
if isAsync {
|
|
81
|
+
c.tsw.WriteLiterally(">")
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
if isAsync {
|
|
85
|
+
c.tsw.WriteLiterally(": Promise<void>")
|
|
86
|
+
} else {
|
|
87
|
+
c.tsw.WriteLiterally(": void")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
c.tsw.WriteLiterally(" => ")
|
|
92
|
+
|
|
93
|
+
// Write function body
|
|
94
|
+
if err := c.WriteStmt(exp.Body); err != nil {
|
|
95
|
+
return err
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return nil
|
|
99
|
+
}
|