goscript 0.0.22 → 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.
@@ -9,8 +9,8 @@ import (
9
9
  "github.com/pkg/errors"
10
10
  "github.com/sirupsen/logrus"
11
11
 
12
- // _ ensure we include the builtin package
13
- _ "github.com/aperturerobotics/goscript/builtin"
12
+ // _ ensure we include the gs package
13
+ _ "github.com/aperturerobotics/goscript"
14
14
  )
15
15
 
16
16
  var (
@@ -41,6 +41,21 @@ type VariableUsageInfo struct {
41
41
  Destinations []AssignmentInfo
42
42
  }
43
43
 
44
+ // FunctionInfo consolidates function-related tracking data.
45
+ type FunctionInfo struct {
46
+ IsAsync bool
47
+ NamedReturns []string
48
+ }
49
+
50
+ // NodeInfo consolidates node-related tracking data.
51
+ type NodeInfo struct {
52
+ NeedsDefer bool
53
+ InAsyncContext bool
54
+ IsBareReturn bool
55
+ EnclosingFuncDecl *ast.FuncDecl
56
+ EnclosingFuncLit *ast.FuncLit
57
+ }
58
+
44
59
  // Analysis holds information gathered during the analysis phase of the Go code compilation.
45
60
  // This data is used to make decisions about how to generate TypeScript code.
46
61
  // Analysis is read-only after being built and should not be modified during code generation.
@@ -55,25 +70,25 @@ type Analysis struct {
55
70
  // Cmap stores the comment map for the file
56
71
  Cmap ast.CommentMap
57
72
 
58
- // AsyncFuncs tracks which functions are async using the function's types.Object
59
- // as the key to avoid false-positive matches with functions having the same name
60
- AsyncFuncs map[types.Object]bool
73
+ // FunctionData consolidates function-related tracking into one map
74
+ FunctionData map[types.Object]*FunctionInfo
61
75
 
62
- // NeedsDeferMap tracks nodes that need defer handling
63
- NeedsDeferMap map[ast.Node]bool
76
+ // NodeData consolidates node-related tracking into one map
77
+ NodeData map[ast.Node]*NodeInfo
64
78
 
65
- // IsInAsyncFunctionMap tracks nodes that are inside async functions
66
- IsInAsyncFunctionMap map[ast.Node]bool
79
+ // Keep specialized maps that serve different purposes
80
+ // FuncLitData tracks function literal specific data since they don't have types.Object
81
+ FuncLitData map[*ast.FuncLit]*FunctionInfo
67
82
  }
68
83
 
69
84
  // NewAnalysis creates a new Analysis instance.
70
85
  func NewAnalysis() *Analysis {
71
86
  return &Analysis{
72
- VariableUsage: make(map[types.Object]*VariableUsageInfo),
73
- Imports: make(map[string]*fileImport),
74
- AsyncFuncs: make(map[types.Object]bool),
75
- NeedsDeferMap: make(map[ast.Node]bool),
76
- IsInAsyncFunctionMap: make(map[ast.Node]bool),
87
+ VariableUsage: make(map[types.Object]*VariableUsageInfo),
88
+ Imports: make(map[string]*fileImport),
89
+ FunctionData: make(map[types.Object]*FunctionInfo),
90
+ NodeData: make(map[ast.Node]*NodeInfo),
91
+ FuncLitData: make(map[*ast.FuncLit]*FunctionInfo),
77
92
  }
78
93
  }
79
94
 
@@ -82,7 +97,11 @@ func (a *Analysis) NeedsDefer(node ast.Node) bool {
82
97
  if node == nil {
83
98
  return false
84
99
  }
85
- return a.NeedsDeferMap[node]
100
+ nodeInfo := a.NodeData[node]
101
+ if nodeInfo == nil {
102
+ return false
103
+ }
104
+ return nodeInfo.NeedsDefer
86
105
  }
87
106
 
88
107
  // IsInAsyncFunction returns whether the given node is inside an async function.
@@ -90,7 +109,11 @@ func (a *Analysis) IsInAsyncFunction(node ast.Node) bool {
90
109
  if node == nil {
91
110
  return false
92
111
  }
93
- return a.IsInAsyncFunctionMap[node]
112
+ nodeInfo := a.NodeData[node]
113
+ if nodeInfo == nil {
114
+ return false
115
+ }
116
+ return nodeInfo.InAsyncContext
94
117
  }
95
118
 
96
119
  // IsAsyncFunc returns whether the given object represents an async function.
@@ -98,7 +121,11 @@ func (a *Analysis) IsAsyncFunc(obj types.Object) bool {
98
121
  if obj == nil {
99
122
  return false
100
123
  }
101
- return a.AsyncFuncs[obj]
124
+ funcInfo := a.FunctionData[obj]
125
+ if funcInfo == nil {
126
+ return false
127
+ }
128
+ return funcInfo.IsAsync
102
129
  }
103
130
 
104
131
  // IsFuncLitAsync checks if a function literal is async based on our analysis.
@@ -106,8 +133,16 @@ func (a *Analysis) IsFuncLitAsync(funcLit *ast.FuncLit) bool {
106
133
  if funcLit == nil {
107
134
  return false
108
135
  }
109
- // Function literals are marked during analysis if they contain async operations
110
- return a.IsInAsyncFunctionMap[funcLit]
136
+ // Check function literal specific data first
137
+ if funcInfo := a.FuncLitData[funcLit]; funcInfo != nil {
138
+ return funcInfo.IsAsync
139
+ }
140
+ // Fall back to node data for backwards compatibility
141
+ nodeInfo := a.NodeData[funcLit]
142
+ if nodeInfo == nil {
143
+ return false
144
+ }
145
+ return nodeInfo.InAsyncContext
111
146
  }
112
147
 
113
148
  // NeedsBoxed returns whether the given object needs to be boxed.
@@ -302,6 +337,12 @@ type analysisVisitor struct {
302
337
 
303
338
  // currentFuncObj tracks the object of the function declaration we're currently analyzing
304
339
  currentFuncObj types.Object
340
+
341
+ // currentFuncDecl tracks the *ast.FuncDecl of the function we're currently analyzing.
342
+ currentFuncDecl *ast.FuncDecl
343
+
344
+ // currentFuncLit tracks the *ast.FuncLit of the function literal we're currently analyzing.
345
+ currentFuncLit *ast.FuncLit
305
346
  }
306
347
 
307
348
  // getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
@@ -324,8 +365,11 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
324
365
  return nil
325
366
  }
