goscript 0.2.0 → 0.2.2

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 (75) hide show
  1. package/cmd/goscript-wasm/main.go +38 -6
  2. package/compiler/diagnostic.go +104 -12
  3. package/compiler/diagnostic_test.go +106 -0
  4. package/compiler/gotest/runner.go +99 -17
  5. package/compiler/gotest/runner_test.go +65 -0
  6. package/compiler/index.test.ts +23 -0
  7. package/compiler/lowered-program.go +9 -7
  8. package/compiler/lowering.go +361 -72
  9. package/compiler/lowering_bench_test.go +1 -0
  10. package/compiler/lowering_internal_test.go +18 -0
  11. package/compiler/protobuf-ts-binding.go +65 -12
  12. package/compiler/protobuf-ts-binding_test.go +339 -0
  13. package/compiler/runtime-contract.go +4 -0
  14. package/compiler/runtime-contract_test.go +2 -0
  15. package/compiler/service.go +1 -0
  16. package/compiler/skeleton_test.go +60 -3
  17. package/compiler/wasm/compile_test.go +37 -4
  18. package/compiler/wasm-api.go +57 -7
  19. package/dist/gs/builtin/hostio.js +6 -1
  20. package/dist/gs/builtin/hostio.js.map +1 -1
  21. package/dist/gs/builtin/slice.d.ts +11 -1
  22. package/dist/gs/builtin/slice.js +158 -2
  23. package/dist/gs/builtin/slice.js.map +1 -1
  24. package/dist/gs/crypto/aes/index.d.ts +15 -0
  25. package/dist/gs/crypto/aes/index.js +57 -0
  26. package/dist/gs/crypto/aes/index.js.map +1 -0
  27. package/dist/gs/crypto/cipher/index.d.ts +41 -0
  28. package/dist/gs/crypto/cipher/index.js +255 -0
  29. package/dist/gs/crypto/cipher/index.js.map +1 -0
  30. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  31. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +30 -5
  32. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  33. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.d.ts +1 -0
  34. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +17 -11
  35. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -1
  36. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.d.ts +31 -0
  37. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js +117 -0
  38. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js.map +1 -0
  39. package/dist/gs/internal/byteorder/index.js +2 -2
  40. package/dist/gs/internal/byteorder/index.js.map +1 -1
  41. package/dist/gs/io/io.js +18 -2
  42. package/dist/gs/io/io.js.map +1 -1
  43. package/dist/gs/reflect/type.js +57 -0
  44. package/dist/gs/reflect/type.js.map +1 -1
  45. package/dist/gs/runtime/debug/index.js +2 -1
  46. package/dist/gs/runtime/debug/index.js.map +1 -1
  47. package/dist/gs/sync/atomic/doc_64.gs.js +7 -6
  48. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  49. package/go.mod +2 -2
  50. package/go.sum +2 -0
  51. package/gs/builtin/hostio.test.ts +22 -1
  52. package/gs/builtin/hostio.ts +6 -1
  53. package/gs/builtin/runtime-contract.test.ts +28 -0
  54. package/gs/builtin/slice.ts +225 -20
  55. package/gs/crypto/aes/index.test.ts +120 -0
  56. package/gs/crypto/aes/index.ts +76 -0
  57. package/gs/crypto/cipher/index.ts +345 -0
  58. package/gs/crypto/cipher/meta.json +6 -0
  59. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +162 -0
  60. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +41 -5
  61. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +18 -0
  62. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +17 -11
  63. package/gs/golang.org/x/crypto/chacha20poly1305/index.test.ts +91 -0
  64. package/gs/golang.org/x/crypto/chacha20poly1305/index.ts +245 -0
  65. package/gs/internal/byteorder/index.test.ts +2 -2
  66. package/gs/internal/byteorder/index.ts +2 -2
  67. package/gs/io/io.test.ts +56 -1
  68. package/gs/io/io.ts +19 -2
  69. package/gs/reflect/type.ts +64 -0
  70. package/gs/reflect/typefor.test.ts +21 -1
  71. package/gs/runtime/debug/index.test.ts +32 -4
  72. package/gs/runtime/debug/index.ts +5 -2
  73. package/gs/sync/atomic/doc_64.gs.ts +6 -7
  74. package/gs/sync/atomic/doc_64.test.ts +43 -0
  75. package/package.json +10 -3
