goscript 0.0.57 → 0.0.58

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/compiler/decl.go CHANGED
@@ -3,7 +3,9 @@ package compiler
3
3
  import (
4
4
  "fmt"
5
5
  "go/ast"
6
+ "go/token"
6
7
  "go/types"
8
+ "sort"
7
9
  )
8
10
 
9
11
  // WriteDecls iterates through a slice of Go top-level declarations (`ast.Decl`)
@@ -17,33 +19,384 @@ import (
17
19
  // variables, or type definitions: It iterates through `d.Specs` and calls
18
20
  // `WriteSpec` for each specification.
19
21
  //
22
+ // Type declarations are sorted by dependencies to ensure referenced types are
23
+ // defined before types that reference them, avoiding initialization order issues.
20
24
  // A newline is added after each processed declaration or spec group for readability.
21
25
  // Unknown declaration types result in a printed diagnostic message.
22
26
  func (c *GoToTSCompiler) WriteDecls(decls []ast.Decl) error {
27
+ // Separate type declarations from other declarations for dependency sorting
28
+ var typeSpecs []*ast.TypeSpec
29
+ var varSpecs []*ast.ValueSpec
30
+ var otherDecls []ast.Decl
31
+ var otherSpecs []ast.Spec
32
+
23
33
  for _, decl := range decls {
24
34
  switch d := decl.(type) {
25
35
  case *ast.FuncDecl:
26
36
  // Only handle top-level functions here. Methods are handled within WriteTypeSpec.
27
37
  if d.Recv == nil {
28
- if err := c.WriteFuncDeclAsFunction(d); err != nil {
29
- return err
30
- }
31
- c.tsw.WriteLine("") // Add space after function
38
+ otherDecls = append(otherDecls, d)
32
39
  }
33
40
  case *ast.GenDecl:
34
41
  for _, spec := range d.Specs {
35
- if err := c.WriteSpec(spec); err != nil {
36
- return err
42
+ if typeSpec, ok := spec.(*ast.TypeSpec); ok {
43
+ typeSpecs = append(typeSpecs, typeSpec)
44
+ } else if varSpec, ok := spec.(*ast.ValueSpec); ok && d.Tok == token.VAR {
45
+ varSpecs = append(varSpecs, varSpec)
46
+ } else {
47
+ otherSpecs = append(otherSpecs, spec)
37
48
  }
38
- c.tsw.WriteLine("") // Add space after spec
39
49
  }
50
+ default:
51
+ otherDecls = append(otherDecls, d)
52
+ }
53
+ }
54
+
55
+ // Sort type declarations by dependencies
56
+ sortedTypeSpecs, err := c.sortTypeSpecsByDependencies(typeSpecs)
57
+ if err != nil {
58
+ return fmt.Errorf("failed to sort type declarations: %w", err)
59
+ }
60
+
61
+ // Sort variable declarations by type dependencies
62
+ sortedVarSpecs, err := c.sortVarSpecsByTypeDependencies(varSpecs, typeSpecs)
63
+ if err != nil {
64
+ return fmt.Errorf("failed to sort variable declarations: %w", err)
65
+ }
66
+
67
+ // Write non-type, non-var declarations first (imports, constants)
68
+ for _, spec := range otherSpecs {
69
+ if err := c.WriteSpec(spec); err != nil {
70
+ return err
71
+ }
72
+ c.tsw.WriteLine("") // Add space after spec
73
+ }
74
+
75
+ // Write sorted type declarations
76
+ for _, typeSpec := range sortedTypeSpecs {
77
+ if err := c.WriteSpec(typeSpec); err != nil {
78
+ return err
79
+ }
80
+ c.tsw.WriteLine("") // Add space after spec
81
+ }
82
+
83
+ // Write sorted variable declarations
84
+ for _, varSpec := range sortedVarSpecs {
85
+ if err := c.WriteSpec(varSpec); err != nil {
86
+ return err
87
+ }
88
+ c.tsw.WriteLine("") // Add space after spec
89
+ }
90
+
91
+ // Write function declarations last
92
+ for _, decl := range otherDecls {
93
+ switch d := decl.(type) {
94
+ case *ast.FuncDecl:
95
+ if err := c.WriteFuncDeclAsFunction(d); err != nil {
96
+ return err
97
+ }
98
+ c.tsw.WriteLine("") // Add space after function
40
99
  default:
41
100
  return fmt.Errorf("unknown decl: %#v", decl)
42
101
  }
43
102
  }
103
+
44
104
  return nil
45
105
  }
46
106
 
107
+ // sortTypeSpecsByDependencies performs a topological sort of type specifications
108
+ // based on their dependencies to ensure referenced types are defined before
109
+ // types that reference them.
110
+ func (c *GoToTSCompiler) sortTypeSpecsByDependencies(typeSpecs []*ast.TypeSpec) ([]*ast.TypeSpec, error) {
111
+ if len(typeSpecs) <= 1 {
112
+ return typeSpecs, nil
113
+ }
114
+
115
+ // Build dependency graph
116
+ dependencies := make(map[string][]string) // typeName -> list of types it depends on
117
+ typeSpecMap := make(map[string]*ast.TypeSpec)
118
+
119
+ // First pass: collect all type names
120
+ for _, typeSpec := range typeSpecs {
121
+ typeName := typeSpec.Name.Name
122
+ typeSpecMap[typeName] = typeSpec
123
+ dependencies[typeName] = []string{}
124
+ }
125
+
126
+ // Second pass: analyze dependencies
127
+ for _, typeSpec := range typeSpecs {
128
+ typeName := typeSpec.Name.Name
129
+ deps := c.extractTypeDependencies(typeSpec.Type, typeSpecMap)
130
+ dependencies[typeName] = deps
131
+ }
132
+
133
+ // Perform topological sort
134
+ sorted, err := c.topologicalSort(dependencies)
135
+ if err != nil {
136
+ return nil, err
137
+ }
138
+
139
+ // Build result in sorted order
140
+ var result []*ast.TypeSpec
141
+ for _, typeName := range sorted {
142
+ if typeSpec, exists := typeSpecMap[typeName]; exists {
143
+ result = append(result, typeSpec)
144
+ }
145
+ }
146
+
147
+ return result, nil
148
+ }
149
+
150
+ // extractTypeDependencies extracts structural type dependencies from a type expression.
151
+ // Only dependencies that affect class initialization order are considered:
152
+ // - Struct field types (cause initialization order issues)
153
+ // - Embedded types (directly affect struct layout)
154
+ // - Type aliases (direct type references)
155
+ // Method parameters and return types are ignored as they're just annotations.
156
+ func (c *GoToTSCompiler) extractTypeDependencies(typeExpr ast.Expr, typeSpecMap map[string]*ast.TypeSpec) []string {
157
+ var deps []string
158
+
159
+ switch t := typeExpr.(type) {
160
+ case *ast.Ident:
161
+ // Direct type reference (e.g., type MyType OtherType)
162
+ if _, isLocalType := typeSpecMap[t.Name]; isLocalType {
163
+ deps = append(deps, t.Name)
164
+ }
165
+
166
+ case *ast.StructType:
167
+ // Struct type - check field types only
168
+ if t.Fields != nil {
169
+ for _, field := range t.Fields.List {
170
+ fieldDeps := c.extractTypeDependencies(field.Type, typeSpecMap)
171
+ deps = append(deps, fieldDeps...)
172
+ }
173
+ }
174
+
175
+ case *ast.ArrayType:
176
+ // Array type - check element type
177
+ elemDeps := c.extractTypeDependencies(t.Elt, typeSpecMap)
178
+ deps = append(deps, elemDeps...)
179
+
180
+ case *ast.StarExpr:
181
+ // Pointer type - check pointed-to type
182
+ ptrDeps := c.extractTypeDependencies(t.X, typeSpecMap)
183
+ deps = append(deps, ptrDeps...)
184
+
185
+ case *ast.MapType:
186
+ // Map type - check key and value types
187
+ keyDeps := c.extractTypeDependencies(t.Key, typeSpecMap)
188
+ valueDeps := c.extractTypeDependencies(t.Value, typeSpecMap)
189
+ deps = append(deps, keyDeps...)
190
+ deps = append(deps, valueDeps...)
191
+
192
+ case *ast.InterfaceType:
193
+ // Interface type - methods don't create initialization dependencies
194
+ // Only embedded interfaces matter, but those are rare and complex to handle
195
+ // For now, interfaces are considered to have no dependencies
196
+
197
+ case *ast.FuncType:
198
+ // Function types don't create initialization dependencies
199
+
200
+ case *ast.SelectorExpr:
201
+ // External package types don't create local dependencies
202
+
203
+ // Add other type expressions as needed
204
+ }
205
+
206
+ // Sort dependencies for deterministic output
207
+ sort.Strings(deps)
208
+ return deps
209
+ }
210
+
211
+ // sortVarSpecsByTypeDependencies sorts variable declarations based on their type dependencies
212
+ func (c *GoToTSCompiler) sortVarSpecsByTypeDependencies(varSpecs []*ast.ValueSpec, typeSpecs []*ast.TypeSpec) ([]*ast.ValueSpec, error) {
213
+ if len(varSpecs) <= 1 {
214
+ return varSpecs, nil
215
+ }
216
+
217
+ // Build type name map
218
+ typeSpecMap := make(map[string]*ast.TypeSpec)
219
+ for _, typeSpec := range typeSpecs {
220
+ typeSpecMap[typeSpec.Name.Name] = typeSpec
221
+ }
222
+
223
+ // Group variables by dependency status with names for sorting
224
+ type namedVarSpec struct {
225
+ spec *ast.ValueSpec
226
+ name string
227
+ }
228
+
229
+ var independentVars []namedVarSpec
230
+ var dependentVars []namedVarSpec
231
+
232
+ for _, varSpec := range varSpecs {
233
+ // Get variable name for sorting
234
+ varName := ""
235
+ if len(varSpec.Names) > 0 {
236
+ varName = varSpec.Names[0].Name
237
+ }
238
+
239
+ hasDependency := false
240
+
241
+ // Check type annotation
242
+ if varSpec.Type != nil {
243
+ deps := c.extractTypeDependencies(varSpec.Type, typeSpecMap)
244
+ if len(deps) > 0 {
245
+ hasDependency = true
246
+ }
247
+ }
248
+
249
+ // Check initializer expressions for type usage
250
+ if !hasDependency {
251
+ for _, value := range varSpec.Values {
252
+ if c.hasTypeReferences(value, typeSpecMap) {
253
+ hasDependency = true
254
+ break
255
+ }
256
+ }
257
+ }
258
+
259
+ namedVar := namedVarSpec{spec: varSpec, name: varName}
260
+ if hasDependency {
261
+ dependentVars = append(dependentVars, namedVar)
262
+ } else {
263
+ independentVars = append(independentVars, namedVar)
264
+ }
265
+ }
266
+
267
+ // Sort both groups by name for deterministic output
268
+ sort.Slice(independentVars, func(i, j int) bool {
269
+ return independentVars[i].name < independentVars[j].name
270
+ })
271
+ sort.Slice(dependentVars, func(i, j int) bool {
272
+ return dependentVars[i].name < dependentVars[j].name
273
+ })
274
+
275
+ // Return independent variables first, then dependent ones
276
+ result := make([]*ast.ValueSpec, 0, len(varSpecs))
277
+ for _, namedVar := range independentVars {
278
+ result = append(result, namedVar.spec)
279
+ }
280
+ for _, namedVar := range dependentVars {
281
+ result = append(result, namedVar.spec)
282
+ }
283
+
284
+ return result, nil
285
+ }
286
+
287
+ // hasTypeReferences checks if an expression contains references to local types
288
+ func (c *GoToTSCompiler) hasTypeReferences(expr ast.Expr, typeSpecMap map[string]*ast.TypeSpec) bool {
289
+ hasRef := false
290
+
291
+ ast.Inspect(expr, func(n ast.Node) bool {
292
+ switch t := n.(type) {
293
+ case *ast.CallExpr:
294
+ // Check function calls like new Message(), makeChannel<Message>()
295
+ if ident, ok := t.Fun.(*ast.Ident); ok {
296
+ if _, isLocalType := typeSpecMap[ident.Name]; isLocalType {
297
+ hasRef = true
298
+ return false
299
+ }
300
+ }
301
+ // Check type arguments in generic calls
302
+ if funcType, ok := t.Fun.(*ast.IndexExpr); ok {
303
+ if c.hasTypeReferences(funcType.Index, typeSpecMap) {
304
+ hasRef = true
305
+ return false
306
+ }
307
+ }
308
+ case *ast.CompositeLit:
309
+ // Check composite literals like Message{...}
310
+ if ident, ok := t.Type.(*ast.Ident); ok {
311
+ if _, isLocalType := typeSpecMap[ident.Name]; isLocalType {
312
+ hasRef = true
313
+ return false
314
+ }
315
+ }
316
+ case *ast.Ident:
317
+ // Check direct type references
318
+ if _, isLocalType := typeSpecMap[t.Name]; isLocalType {
319
+ hasRef = true
320
+ return false
321
+ }
322
+ }
323
+ return !hasRef // Stop walking if we found a reference
324
+ })
325
+
326
+ return hasRef
327
+ }
328
+
329
+ // topologicalSort performs a topological sort of the dependency graph
330
+ func (c *GoToTSCompiler) topologicalSort(dependencies map[string][]string) ([]string, error) {
331
+ // Kahn's algorithm for topological sorting with deterministic ordering
332
+ inDegree := make(map[string]int)
333
+ graph := make(map[string][]string)
334
+
335
+ // Initialize in-degree counts and reverse graph
336
+ for node := range dependencies {
337
+ inDegree[node] = 0
338
+ graph[node] = []string{}
339
+ }
340
+
341
+ // Build reverse graph and count in-degrees
342
+ for node, deps := range dependencies {
343
+ // Sort dependencies for consistent output
344
+ sortedDeps := make([]string, len(deps))
345
+ copy(sortedDeps, deps)
346
+ sort.Strings(sortedDeps)
347
+
348
+ for _, dep := range sortedDeps {
349
+ if _, exists := inDegree[dep]; exists {
350
+ graph[dep] = append(graph[dep], node)
351
+ inDegree[node]++
352
+ }
353
+ }
354
+ }
355
+
356
+ // Sort neighbors in graph for consistency
357
+ for node := range graph {
358
+ sort.Strings(graph[node])
359
+ }
360
+
361
+ // Find nodes with no incoming edges and sort them
362
+ var queue []string
363
+ for node, degree := range inDegree {
364
+ if degree == 0 {
365
+ queue = append(queue, node)
366
+ }
367
+ }
368
+ sort.Strings(queue) // Sort initial queue for deterministic output
369
+
370
+ var result []string
371
+
372
+ for len(queue) > 0 {
373
+ // Remove node from queue (already sorted)
374
+ current := queue[0]
375
+ queue = queue[1:]
376
+ result = append(result, current)
377
+
378
+ // Collect new zero-degree nodes
379
+ var newZeroNodes []string
380
+ for _, neighbor := range graph[current] {
381
+ inDegree[neighbor]--
382
+ if inDegree[neighbor] == 0 {
383
+ newZeroNodes = append(newZeroNodes, neighbor)
384
+ }
385
+ }
386
+
387
+ // Sort new zero-degree nodes and add to queue
388
+ sort.Strings(newZeroNodes)
389
+ queue = append(queue, newZeroNodes...)
390
+ }
391
+
392
+ // Check for cycles
393
+ if len(result) != len(dependencies) {
394
+ return nil, fmt.Errorf("circular dependency detected in type declarations")
395
+ }
396
+
397
+ return result, nil
398
+ }
399
+
47
400
  // WriteFuncDeclAsFunction translates a Go function declaration (`ast.FuncDecl`)
48
401
  // that does not have a receiver (i.e., it's a regular function, not a method)
49
402
  // into a TypeScript function.
package/compiler/stmt.go CHANGED
@@ -680,6 +680,12 @@ func (c *GoToTSCompiler) WriteStmtBlock(exp *ast.BlockStmt, suppressNewline bool
680
680
  hasAsyncDefer = true
681
681
  break
682
682
  }
683
+ } else {
684
+ // Check if the deferred call is to an async function
685
+ if c.isCallAsyncInDefer(deferStmt.Call) {
686
+ hasAsyncDefer = true
687
+ break
688
+ }
683
689
  }
684
690
  }