326
367
 
327
- // Store async state for the current node
328
- v.analysis.IsInAsyncFunctionMap[node] = v.inAsyncFunction
368
+ // Initialize and store async state for the current node
369
+ if v.analysis.NodeData[node] == nil {
370
+ v.analysis.NodeData[node] = &NodeInfo{}
371
+ }
372
+ v.analysis.NodeData[node].InAsyncContext = v.inAsyncFunction
329
373
 
330
374
  switch n := node.(type) {
331
375
  case *ast.GenDecl:
@@ -393,24 +437,40 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
393
437
  return v
394
438
 
395
439
  case *ast.FuncDecl:
396
- // Determine if this function declaration is async based on its body
440
+ // Save original states to restore after visiting
441
+ originalInAsync := v.inAsyncFunction
442
+ originalFuncObj := v.currentFuncObj
443
+ originalFuncDecl := v.currentFuncDecl
444
+ originalFuncLit := v.currentFuncLit
445
+ originalReceiver := v.currentReceiver
446
+
447
+ // Reset for current function
397
448
  v.currentFuncName = n.Name.Name
449
+ v.currentFuncDecl = n
450
+ v.currentFuncLit = nil
451
+ v.currentReceiver = nil
452
+
453
+ // Determine if this function declaration is async based on its body
398
454
  isAsync := false
399
455
  if n.Body != nil {
400
456
  containsAsyncOps := v.containsAsyncOperations(n.Body)
401
457
  if containsAsyncOps {
402
458
  // Get the object for this function declaration
403
459
  if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
404
- v.analysis.AsyncFuncs[obj] = true
460
+ v.analysis.FunctionData[obj] = &FunctionInfo{
461
+ IsAsync: true,
462
+ NamedReturns: v.getNamedReturns(n),
463
+ }
464
+ isAsync = true
405
465
  }
406
- isAsync = true
407
466
  }
408
467
  }
