goscript 0.2.0 → 0.2.1

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 (50) 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 +1 -17
  5. package/compiler/gotest/runner_test.go +20 -0
  6. package/compiler/index.test.ts +23 -0
  7. package/compiler/lowered-program.go +9 -7
  8. package/compiler/lowering.go +359 -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 +230 -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 +56 -2
  17. package/compiler/wasm/compile_test.go +37 -4
  18. package/compiler/wasm-api.go +57 -7
  19. package/dist/gs/builtin/hostio.js +5 -0
  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/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  25. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +30 -5
  26. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  27. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.d.ts +1 -0
  28. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +17 -11
  29. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -1
  30. package/dist/gs/internal/byteorder/index.js +2 -2
  31. package/dist/gs/internal/byteorder/index.js.map +1 -1
  32. package/dist/gs/reflect/type.js +57 -0
  33. package/dist/gs/reflect/type.js.map +1 -1
  34. package/dist/gs/sync/atomic/doc_64.gs.js +7 -6
  35. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  36. package/gs/builtin/hostio.test.ts +16 -0
  37. package/gs/builtin/hostio.ts +7 -0
  38. package/gs/builtin/runtime-contract.test.ts +28 -0
  39. package/gs/builtin/slice.ts +225 -20
  40. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +162 -0
  41. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +41 -5
  42. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +18 -0
  43. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +17 -11
  44. package/gs/internal/byteorder/index.test.ts +2 -2
  45. package/gs/internal/byteorder/index.ts +2 -2
  46. package/gs/reflect/type.ts +64 -0
  47. package/gs/reflect/typefor.test.ts +21 -1
  48. package/gs/sync/atomic/doc_64.gs.ts +6 -7
  49. package/gs/sync/atomic/doc_64.test.ts +43 -0
  50. package/package.json +1 -1
@@ -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
+ }
@@ -1531,21 +1531,5 @@ func diagnosticsHaveErrors(diagnostics []compiler.Diagnostic) bool {
1531
1531
  }
1532
1532
 
1533
1533
  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()
1534
+ return compiler.FormatDiagnostics(diagnostics)
1551
1535
  }
@@ -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",
@@ -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