685
691
  }
@@ -877,6 +883,9 @@ func (c *GoToTSCompiler) WriteStmtDefer(exp *ast.DeferStmt) error {
877
883
  isAsyncDeferred := false
878
884
  if funcLit, ok := exp.Call.Fun.(*ast.FuncLit); ok {
879
885
  isAsyncDeferred = c.analysis.IsFuncLitAsync(funcLit)
886
+ } else {
887
+ // Check if the deferred call is to an async function
888
+ isAsyncDeferred = c.isCallAsyncInDefer(exp.Call)
880
889
  }
881
890
 
882
891
  // Set async prefix based on pre-computed async status
@@ -914,6 +923,35 @@ func (c *GoToTSCompiler) WriteStmtDefer(exp *ast.DeferStmt) error {
914
923
  return nil
915
924
  }
916
925
 
926
+ // isCallAsyncInDefer determines if a call expression in a defer statement is async
927
+ func (c *GoToTSCompiler) isCallAsyncInDefer(callExpr *ast.CallExpr) bool {
928
+ switch fun := callExpr.Fun.(type) {
929
+ case *ast.Ident:
930
+ // Direct function call (e.g., defer myFunc())
931
+ if obj := c.pkg.TypesInfo.Uses[fun]; obj != nil {
932
+ return c.analysis.IsAsyncFunc(obj)
933
+ }
934
+ case *ast.SelectorExpr:
935
+ // Method call (e.g., defer handle.Release()) or package function call
936
+ if selection := c.pkg.TypesInfo.Selections[fun]; selection != nil {
937
+ // Method call on an object
938
+ if methodObj := selection.Obj(); methodObj != nil {
939
+ return c.analysis.IsAsyncFunc(methodObj)
940
+ }
941
+ } else if ident, ok := fun.X.(*ast.Ident); ok {
942
+ // Package-level function call (e.g., defer time.Sleep())
943
+ if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
944
+ if pkgName, isPkg := obj.(*types.PkgName); isPkg {
945
+ methodName := fun.Sel.Name
946
+ pkgPath := pkgName.Imported().Path()
947
+ return c.analysis.IsMethodAsync(pkgPath, "", methodName)
948
+ }
949
+ }
950
+ }
951
+ }
952
+ return false
953
+ }
954
+
917
955
  // WriteStmtLabeled handles labeled statements (ast.LabeledStmt), such as "label: statement".
918
956
  // In TypeScript, labels cannot be used with variable declarations, so we need to handle this case specially.
919
957
  func (c *GoToTSCompiler) WriteStmtLabeled(stmt *ast.LabeledStmt) error {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "goscript",
3
3
  "description": "Go to TypeScript transpiler",
4
- "version": "0.0.57",
4
+ "version": "0.0.58",
5
5
  "author": {
6
6
  "name": "Aperture Robotics LLC.",
7
7
  "email": "support@aperture.us",