409
- v.analysis.IsInAsyncFunctionMap[n] = isAsync
468
+ if v.analysis.NodeData[n] == nil {
469
+ v.analysis.NodeData[n] = &NodeInfo{}
470
+ }
471
+ v.analysis.NodeData[n].InAsyncContext = isAsync
410
472
 
411
473
  // Set current receiver if this is a method
412
- originalReceiver := v.currentReceiver
413
- v.currentReceiver = nil // Reset for current function
414
474
  if n.Recv != nil && len(n.Recv.List) > 0 {
415
475
  // Assuming a single receiver for simplicity for now
416
476
  if len(n.Recv.List[0].Names) > 0 {
@@ -427,22 +487,41 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
427
487
  }
428
488
  }
429
489
 
430
- // Save original states to restore after visiting
431
- originalInAsync := v.inAsyncFunction
432
- originalFuncObj := v.currentFuncObj
490
+ // Store named return variables
491
+ if n.Type != nil && n.Type.Results != nil {
492
+ var namedReturns []string
493
+ for _, field := range n.Type.Results.List {
494
+ for _, name := range field.Names {
495
+ namedReturns = append(namedReturns, name.Name)
496
+ }
497
+ }
498
+ if len(namedReturns) > 0 {
499
+ if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
500
+ if v.analysis.FunctionData[obj] == nil {
501
+ v.analysis.FunctionData[obj] = &FunctionInfo{}
502
+ }
503
+ v.analysis.FunctionData[obj].NamedReturns = namedReturns
504
+ }
505
+ }
506
+ }
433
507
 
434
508
  // Update visitor state for this function
435
509
  v.inAsyncFunction = isAsync
436
510
  v.currentFuncObj = v.pkg.TypesInfo.ObjectOf(n.Name)
437
- v.analysis.IsInAsyncFunctionMap[n] = isAsync // Ensure FuncDecl node itself is marked
511
+ v.analysis.NodeData[n].InAsyncContext = isAsync // Ensure FuncDecl node itself is marked
438
512
 
439
- // Check if the body contains any defer statements
440
- if n.Body != nil && v.containsDefer(n.Body) {
441
- v.analysis.NeedsDeferMap[n.Body] = true
442
- }
513
+ if n.Body != nil {
514
+ // Check if the body contains any defer statements
515
+ if v.containsDefer(n.Body) {
516
+ if v.analysis.NodeData[n] == nil {
517
+ v.analysis.NodeData[n] = &NodeInfo{}
518
+ }
519
+ v.analysis.NodeData[n].NeedsDefer = true
520
+ }
443
521
 
444
- // Visit the body with updated state
445
- ast.Walk(v, n.Body)
522
+ // Visit the body with updated state
523
+ ast.Walk(v, n.Body)
524
+ }
446
525
 
447
526
  // Restore states after visiting
448
527
  defer func() {
@@ -450,21 +529,52 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
450
529
  v.inAsyncFunction = originalInAsync
451
530
  v.currentReceiver = originalReceiver
452
531
  v.currentFuncObj = originalFuncObj
532
+ v.currentFuncDecl = originalFuncDecl
533
+ v.currentFuncLit = originalFuncLit
453
534
  }()
454
535
  return nil // Stop traversal here, ast.Walk handled the body
455
536
 
456
537
  case *ast.FuncLit:
538
+ // Save original inAsyncFunction state to restore after visiting
539
+ originalInAsync := v.inAsyncFunction
540
+ originalFuncDecl := v.currentFuncDecl
541
+ originalFuncLit := v.currentFuncLit
542
+
543
+ // Set current function literal
544
+ v.currentFuncDecl = nil
545
+ v.currentFuncLit = n
546
+
457
547
  // Determine if this function literal is async based on its body
458
548
  isAsync := v.containsAsyncOperations(n.Body)
459
- v.analysis.IsInAsyncFunctionMap[n] = isAsync
549
+ if v.analysis.NodeData[n] == nil {
550
+ v.analysis.NodeData[n] = &NodeInfo{}
551
+ }
552
+ v.analysis.NodeData[n].InAsyncContext = isAsync
553
+
554
+ // Store named return variables for function literal
555
+ if n.Type != nil && n.Type.Results != nil {
556
+ var namedReturns []string
557
+ for _, field := range n.Type.Results.List {
558
+ for _, name := range field.Names {
559
+ namedReturns = append(namedReturns, name.Name)
560
+ }
561
+ }
562
+ if len(namedReturns) > 0 {
563
+ v.analysis.FuncLitData[n] = &FunctionInfo{
564
+ IsAsync: isAsync,
565
+ NamedReturns: namedReturns,
566
+ }
567
+ }
568
+ }
460
569
 
461
- // Save original inAsyncFunction state to restore after visiting
462
- originalInAsync := v.inAsyncFunction
463
570
  v.inAsyncFunction = isAsync
464
571
 
465
572
  // Check if the body contains any defer statements
466
573
  if n.Body != nil && v.containsDefer(n.Body) {
467
- v.analysis.NeedsDeferMap[n.Body] = true
574
+ if v.analysis.NodeData[n] == nil {
575
+ v.analysis.NodeData[n] = &NodeInfo{}
576
+ }
577
+ v.analysis.NodeData[n].NeedsDefer = true
468
578
  }
469
579
 
470
580
  // Visit the body with updated state
@@ -472,16 +582,27 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
472
582
 
473
583
  // Restore inAsyncFunction state after visiting
474
584
  v.inAsyncFunction = originalInAsync
585
+ v.currentFuncDecl = originalFuncDecl
586
+ v.currentFuncLit = originalFuncLit
475
587
  return nil // Stop traversal here, ast.Walk handled the body
476
588
 
477
589
  case *ast.BlockStmt:
590
+ if n == nil || len(n.List) == 0 {
591
+ break
592
+ }
593
+
594
+ // Initialize NodeData for this block
595
+ if v.analysis.NodeData[n] == nil {
596
+ v.analysis.NodeData[n] = &NodeInfo{}
597
+ }
598
+
478
599
  // Check for defer statements in this block
479
600
  if v.containsDefer(n) {
480
- v.analysis.NeedsDeferMap[n] = true
601
+ v.analysis.NodeData[n].NeedsDefer = true
481
602
  }
482
603
 
483
604
  // Store async state for this block
484
- v.analysis.IsInAsyncFunctionMap[n] = v.inAsyncFunction
605
+ v.analysis.NodeData[n].InAsyncContext = v.inAsyncFunction
485
606
 
486
607
  return v
487
608
 