@@ -3,8 +3,10 @@
3
3
  package main
4
4
 
5
5
  import (
6
+ "errors"
6
7
  "syscall/js"
7
8
 
9
+ "github.com/aperturerobotics/goscript/compiler"
8
10
  "github.com/aperturerobotics/goscript/compiler/wasm"
9
11
  )
10
12
 
@@ -20,8 +22,9 @@ func main() {
20
22
  func compileWrapper(this js.Value, args []js.Value) interface{} {
21
23
  if len(args) < 1 {
22
24
  return map[string]interface{}{
23
- "error": "missing source code argument",
24
- "output": "",
25
+ "error": "missing source code argument",
26
+ "output": "",
27
+ "diagnostics": []interface{}{},
25
28
  }
26
29
  }
27
30
 
@@ -33,14 +36,43 @@ func compileWrapper(this js.Value, args []js.Value) interface{} {
33
36
 
34
37
  output, err := wasm.CompileSource(source, packageName)
35
38
  if err != nil {
39
+ diagnostics := []interface{}{}
40
+ var compileErr *compiler.CompileError
41
+ if errors.As(err, &compileErr) {
42
+ diagnostics = encodeDiagnostics(compileErr.Diagnostics)
43
+ }
36
44
  return map[string]interface{}{
37
- "error": err.Error(),
38
- "output": "",
45
+ "error": err.Error(),
46
+ "output": "",
47
+ "diagnostics": diagnostics,
39
48
  }
40
49
  }
41
50
 
42
51
  return map[string]interface{}{
43
- "error": "",
44
- "output": output,
52
+ "error": "",
53
+ "output": output,
54
+ "diagnostics": []interface{}{},
55
+ }
56
+ }
57
+
58
+ func encodeDiagnostics(diagnostics []compiler.Diagnostic) []interface{} {
59
+ encoded := make([]interface{}, 0, len(diagnostics))
60
+ for _, diagnostic := range diagnostics {
61
+ item := map[string]interface{}{
62
+ "severity": string(diagnostic.Severity),
63
+ "code": diagnostic.Code,
64
+ "message": diagnostic.Message,
65
+ "detail": diagnostic.Detail,
66
+ }
67
+ if diagnostic.Position != nil {
68
+ item["position"] = map[string]interface{}{
69
+ "file": diagnostic.Position.File,
70
+ "displayFile": diagnostic.Position.DisplayFile,
71
+ "line": diagnostic.Position.Line,
72
+ "column": diagnostic.Position.Column,
73
+ }
74
+ }
75
+ encoded = append(encoded, item)
45
76
  }
77
+ return encoded
46
78
  }
@@ -1,6 +1,10 @@
1
1
  package compiler
2
2
 
3
- import "strings"
3
+ import (
4
+ "path/filepath"
5
+ "strconv"
6
+ "strings"
7
+ )
4
8
 
5
9
  // DiagnosticSeverity is the severity of a compiler diagnostic.
6
10
  type DiagnosticSeverity string
@@ -22,6 +26,20 @@ type Diagnostic struct {
22
26
  Message string
23
27
  // Detail carries optional longer guidance.
24
28
  Detail string
29
+ // Position is the optional source point that caused the diagnostic.
30
+ Position *DiagnosticPosition
31
+ }
32
+
33
+ // DiagnosticPosition identifies the source point that caused a diagnostic.
34
+ type DiagnosticPosition struct {
35
+ // File is the raw file identity from the compiler source owner.
36
+ File string
37
+ // DisplayFile is the request-relative file identity for human output.
38
+ DisplayFile string
39
+ // Line is the 1-based source line.
40
+ Line int
41
+ // Column is the 1-based source column.
42
+ Column int
25
43
  }
26
44
 
27
45
  // CompileError wraps structured diagnostics for ordinary Go error paths.
@@ -41,25 +59,99 @@ func (e *CompileError) Error() string {
41
59
  return "goscript: compile failed"
42
60
  }
43
61
 
62
+ return FormatDiagnostics(e.Diagnostics)
63
+ }
64
+
65
+ // FormatDiagnostics returns the canonical human-readable diagnostic summary.
66
+ func FormatDiagnostics(diagnostics []Diagnostic) string {
44
67
  var b strings.Builder
45
- for i, diag := range e.Diagnostics {
68
+ for i, diag := range diagnostics {
46
69
  if i != 0 {
47
70
  b.WriteString("; ")
48
71
  }
49
- if diag.Code != "" {
50
- b.WriteString(diag.Code)
51
- b.WriteString(": ")
52
- }
53
- b.WriteString(diag.Message)
54
- if diag.Detail != "" {
55
- b.WriteString(" (")
56
- b.WriteString(diag.Detail)
57
- b.WriteString(")")
58
- }
72
+ b.WriteString(FormatDiagnostic(diag))
59
73
  }
60
74
  return b.String()
61
75
  }
62
76
 
77
+ // FormatDiagnostic returns the canonical human-readable form of one diagnostic.
78
+ func FormatDiagnostic(diag Diagnostic) string {
79
+ var b strings.Builder
80
+ if pos := formatDiagnosticPosition(diag.Position); pos != "" {
81
+ b.WriteString(pos)
82
+ b.WriteString(": ")
83
+ }
84
+ if diag.Code != "" {
85
+ b.WriteString(diag.Code)
86
+ b.WriteString(": ")
87
+ }
88
+ b.WriteString(diag.Message)
89
+ if diag.Detail != "" {
90
+ b.WriteString(" (")
91
+ b.WriteString(diag.Detail)
92
+ b.WriteString(")")
93
+ }
94
+ return b.String()
95
+ }
96
+
97
+ func formatDiagnosticPosition(pos *DiagnosticPosition) string {
98
+ if pos == nil || pos.Line <= 0 {
99
+ return ""
100
+ }
101
+ file := strings.TrimSpace(pos.DisplayFile)
102
+ if file == "" {
103
+ file = strings.TrimSpace(pos.File)
104
+ }
105
+ if file == "" {
106
+ return ""
107
+ }
108
+ var b strings.Builder
109
+ b.WriteString(filepath.ToSlash(file))
110
+ b.WriteString(":")
111
+ b.WriteString(strconv.Itoa(pos.Line))
112
+ if pos.Column > 0 {
113
+ b.WriteString(":")
114
+ b.WriteString(strconv.Itoa(pos.Column))
115
+ }
116
+ return b.String()
117
+ }
118
+
119
+ func diagnosticPositionFromSource(pos sourcePosition, displayRoot string) *DiagnosticPosition {
120
+ if pos.line <= 0 {
121
+ return nil
122
+ }
123
+ file := strings.TrimSpace(pos.file)
124
+ return &DiagnosticPosition{
125
+ File: file,
126
+ DisplayFile: diagnosticDisplayFile(file, displayRoot),
127
+ Line: pos.line,
128
+ Column: pos.column,
129
+ }
130
+ }
131
+
132
+ func diagnosticDisplayFile(file string, displayRoot string) string {
133
+ file = strings.TrimSpace(file)
134
+ if file == "" {
135
+ return ""
136
+ }
137
+ displayRoot = strings.TrimSpace(displayRoot)
138
+ if displayRoot == "" {
139
+ return filepath.ToSlash(file)
140
+ }
141
+ root := displayRoot
142
+ if absRoot, err := filepath.Abs(root); err == nil {
143
+ root = absRoot
144
+ }
145
+ candidate := file
146
+ if !filepath.IsAbs(candidate) {
147
+ candidate = filepath.Join(root, candidate)
148
+ }
149
+ if rel, err := filepath.Rel(root, candidate); err == nil && rel != "." && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
150
+ return filepath.ToSlash(rel)
151
+ }
152
+ return filepath.ToSlash(file)
153
+ }
154
+
63
155
  func diagnosticsHaveErrors(diagnostics []Diagnostic) bool {
64
156
  for _, diag := range diagnostics {
65
157
  if diag.Severity == DiagnosticSeverityError {
@@ -0,0 +1,106 @@
1
+ package compiler
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ func TestFormatDiagnosticIncludesDisplayPosition(t *testing.T) {
12
+ diag := Diagnostic{
13
+ Severity: DiagnosticSeverityError,
14
+ Code: "goscript/test",
15
+ Message: "failed",
16
+ Detail: "bad input",
17
+ Position: &DiagnosticPosition{
18
+ File: filepath.Join("internal", "raw.go"),
19
+ DisplayFile: filepath.Join("pkg", "main.go"),
20
+ Line: 12,
21
+ Column: 3,
22
+ },
23
+ }
24
+
25
+ got := FormatDiagnostic(diag)
26
+ want := "pkg/main.go:12:3: goscript/test: failed (bad input)"
27
+ if got != want {
28
+ t.Fatalf("FormatDiagnostic() = %q, want %q", got, want)
29
+ }
30
+ }
31
+
32
+ func TestFormatDiagnosticWithoutPositionPreservesLegacyShape(t *testing.T) {
33
+ diag := Diagnostic{
34
+ Severity: DiagnosticSeverityError,
35
+ Code: "goscript/test",
36
+ Message: "failed",
37
+ Detail: "bad input",
38
+ }
39
+
40
+ got := FormatDiagnostic(diag)
41
+ want := "goscript/test: failed (bad input)"
42
+ if got != want {
43
+ t.Fatalf("FormatDiagnostic() = %q, want %q", got, want)
44
+ }
45
+ }
46
+
47
+ func TestDiagnosticPositionFromSourceUsesDisplayRoot(t *testing.T) {
48
+ root := t.TempDir()
49
+ file := filepath.Join(root, "pkg", "main.go")
50
+ pos := diagnosticPositionFromSource(sourcePosition{
51
+ file: file,
52
+ line: 7,
53
+ column: 9,
54
+ }, root)
55
+
56
+ if pos == nil {
57
+ t.Fatal("expected diagnostic position")
58
+ }
59
+ if pos.File != file {
60
+ t.Fatalf("File = %q, want %q", pos.File, file)
61
+ }
62
+ if pos.DisplayFile != "pkg/main.go" {
63
+ t.Fatalf("DisplayFile = %q, want %q", pos.DisplayFile, "pkg/main.go")
64
+ }
65
+ }
66
+
67
+ func TestLoweringUnsupportedDiagnosticIncludesSourcePosition(t *testing.T) {
68
+ moduleDir := writePackageGraphFixture(t, map[string]string{
69
+ "go.mod": "module example.test/loweringdiag\n\ngo 1.25.3\n",
70
+ "main.go": "package loweringdiag\n\nfunc Make[T ~[]int]() T {\n\treturn make(T, 1)\n}\n",
71
+ })
72
+ service := NewCompileService()
73
+ _, err := service.Compile(context.Background(), &CompileRequest{
74
+ Patterns: []string{"."},
75
+ Dir: moduleDir,
76
+ OutputPath: filepath.Join(t.TempDir(), "output"),
77
+ DependencyMode: DependencyModeRequested,
78
+ RuntimeEmissionMode: RuntimeEmissionModeReference,
79
+ })
80
+ if err == nil {
81
+ t.Fatal("expected lowering diagnostic")
82
+ }
83
+ var compileErr *CompileError
84
+ if !errors.As(err, &compileErr) {
85
+ t.Fatalf("expected CompileError, got %T: %v", err, err)
86
+ }
87
+ if len(compileErr.Diagnostics) != 1 {
88
+ t.Fatalf("Diagnostics = %#v, want exactly one", compileErr.Diagnostics)
89
+ }
90
+ diag := compileErr.Diagnostics[0]
91
+ if diag.Code != "goscript/lowering:unsupported" {
92
+ t.Fatalf("Code = %q, want goscript/lowering:unsupported", diag.Code)
93
+ }
94
+ if diag.Position == nil {
95
+ t.Fatalf("missing position in %#v", diag)
96
+ }
97
+ if diag.Position.DisplayFile != "main.go" {
98
+ t.Fatalf("DisplayFile = %q, want main.go", diag.Position.DisplayFile)
99
+ }
100
+ if diag.Position.Line != 4 {
101
+ t.Fatalf("Line = %d, want 4", diag.Position.Line)
102
+ }
103
+ if got := FormatDiagnostics(compileErr.Diagnostics); !strings.HasPrefix(got, "main.go:4:") {
104
+ t.Fatalf("formatted diagnostic = %q, want main.go:4 prefix", got)
105
+ }
106
+ }
@@ -141,6 +141,10 @@ func (r *Runner) runPackageTools(
141
141
  if len(indexes) == 0 {
142
142
  return
143
143
  }
144
+ if phase := materializeRuntimeModuleShims(req, outputRoots); phase.Failed() {
145
+ markRuntimeFailures(result, indexes, OwnerTestRunner, phase.Error)
146
+ return
147
+ }
144
148
  if len(indexes) == 1 {
145
149
  r.runPackageTypeCheckAndRuntime(ctx, req, workspace, result, outputRoots, indexes[0])
146
150
  return
@@ -286,6 +290,100 @@ func (r *Runner) runPackageRuntimes(
286
290
  r.runPackageRuntimesIndividually(ctx, req, workspace, result, outputRoots, indexes)
287
291
  }
288
292
 
293
+ func materializeRuntimeModuleShims(req *normalizedRequest, outputRoots []string) tsworkspace.Result {
294
+ if req.RuntimeBackend != RuntimeBackendBun {
295
+ return tsworkspace.Result{Phase: tsworkspace.PhaseWorkspace}
296
+ }
297
+ seen := make(map[string]bool)
298
+ shimRoots := runtimeModuleShimRoots(req, outputRoots)
299
+ for _, outputRoot := range outputRoots {
300
+ if outputRoot == "" {
301
+ continue
302
+ }
303
+ root := filepath.Join(outputRoot, "@goscript")
304
+ if _, err := os.Stat(root); err != nil {
305
+ if os.IsNotExist(err) {
306
+ continue
307
+ }
308
+ return tsworkspace.Result{Phase: tsworkspace.PhaseWorkspace, Error: errors.Wrap(err, "stat GoScript runtime output root").Error()}
309
+ }
310
+ err := filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
311
+ if err != nil {
312
+ return err
313
+ }
314
+ if entry.IsDir() || filepath.Ext(path) != ".ts" {
315
+ return nil
316
+ }
317
+ rel, err := filepath.Rel(root, path)
318
+ if err != nil {
319
+ return err
320
+ }
321
+ shimRel := filepath.Join("node_modules", "@goscript", strings.TrimSuffix(rel, ".ts")+".js")
322
+ for _, shimRoot := range shimRoots {
323
+ shimPath := filepath.Join(shimRoot, shimRel)
324
+ seenKey := shimPath
325
+ if seen[seenKey] {
326
+ continue
327
+ }
328
+ seen[seenKey] = true
329
+ shimDir := filepath.Dir(shimPath)
330
+ importPath, err := filepath.Rel(shimDir, path)
331
+ if err != nil {
332
+ return err
333
+ }
334
+ importPath = filepath.ToSlash(importPath)
335
+ if !strings.HasPrefix(importPath, ".") {
336
+ importPath = "./" + importPath
337
+ }
338
+ if err := writeRuntimeModuleShim(shimPath, "export * from "+strconv.Quote(importPath)+"\n"); err != nil {
339
+ return err
340
+ }
341
+ }
342
+ return nil
343
+ })
344
+ if err != nil {
345
+ return tsworkspace.Result{Phase: tsworkspace.PhaseWorkspace, Error: errors.Wrap(err, "materialize GoScript runtime module shims").Error()}
346
+ }
347
+ }
348
+ return tsworkspace.Result{Phase: tsworkspace.PhaseWorkspace}
349
+ }
350
+
351
+ func runtimeModuleShimRoots(req *normalizedRequest, outputRoots []string) []string {
352
+ seen := make(map[string]bool)
353
+ var roots []string
354
+ for _, root := range append([]string{req.WorkDir}, outputRoots...) {
355
+ if root == "" {
356
+ continue
357
+ }
358
+ clean := filepath.Clean(root)
359
+ if seen[clean] {
360
+ continue
361
+ }
362
+ seen[clean] = true
363
+ roots = append(roots, clean)
364
+ }
365
+ return roots
366
+ }
367
+
368
+ func writeRuntimeModuleShim(path string, data string) error {
369
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
370
+ return err
371
+ }
372
+ return os.WriteFile(path, []byte(data), 0o644)
373
+ }
374
+
375
+ func markRuntimeFailures(result *Result, indexes []int, owner Owner, message string) {
376
+ for _, idx := range indexes {
377
+ if idx < 0 || idx >= len(result.Packages) {
378
+ continue
379
+ }
380
+ result.Packages[idx].Action = ActionFail
381
+ result.Packages[idx].Owner = owner
382
+ result.Packages[idx].Phases.Runtime = PhaseStatusFail
383
+ result.Packages[idx].Error = message
384
+ }
385
+ }
386
+
289
387
  func (r *Runner) runPackageRuntimesIndividually(
290
388
  ctx context.Context,
291
389
  req *normalizedRequest,
@@ -1531,21 +1629,5 @@ func diagnosticsHaveErrors(diagnostics []compiler.Diagnostic) bool {
1531
1629
  }
1532
1630
 
1533
1631
  func diagnosticsSummary(diagnostics []compiler.Diagnostic) string {
1534
- var b strings.Builder
1535
- for idx, diagnostic := range diagnostics {
1536
- if idx != 0 {
1537
- b.WriteString("; ")
1538
- }
1539
- if diagnostic.Code != "" {
1540
- b.WriteString(diagnostic.Code)
1541
- b.WriteString(": ")
1542
- }
1543
- b.WriteString(diagnostic.Message)
1544
- if diagnostic.Detail != "" {
1545
- b.WriteString(" (")
1546
- b.WriteString(diagnostic.Detail)
1547
- b.WriteString(")")
1548
- }
1549
- }
1550
- return b.String()
1632
+ return compiler.FormatDiagnostics(diagnostics)
1551
1633
  }
@@ -10,8 +10,28 @@ import (
10
10
  "strings"
11
11
  "testing"
12
12
  "time"
13
+
14
+ "github.com/aperturerobotics/goscript/compiler"
13
15
  )
14
16
 
17
+ func TestDiagnosticsSummaryUsesCompilerFormatter(t *testing.T) {
18
+ got := diagnosticsSummary([]compiler.Diagnostic{{
19
+ Severity: compiler.DiagnosticSeverityError,
20
+ Code: "goscript/test",
21
+ Message: "failed",
22
+ Detail: "bad input",
23
+ Position: &compiler.DiagnosticPosition{
24
+ DisplayFile: "pkg/main.go",
25
+ Line: 4,
26
+ Column: 2,
27
+ },
28
+ }})
29
+ want := "pkg/main.go:4:2: goscript/test: failed (bad input)"
30
+ if got != want {
31
+ t.Fatalf("diagnosticsSummary() = %q, want %q", got, want)
32
+ }
33
+ }
34
+
15
35
  func TestRunnerRunsOrdinaryPackageTest(t *testing.T) {
16
36
  moduleDir := writeFixture(t, map[string]string{
17
37
  "go.mod": "module example.test/gotest\n\ngo 1.25.3\n",
@@ -1609,6 +1629,51 @@ func TestRenderRuntimeTypeScriptProjectDisablesEmit(t *testing.T) {
1609
1629
  }
1610
1630
  }
1611
1631
 
1632
+ func TestMaterializeRuntimeModuleShimsReexportsGeneratedTypeScript(t *testing.T) {
1633
+ workDir := t.TempDir()
1634
+ outputRoot := filepath.Join(workDir, "output")
1635
+ for _, name := range []string{
1636
+ "errors/index.ts",
1637
+ "github.com/s4wave/spacewave/core/plugin/space/config.gs.ts",
1638
+ } {
1639
+ path := filepath.Join(outputRoot, "@goscript", filepath.FromSlash(name))
1640
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
1641
+ t.Fatalf("create output parent: %v", err)
1642
+ }
1643
+ if err := os.WriteFile(path, []byte("export const value = 1\n"), 0o644); err != nil {
1644
+ t.Fatalf("write output file: %v", err)
1645
+ }
1646
+ }
1647
+
1648
+ req := &normalizedRequest{
1649
+ WorkDir: workDir,
1650
+ RuntimeBackend: RuntimeBackendBun,
1651
+ }
1652
+ phase := materializeRuntimeModuleShims(req, []string{outputRoot})
1653
+ if phase.Failed() {
1654
+ t.Fatalf("materialize shims: %s", phase.Error)
1655
+ }
1656
+ for _, root := range []string{workDir, outputRoot} {
1657
+ for _, name := range []string{
1658
+ "errors/index.js",
1659
+ "github.com/s4wave/spacewave/core/plugin/space/config.gs.js",
1660
+ } {
1661
+ path := filepath.Join(root, "node_modules", "@goscript", filepath.FromSlash(name))
1662
+ data, err := os.ReadFile(path)
1663
+ if err != nil {
1664
+ t.Fatalf("read shim %s: %v", name, err)
1665
+ }
1666
+ text := string(data)
1667
+ if !strings.Contains(text, ".ts\"") {
1668
+ t.Fatalf("shim should re-export generated TypeScript: %s", text)
1669
+ }
1670
+ if strings.Contains(text, workDir) {
1671
+ t.Fatalf("shim should use a portable relative import, got: %s", text)
1672
+ }
1673
+ }
1674
+ }
1675
+ }
1676
+
1612
1677
  func TestRenderTypeScriptProjectsCanUseIncrementalBuildInfo(t *testing.T) {
1613
1678
  req := &normalizedRequest{
1614
1679
  WorkDir: "/work",
@@ -28,4 +28,27 @@ describe('GoScript Compiler API', () => {
28
28
  expect(generated).toContain('export async function main(): globalThis.Promise<void>')
29
29
  expect(generated).toContain('$.println("api")')
30
30
  }, 30000)
31
+
32
+ it('inherits positioned compiler diagnostics on stderr', async () => {
33
+ const dir = await mkdtemp(join(tmpdir(), 'goscript-api-diagnostic-'))
34
+ const output = join(dir, 'output')
35
+ await mkdir(dir, { recursive: true })
36
+ await writeFile(join(dir, 'go.mod'), 'module example.test/apierr\n\ngo 1.25.3\n')
37
+ await writeFile(join(dir, 'main.go'), [
38
+ 'package apierr',
39
+ '',
40
+ 'func Make[T ~[]int]() T {',
41
+ ' return make(T, 1)',
42
+ '}',
43
+ '',
44
+ ].join('\n'))
45
+
46
+ await expect(compile({
47
+ pkg: '.',
48
+ output,
49
+ dir,
50
+ })).rejects.toMatchObject({
51
+ stderr: expect.stringContaining('main.go:4:'),
52
+ })
53
+ }, 30000)
31
54
  })
@@ -59,13 +59,14 @@ type loweredDecl struct {
59
59
  }
60
60
 
61
61
  type loweredStruct struct {
62
- exported bool
63
- indexExported bool
64
- name string
65
- typeName string
66
- cloneMethod string
67
- fields []loweredStructField
68
- methods []loweredFunction
62
+ exported bool
63
+ indexExported bool
64
+ protobufPreserveJSON bool
65
+ name string
66
+ typeName string
67
+ cloneMethod string
68
+ fields []loweredStructField
69
+ methods []loweredFunction
69
70
  }
70
71
 
71
72
  type loweredStructField struct {
@@ -90,6 +91,7 @@ type loweredFunction struct {
90
91
  indexExported bool
91
92
  init bool
92
93
  async bool
94
+ sourcePath string
93
95
  name string
94
96
  typeParams []string
95
97
  runtimeName string