goscript 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,259 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ )
7
+
8
+ // WriteDecls iterates through a slice of Go top-level declarations (`ast.Decl`)
9
+ // and translates each one into its TypeScript equivalent.
10
+ // It distinguishes between:
11
+ // - Function declarations (`ast.FuncDecl`):
12
+ // - If it's a regular function (no receiver), it delegates to `WriteFuncDeclAsFunction`.
13
+ // - Methods (with receivers) are handled within `WriteTypeSpec` when their
14
+ // associated struct/type is defined, so they are skipped here.
15
+ // - General declarations (`ast.GenDecl`), which can contain imports, constants,
16
+ // variables, or type definitions: It iterates through `d.Specs` and calls
17
+ // `WriteSpec` for each specification.
18
+ //
19
+ // A newline is added after each processed declaration or spec group for readability.
20
+ // Unknown declaration types result in a printed diagnostic message.
21
+ func (c *GoToTSCompiler) WriteDecls(decls []ast.Decl) error {
22
+ for _, decl := range decls {
23
+ switch d := decl.(type) {
24
+ case *ast.FuncDecl:
25
+ // Only handle top-level functions here. Methods are handled within WriteTypeSpec.
26
+ if d.Recv == nil {
27
+ if err := c.WriteFuncDeclAsFunction(d); err != nil {
28
+ return err
29
+ }
30
+ c.tsw.WriteLine("") // Add space after function
31
+ }
32
+ case *ast.GenDecl:
33
+ for _, spec := range d.Specs {
34
+ if err := c.WriteSpec(spec); err != nil {
35
+ return err
36
+ }
37
+ c.tsw.WriteLine("") // Add space after spec
38
+ }
39
+ default:
40
+ fmt.Printf("unknown decl: %#v\n", decl)
41
+ }
42
+ }
43
+ return nil
44
+ }
45
+
46
+ // WriteFuncDeclAsFunction translates a Go function declaration (`ast.FuncDecl`)
47
+ // that does not have a receiver (i.e., it's a regular function, not a method)
48
+ // into a TypeScript function.
49
+ // - Go documentation comments (`decl.Doc`) are preserved.
50
+ // - If the Go function is exported (name starts with an uppercase letter) or is
51
+ // the `main` function, the `export` keyword is added to the TypeScript output.
52
+ // - If the `Analysis` data indicates the function is asynchronous, the `async`
53
+ // keyword is prepended.
54
+ // - The function signature (parameters and return type) is translated using `WriteFuncType`,
55
+ // passing the `isAsync` status.
56
+ // - The function body (`decl.Body`) is translated using `WriteStmt`.
57
+ //
58
+ // This function specifically handles top-level functions; methods are generated
59
+ // by `WriteFuncDeclAsMethod` within the context of their type definition.
60
+ func (c *GoToTSCompiler) WriteFuncDeclAsFunction(decl *ast.FuncDecl) error {
61
+ if decl.Recv != nil {
62
+ // This function should not be called for methods.
63
+ // Methods are handled by WriteFuncDeclAsMethod within WriteTypeSpec.
64
+ return nil
65
+ }
66
+
67
+ if decl.Doc != nil {
68
+ c.WriteDoc(decl.Doc)
69
+ }
70
+
71
+ // Exported functions start with uppercase in Go, or special-case "main" entry point
72
+ isExported := decl.Name.IsExported() || decl.Name.Name == "main"
73
+ if isExported {
74
+ c.tsw.WriteLiterally("export ")
75
+ }
76
+
77
+ // Check if this function is async using the analysis data
78
+ var isAsync bool
79
+ if obj := c.pkg.TypesInfo.Defs[decl.Name]; obj != nil {
80
+ isAsync = c.analysis.IsAsyncFunc(obj)
81
+ }
82
+ if isAsync {
83
+ c.tsw.WriteLiterally("async ")
84
+ }
85
+
86
+ c.tsw.WriteLiterally("function ")
87
+ if err := c.WriteValueExpr(decl.Name); err != nil { // Function name is a value identifier
88
+ return fmt.Errorf("failed to write function name: %w", err)
89
+ }
90
+
91
+ // Write type parameters if present
92
+ if decl.Type.TypeParams != nil {
93
+ c.WriteTypeParameters(decl.Type.TypeParams)
94
+ }
95
+
96
+ // WriteFuncType needs to be aware if the function is async
97
+ c.WriteFuncType(decl.Type, isAsync) // Write signature (params, return type)
98
+ c.tsw.WriteLiterally(" ")
99
+
100
+ hasNamedReturns := false
101
+ if decl.Type.Results != nil {
102
+ for _, field := range decl.Type.Results.List {
103
+ if len(field.Names) > 0 {
104
+ hasNamedReturns = true
105
+ break
106
+ }
107
+ }
108
+ }
109
+
110
+ if hasNamedReturns {
111
+ c.tsw.WriteLine("{")
112
+ c.tsw.Indent(1)
113
+
114
+ // Declare named return variables and initialize them to their zero values
115
+ for _, field := range decl.Type.Results.List {
116
+ for _, name := range field.Names {
117
+ c.tsw.WriteLiterallyf("let %s: ", name.Name)
118
+ c.WriteTypeExpr(field.Type)
119
+ c.tsw.WriteLiterally(" = ")
120
+ c.WriteZeroValueForType(c.pkg.TypesInfo.TypeOf(field.Type))
121
+ c.tsw.WriteLine("")
122
+ }
123
+ }
124
+ }
125
+
126
+ if err := c.WriteStmt(decl.Body); err != nil {
127
+ return fmt.Errorf("failed to write function body: %w", err)
128
+ }
129
+
130
+ if hasNamedReturns {
131
+ c.tsw.Indent(-1)
132
+ c.tsw.WriteLine("}")
133
+ }
134
+
135
+ return nil
136
+ }
137
+
138
+ // WriteFuncDeclAsMethod translates a Go function declaration (`ast.FuncDecl`)
139
+ // that has a receiver (i.e., it's a method) into a TypeScript class method.
140
+ // - It preserves Go documentation comments (`decl.Doc`).
141
+ // - The method is declared as `public`.
142
+ // - If the `Analysis` data indicates the method is asynchronous, the `async`
143
+ // keyword is prepended.
144
+ // - The method name retains its original Go casing.
145
+ // - Parameters and return types are translated using `WriteFieldList` and
146
+ // `WriteTypeExpr`, respectively. Async methods have their return types
147
+ // wrapped in `Promise<>`.
148
+ // - The method body is translated. If the Go receiver has a name (e.g., `(s *MyStruct)`),
149
+ // a `const receiverName = this;` binding is generated at the start of the
150
+ // TypeScript method body to make `this` available via the Go receiver's name.
151
+ // If the method body requires deferred cleanup (`NeedsDefer`), the appropriate
152
+ // `using __defer = new $.DisposableStack()` (or `AsyncDisposableStack`)
153
+ // is also generated.
154
+ //
155
+ // This function assumes it is called only for `FuncDecl` nodes that are methods.
156
+ func (c *GoToTSCompiler) WriteFuncDeclAsMethod(decl *ast.FuncDecl) error {
157
+ if decl.Doc != nil {
158
+ c.WriteDoc(decl.Doc)
159
+ }
160
+
161
+ // Determine if method is async
162
+ var isAsync bool
163
+ if obj := c.pkg.TypesInfo.Defs[decl.Name]; obj != nil {
164
+ isAsync = c.analysis.IsAsyncFunc(obj)
165
+ }
166
+
167
+ // Methods are typically public in the TS output
168
+ c.tsw.WriteLiterally("public ")
169
+
170
+ // Add async modifier if needed
171
+ if isAsync {
172
+ c.tsw.WriteLiterally("async ")
173
+ }
174
+
175
+ // Keep original Go casing for method names
176
+ if err := c.WriteValueExpr(decl.Name); err != nil { // Method name is a value identifier
177
+ return err
178
+ }
179
+
180
+ // Write signature (parameters and return type)
181
+ // We adapt the logic from WriteFuncType here, but without the 'function' keyword
182
+ funcType := decl.Type
183
+ c.tsw.WriteLiterally("(")
184
+ if funcType.Params != nil {
185
+ c.WriteFieldList(funcType.Params, true) // true = arguments
186
+ }
187
+ c.tsw.WriteLiterally(")")
188
+
189
+ // Handle return type
190
+ if funcType.Results != nil && len(funcType.Results.List) > 0 {
191
+ c.tsw.WriteLiterally(": ")
192
+ if isAsync {
193
+ c.tsw.WriteLiterally("Promise<")
194
+ }
195
+ if len(funcType.Results.List) == 1 {
196
+ // Single return value
197
+ resultType := funcType.Results.List[0].Type
198
+ c.WriteTypeExpr(resultType)
199
+ } else {
200
+ // Multiple return values -> tuple type
201
+ c.tsw.WriteLiterally("[")
202
+ for i, field := range funcType.Results.List {
203
+ if i > 0 {
204
+ c.tsw.WriteLiterally(", ")
205
+ }
206
+ c.WriteTypeExpr(field.Type)
207
+ }
208
+ c.tsw.WriteLiterally("]")
209
+ }
210
+ if isAsync {
211
+ c.tsw.WriteLiterally(">")
212
+ }
213
+ } else {
214
+ // No return value -> void
215
+ if isAsync {
216
+ c.tsw.WriteLiterally(": Promise<void>")
217
+ } else {
218
+ c.tsw.WriteLiterally(": void")
219
+ }
220
+ }
221
+
222
+ c.tsw.WriteLiterally(" ")
223
+
224
+ // Bind receiver name to this
225
+ if recvField := decl.Recv.List[0]; len(recvField.Names) > 0 {
226
+ recvName := recvField.Names[0].Name
227
+ if recvName != "_" {
228
+ c.tsw.WriteLine("{")
229
+ c.tsw.Indent(1)
230
+ c.tsw.WriteLinef("const %s = this", recvName)
231
+
232
+ // Add using statement if needed
233
+ if c.analysis.NeedsDefer(decl.Body) {
234
+ if c.analysis.IsInAsyncFunction(decl) {
235
+ c.tsw.WriteLine("await using __defer = new $.AsyncDisposableStack();")
236
+ } else {
237
+ c.tsw.WriteLine("using cleanup = new $.DisposableStack();")
238
+ }
239
+ }
240
+
241
+ // write method body without outer braces
242
+ for _, stmt := range decl.Body.List {
243
+ if err := c.WriteStmt(stmt); err != nil {
244
+ return fmt.Errorf("failed to write statement in function body: %w", err)
245
+ }
246
+ }
247
+ c.tsw.Indent(-1)
248
+ c.tsw.WriteLine("}")
249
+
250
+ return nil
251
+ }
252
+ }
253
+ // no named receiver, write whole body
254
+ if err := c.WriteStmt(decl.Body); err != nil {
255
+ return fmt.Errorf("failed to write function body: %w", err)
256
+ }
257
+
258
+ return nil
259
+ }