@@ -500,12 +621,18 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
500
621
  if obj := v.pkg.TypesInfo.Uses[funcIdent]; obj != nil && v.analysis.IsAsyncFunc(obj) {
501
622
  // We're calling an async function, so mark current function as async if we're in one
502
623
  if v.currentFuncObj != nil {
503
- v.analysis.AsyncFuncs[v.currentFuncObj] = true
624
+ v.analysis.FunctionData[v.currentFuncObj] = &FunctionInfo{
625
+ IsAsync: true,
626
+ NamedReturns: v.getNamedReturns(v.currentFuncDecl),
627
+ }
504
628
  v.inAsyncFunction = true // Update visitor state
505
629
  // Mark the FuncDecl node itself if possible (might need to store the node too)
506
- for nodeAst := range v.analysis.IsInAsyncFunctionMap { // Find the node to update
630
+ for nodeAst := range v.analysis.NodeData { // Find the node to update
507
631
  if fd, ok := nodeAst.(*ast.FuncDecl); ok && v.pkg.TypesInfo.ObjectOf(fd.Name) == v.currentFuncObj {
508
- v.analysis.IsInAsyncFunctionMap[nodeAst] = true
632
+ if v.analysis.NodeData[nodeAst] == nil {
633
+ v.analysis.NodeData[nodeAst] = &NodeInfo{}
634
+ }
635
+ v.analysis.NodeData[nodeAst].InAsyncContext = true
509
636
  }
510
637
  }
511
638
  }
@@ -513,7 +640,10 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
513
640
  }
514
641
 
515
642
  // Store async state for this call expression
516
- v.analysis.IsInAsyncFunctionMap[n] = v.inAsyncFunction
643
+ if v.analysis.NodeData[n] == nil {
644
+ v.analysis.NodeData[n] = &NodeInfo{}
645
+ }
646
+ v.analysis.NodeData[n].InAsyncContext = v.inAsyncFunction
517
647
 
518
648
  return v
519
649
 
@@ -610,14 +740,46 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
610
740
  }
611
741
  return v // Continue traversal
612
742
 
613
- case *ast.CompositeLit:
614
- // No need to track private field access in composite literals since all fields are public
615
- return v
743
+ case *ast.ReturnStmt:
744
+ // Initialize NodeData for return statement
745
+ if v.analysis.NodeData[n] == nil {
746
+ v.analysis.NodeData[n] = &NodeInfo{}
747
+ }
616
748
 
617
- default:
618
- // For all other nodes, continue traversal
619
- return v
749
+ // Record the enclosing function/literal for this return statement
750
+ if v.currentFuncDecl != nil {
751
+ v.analysis.NodeData[n].EnclosingFuncDecl = v.currentFuncDecl
752
+ } else if v.currentFuncLit != nil {
753
+ v.analysis.NodeData[n].EnclosingFuncLit = v.currentFuncLit
754
+ }
755
+
756
+ // Check if it's a bare return
757
+ if len(n.Results) == 0 {
758
+ if v.currentFuncDecl != nil {
759
+ // Check if the enclosing function declaration has named returns
760
+ if obj := v.pkg.TypesInfo.ObjectOf(v.currentFuncDecl.Name); obj != nil {
761
+ if _, ok := v.analysis.FunctionData[obj]; ok {
762
+ if v.analysis.NodeData[n] == nil {
763
+ v.analysis.NodeData[n] = &NodeInfo{}
764
+ }
765
+ v.analysis.NodeData[n].IsBareReturn = true
766
+ }
767
+ }
768
+ } else if v.currentFuncLit != nil {
769
+ // Check if the enclosing function literal has named returns
770
+ if _, ok := v.analysis.FuncLitData[v.currentFuncLit]; ok {
771
+ if v.analysis.NodeData[n] == nil {
772
+ v.analysis.NodeData[n] = &NodeInfo{}
773
+ }
774
+ v.analysis.NodeData[n].IsBareReturn = true
775
+ }
776
+ }
777
+ }
778
+ return v // Continue traversal
620
779
  }
780
+
781
+ // For all other nodes, continue traversal
782
+ return v
621
783
  }
622
784
 
623
785
  // containsAsyncOperations checks if a node contains any async operations like channel operations.
@@ -666,6 +828,9 @@ func (v *analysisVisitor) containsDefer(block *ast.BlockStmt) bool {
666
828
  hasDefer := false
667
829
 
668
830
  ast.Inspect(block, func(n ast.Node) bool {
831
+ if n == nil {
832
+ return true
833
+ }
669
834
  if _, ok := n.(*ast.DeferStmt); ok {
670
835
  hasDefer = true
671
836
  return false
@@ -724,3 +889,16 @@ func AnalyzeFile(file *ast.File, pkg *packages.Package, analysis *Analysis, cmap
724
889
  // Walk the AST with our visitor
725
890
  ast.Walk(visitor, file)
726
891
  }
892
+
893
+ // getNamedReturns retrieves the named returns for a function
894
+ func (v *analysisVisitor) getNamedReturns(funcDecl *ast.FuncDecl) []string {
895
+ var namedReturns []string
896
+ if funcDecl.Type != nil && funcDecl.Type.Results != nil {
897
+ for _, field := range funcDecl.Type.Results.List {
898
+ for _, name := range field.Names {
899
+ namedReturns = append(namedReturns, name.Name)
900
+ }
901
+ }
902
+ }
903
+ return namedReturns
904
+ }
@@ -100,7 +100,7 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
100
100
  if lhsObj != nil {
101
101
  c.tsw.WriteLiterally(": ")
102
102
  c.tsw.WriteLiterally("$.Box<")
103
- c.WriteGoType(lhsObj.Type())
103
+ c.WriteGoType(lhsObj.Type(), GoTypeContextGeneral)
104
104
  c.tsw.WriteLiterally(">")
105
105
  }
106
106
 
@@ -177,6 +177,11 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
177
177
  return err
178
178
  }
179
179
  c.tsw.WriteLiterally("]")
180
+ } else if callExpr, isCallExpr := r.(*ast.CallExpr); isCallExpr {
181
+ // If the RHS is a function call, write it as a call
182
+ if err := c.WriteCallExpr(callExpr); err != nil {
183
+ return err
184
+ }
180
185
  } else {
181
186
  // Normal case - write the entire expression
182
187
  if err := c.WriteValueExpr(r); err != nil {
@@ -8,8 +8,10 @@ import (
8
8
  "go/types"
9
9
  "os"
10
10
  "path/filepath"
11
+ "slices"
11
12
  "strings"
12
13
 
14
+ gs "github.com/aperturerobotics/goscript"
13
15
  "github.com/sirupsen/logrus"
14
16
  "golang.org/x/tools/go/packages"
15
17
  )
@@ -88,6 +90,12 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
88
90
  return fmt.Errorf("failed to load packages: %w", err)
89
91
  }
90
92
 
93
+ // build a list of packages that patterns matched
94
+ patternPkgPaths := make([]string, 0, len(pkgs))
95
+ for _, pkg := range pkgs {
96
+ patternPkgPaths = append(patternPkgPaths, pkg.PkgPath)
97
+ }
98
+
91
99
  // If AllDependencies is true, we need to collect all dependencies
92
100
  if c.config.AllDependencies {
93
101
  // Create a set to track processed packages by their ID
@@ -140,6 +148,18 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
140
148
 
141
149
  // Compile all packages
142
150
  for _, pkg := range pkgs {
151
+ // Check if the package has a handwritten equivalent
152
+ if !slices.Contains(patternPkgPaths, pkg.PkgPath) {
153
+ _, gsErr := gs.GsOverrides.ReadDir("gs/" + pkg.PkgPath)
154
+ if gsErr != nil && !os.IsNotExist(gsErr) {
155
+ return gsErr
156
+ }
157
+ if gsErr == nil {
158
+ c.le.Infof("Skipping compilation for overridden package %s", pkg.PkgPath)
159
+ continue
160
+ }
161
+ }
162
+
143
163
  // Skip packages that failed to load
144
164
  if len(pkg.Errors) > 0 {
145
165
  c.le.WithError(pkg.Errors[0]).Warnf("Skipping package %s due to errors", pkg.PkgPath)
@@ -328,8 +348,8 @@ func NewFileCompiler(
328
348
  // top-level declarations in the Go file.
329
349
  func (c *FileCompiler) Compile(ctx context.Context) error {
330
350
  f := c.ast
331
-
332
351
  pkgPath := c.pkg.PkgPath
352
+
333
353
  outputFilePath := TranslateGoFilePathToTypescriptFilePath(pkgPath, filepath.Base(c.fullPath))
334
354
  outputFilePathAbs := filepath.Join(c.compilerConfig.OutputPathRoot, outputFilePath)
335
355
 
@@ -349,7 +369,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
349
369
  goWriter := NewGoToTSCompiler(c.codeWriter, c.pkg, c.Analysis)
350
370
 
351
371
  // Add import for the goscript runtime using namespace import and alias
352
- c.codeWriter.WriteLine("import * as $ from \"@goscript/builtin\";")
372
+ c.codeWriter.WriteLinef("import * as $ from %q;", "@goscript/builtin/builtin.js")
353
373
  c.codeWriter.WriteLine("") // Add a newline after the import
354
374
 
355
375
  if err := goWriter.WriteDecls(f.Decls); err != nil {
@@ -368,7 +388,7 @@ type GoToTSCompiler struct {
368
388
 
369
389
  pkg *packages.Package
370
390
 
371
- analysis *Analysis // Holds analysis information for code generation decisions
391
+ analysis *Analysis
372
392
  }
373
393
 
374
394
  // It initializes the compiler with a `TSCodeWriter` for output,
@@ -437,28 +457,31 @@ func (c *GoToTSCompiler) WriteCaseClause(exp *ast.CaseClause) error {
437
457
  c.tsw.WriteLine("")
438
458
  } else {
439
459
  // Case with expressions
440
- c.tsw.WriteLiterally("case ")
441
- for i, expr := range exp.List {
442
- if i > 0 {
443
- c.tsw.WriteLiterally(", ") // Although Go doesn't support multiple expressions per case like this,
444
- } // TypeScript does, so we'll write it this way for now.
445
- if err := c.WriteValueExpr(expr); err != nil {
460
+ // For Go's `case expr1, expr2:`, we translate to:
461
+ // case expr1:
462
+ // case expr2:
463
+ // ... body ...
464
+ // break
465
+ for _, caseExpr := range exp.List {
466
+ c.tsw.WriteLiterally("case ")
467
+ if err := c.WriteValueExpr(caseExpr); err != nil {
446
468
  return fmt.Errorf("failed to write case clause expression: %w", err)
447
469
  }
470
+ c.tsw.WriteLiterally(":")
471
+ c.tsw.WriteLine("")
448
472
  }
449
- c.tsw.WriteLiterally(":")
450
- c.tsw.WriteLine("")
451
473
  }
452
474
 
475
+ // The body is written once, after all case labels for this clause.
476
+ // Indentation for the body starts here.
453
477
  c.tsw.Indent(1)
454
- // Write the body of the case clause
455
478
  for _, stmt := range exp.Body {
456
479
  if err := c.WriteStmt(stmt); err != nil {
457
480
  return fmt.Errorf("failed to write statement in case clause body: %w", err)
458
481
  }
459
482
  }
460
- // Add break statement (Go's switch has implicit breaks)
461
- c.tsw.WriteLine("break") // Remove semicolon
483
+ // Add break statement (Go's switch has implicit breaks, TS needs explicit break)
484
+ c.tsw.WriteLine("break")
462
485
  c.tsw.Indent(-1)
463
486
  return nil
464
487
  }
@@ -527,11 +550,11 @@ func (c *GoToTSCompiler) writeChannelReceiveWithOk(lhs []ast.Expr, unaryExpr *as
527
550
  } else {
528
551
  // If both are blank, just await the call and return
529
552
  if okIsBlank {
530
- c.tsw.WriteLiterally("await ")
553
+ c.tsw.WriteLiterally("await $.chanRecvWithOk(")
531
554
  if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
532
555
  return fmt.Errorf("failed to write channel expression in receive: %w", err)
533
556
  }
534
- c.tsw.WriteLiterally(".receiveWithOk()")
557
+ c.tsw.WriteLiterally(")")
535
558
  c.tsw.WriteLine("")
536
559
  return nil // Nothing to assign
537
560
  }
@@ -549,11 +572,11 @@ func (c *GoToTSCompiler) writeChannelReceiveWithOk(lhs []ast.Expr, unaryExpr *as
549
572
  // Write the destructuring assignment/declaration
550
573
  c.tsw.WriteLiterally(keyword) // "const " or ""
551
574
  c.tsw.WriteLiterally(destructuringPattern)
552
- c.tsw.WriteLiterally(" = await ")
575
+ c.tsw.WriteLiterally(" = await $.chanRecvWithOk(")
553
576
  if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
554
577
  return fmt.Errorf("failed to write channel expression in receive: %w", err)
555
578
  }
556
- c.tsw.WriteLiterally(".receiveWithOk()")
579
+ c.tsw.WriteLiterally(")")
557
580
  c.tsw.WriteLine("")
558
581
 
559
582
  return nil
@@ -6,6 +6,7 @@ import (
6
6
  "path/filepath"
7
7
  "runtime"
8
8
  "slices"
9
+ "strings"
9
10
  "sync"
10
11
  "sync/atomic"
11
12
  "testing"
@@ -38,7 +39,7 @@ func TestCompliance(t *testing.T) {
38
39
  testPath := filepath.Join(testsDir, dir.Name())
39
40
  goFiles, err := filepath.Glob(filepath.Join(testPath, "*.go"))
40
41
  if err != nil || len(goFiles) == 0 {
41
- t.Errorf("no .go files found in %s", testPath)
42
+ // t.Errorf("no .go files found in %s", testPath)
42
43
  continue
43
44
  }
44
45
  testPaths = append(testPaths, testPath)
@@ -97,15 +98,42 @@ func TestCompliance(t *testing.T) {
97
98
  t.SkipNow()
98
99
  }
99
100
 
100
- t.Log("running: npm run typecheck")
101
- cmd := exec.Command("npm", "run", "typecheck")
102
- cmd.Dir = workspaceDir // Run in the workspace directory
103
- cmd.Stdout = os.Stderr
104
- cmd.Stderr = os.Stderr
101
+ // Get parent module path for the global typecheck
102
+ parentModPath, err := getParentGoModulePath()
103
+ if err != nil {
104
+ t.Fatalf("Failed to determine parent Go module path: %v", err)
105
+ }
106
+
107
+ // Create global typecheck tsconfig
108
+ tsconfigPath := compliance.WriteGlobalTypeCheckConfig(t, parentModPath, workspaceDir)
109
+
110
+ // Run TypeScript type checking
111
+ typecheckDir := filepath.Dir(tsconfigPath)
112
+ cmd := exec.Command("tsc", "--project", filepath.Base(tsconfigPath))
113
+ cmd.Dir = typecheckDir
114
+
115
+ // Set up PATH to include node_modules/.bin
116
+ nodeBinDir := filepath.Join(workspaceDir, "node_modules", ".bin")
117
+ currentPath := os.Getenv("PATH")
118
+ newPath := nodeBinDir + string(os.PathListSeparator) + currentPath
119
+ cmd.Env = append(os.Environ(), "PATH="+newPath)
105
120
 
106
- err = cmd.Run()
121
+ output, err := cmd.CombinedOutput()
107
122
  if err != nil {
108
- t.Errorf("npm run typecheck failed: %v", err)
123
+ t.Errorf("Global TypeScript type checking failed: %v\noutput:\n%s", err, string(output))
124
+ } else {
125
+ t.Logf("Global TypeScript type checking passed")
109
126
  }
110
127
  })
111
128
  }
129
+
130
+ // getParentGoModulePath is a helper function to get the parent Go module path
131
+ // This is similar to the one in compliance.go but simplified for use in tests
132
+ func getParentGoModulePath() (string, error) {
133
+ cmd := exec.Command("go", "list", "-m")
134
+ output, err := cmd.Output()
135
+ if err != nil {
136
+ return "", err
137
+ }
138
+ return strings.TrimSpace(string(output)), nil
139
+ }