goscript 0.0.84 → 0.1.0

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 (188) hide show
  1. package/README.md +13 -1
  2. package/cmd/goscript/cmd_compile.go +70 -69
  3. package/cmd/goscript/cmd_compile_test.go +79 -0
  4. package/cmd/goscript/main.go +10 -5
  5. package/compiler/compile-request.go +218 -0
  6. package/compiler/compiler.go +16 -1336
  7. package/compiler/compliance_test.go +196 -0
  8. package/compiler/config.go +6 -13
  9. package/compiler/diagnostic.go +70 -0
  10. package/compiler/index.test.ts +28 -28
  11. package/compiler/index.ts +40 -72
  12. package/compiler/lowered-program.go +132 -0
  13. package/compiler/lowering.go +3576 -0
  14. package/compiler/override-registry.go +422 -0
  15. package/compiler/override-registry_test.go +207 -0
  16. package/compiler/package-graph.go +231 -0
  17. package/compiler/package-graph_test.go +281 -0
  18. package/compiler/result.go +13 -0
  19. package/compiler/runtime-contract.go +279 -0
  20. package/compiler/runtime-contract_test.go +90 -0
  21. package/compiler/semantic-model-types.go +110 -0
  22. package/compiler/semantic-model.go +922 -0
  23. package/compiler/semantic-model_test.go +416 -0
  24. package/compiler/service.go +133 -0
  25. package/compiler/skeleton_test.go +1145 -0
  26. package/compiler/typescript-emitter.go +663 -0
  27. package/compiler/wasm/compile.go +2 -3
  28. package/compiler/wasm/compile_test.go +29 -0
  29. package/compiler/wasm_api.go +10 -159
  30. package/dist/compiler/index.d.ts +1 -3
  31. package/dist/compiler/index.js +31 -55
  32. package/dist/compiler/index.js.map +1 -1
  33. package/dist/gs/builtin/builtin.d.ts +13 -0
  34. package/dist/gs/builtin/builtin.js +23 -0
  35. package/dist/gs/builtin/builtin.js.map +1 -1
  36. package/dist/gs/builtin/channel.d.ts +3 -3
  37. package/dist/gs/builtin/channel.js.map +1 -1
  38. package/dist/gs/builtin/hostio.d.ts +15 -1
  39. package/dist/gs/builtin/hostio.js +134 -49
  40. package/dist/gs/builtin/hostio.js.map +1 -1
  41. package/dist/gs/builtin/index.d.ts +1 -0
  42. package/dist/gs/builtin/index.js +1 -0
  43. package/dist/gs/builtin/index.js.map +1 -1
  44. package/dist/gs/builtin/slice.d.ts +1 -1
  45. package/dist/gs/builtin/slice.js.map +1 -1
  46. package/dist/gs/builtin/type.d.ts +11 -0
  47. package/dist/gs/builtin/type.js +55 -1
  48. package/dist/gs/builtin/type.js.map +1 -1
  49. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  50. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  51. package/dist/gs/bytes/reader.gs.js.map +1 -1
  52. package/dist/gs/context/context.js.map +1 -1
  53. package/dist/gs/crypto/rand/index.d.ts +5 -0
  54. package/dist/gs/crypto/rand/index.js +77 -0
  55. package/dist/gs/crypto/rand/index.js.map +1 -0
  56. package/dist/gs/encoding/json/index.d.ts +3 -0
  57. package/dist/gs/encoding/json/index.js +160 -0
  58. package/dist/gs/encoding/json/index.js.map +1 -0
  59. package/dist/gs/fmt/fmt.js.map +1 -1
  60. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  61. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  62. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  63. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  64. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  65. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  66. package/dist/gs/go/scanner/index.d.ts +29 -0
  67. package/dist/gs/go/scanner/index.js +120 -0
  68. package/dist/gs/go/scanner/index.js.map +1 -0
  69. package/dist/gs/go/token/index.d.ts +31 -0
  70. package/dist/gs/go/token/index.js +82 -0
  71. package/dist/gs/go/token/index.js.map +1 -0
  72. package/dist/gs/internal/abi/index.js.map +1 -1
  73. package/dist/gs/io/fs/fs.js.map +1 -1
  74. package/dist/gs/io/fs/readdir.js.map +1 -1
  75. package/dist/gs/io/fs/readfile.js.map +1 -1
  76. package/dist/gs/io/fs/stat.js.map +1 -1
  77. package/dist/gs/io/fs/sub.js.map +1 -1
  78. package/dist/gs/io/io.js.map +1 -1
  79. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  80. package/dist/gs/os/error.gs.js +2 -4
  81. package/dist/gs/os/error.gs.js.map +1 -1
  82. package/dist/gs/os/exec.gs.js.map +1 -1
  83. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  84. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  85. package/dist/gs/os/root_js.gs.js.map +1 -1
  86. package/dist/gs/os/tempfile.gs.js +66 -9
  87. package/dist/gs/os/tempfile.gs.js.map +1 -1
  88. package/dist/gs/os/types.gs.js.map +1 -1
  89. package/dist/gs/os/types_js.gs.js +9 -9
  90. package/dist/gs/os/types_js.gs.js.map +1 -1
  91. package/dist/gs/os/types_unix.gs.js.map +1 -1
  92. package/dist/gs/path/filepath/match.js.map +1 -1
  93. package/dist/gs/path/match.js.map +1 -1
  94. package/dist/gs/path/path.js.map +1 -1
  95. package/dist/gs/reflect/index.d.ts +2 -2
  96. package/dist/gs/reflect/index.js +1 -1
  97. package/dist/gs/reflect/index.js.map +1 -1
  98. package/dist/gs/reflect/map.js.map +1 -1
  99. package/dist/gs/reflect/type.d.ts +2 -1
  100. package/dist/gs/reflect/type.js +85 -14
  101. package/dist/gs/reflect/type.js.map +1 -1
  102. package/dist/gs/reflect/types.js.map +1 -1
  103. package/dist/gs/reflect/visiblefields.js.map +1 -1
  104. package/dist/gs/runtime/runtime.js.map +1 -1
  105. package/dist/gs/sort/sort.gs.js.map +1 -1
  106. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  107. package/dist/gs/strconv/quote.gs.js.map +1 -1
  108. package/dist/gs/strings/builder.js.map +1 -1
  109. package/dist/gs/strings/reader.js.map +1 -1
  110. package/dist/gs/strings/replace.js.map +1 -1
  111. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  112. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  113. package/dist/gs/sync/sync.d.ts +1 -0
  114. package/dist/gs/sync/sync.js +12 -0
  115. package/dist/gs/sync/sync.js.map +1 -1
  116. package/dist/gs/time/time.js.map +1 -1
  117. package/dist/gs/unicode/unicode.js.map +1 -1
  118. package/go.mod +2 -2
  119. package/gs/builtin/builtin.ts +27 -0
  120. package/gs/builtin/hostio.test.ts +177 -0
  121. package/gs/builtin/hostio.ts +171 -56
  122. package/gs/builtin/index.ts +1 -0
  123. package/gs/builtin/runtime-contract.test.ts +230 -0
  124. package/gs/builtin/type.ts +84 -1
  125. package/gs/crypto/rand/index.test.ts +32 -0
  126. package/gs/crypto/rand/index.ts +90 -0
  127. package/gs/crypto/rand/meta.json +5 -0
  128. package/gs/encoding/json/index.test.ts +65 -0
  129. package/gs/encoding/json/index.ts +186 -0
  130. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  131. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  132. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  133. package/gs/go/scanner/index.test.ts +50 -0
  134. package/gs/go/scanner/index.ts +157 -0
  135. package/gs/go/token/index.test.ts +21 -0
  136. package/gs/go/token/index.ts +120 -0
  137. package/gs/os/file_unix_js.test.ts +50 -0
  138. package/gs/os/meta.json +1 -2
  139. package/gs/os/tempfile.gs.test.ts +85 -0
  140. package/gs/os/tempfile.gs.ts +71 -11
  141. package/gs/os/types_js.gs.ts +9 -9
  142. package/gs/reflect/index.ts +1 -1
  143. package/gs/reflect/type.ts +106 -17
  144. package/gs/reflect/typefor.test.ts +75 -0
  145. package/gs/sync/sync.test.ts +24 -0
  146. package/gs/sync/sync.ts +12 -0
  147. package/package.json +13 -13
  148. package/compiler/analysis.go +0 -3475
  149. package/compiler/analysis_test.go +0 -338
  150. package/compiler/assignment.go +0 -580
  151. package/compiler/builtin_test.go +0 -92
  152. package/compiler/code-writer.go +0 -115
  153. package/compiler/compiler_test.go +0 -149
  154. package/compiler/composite-lit.go +0 -779
  155. package/compiler/config_test.go +0 -62
  156. package/compiler/constraint.go +0 -86
  157. package/compiler/decl.go +0 -801
  158. package/compiler/expr-call-async.go +0 -188
  159. package/compiler/expr-call-builtins.go +0 -208
  160. package/compiler/expr-call-helpers.go +0 -382
  161. package/compiler/expr-call-make.go +0 -318
  162. package/compiler/expr-call-type-conversion.go +0 -520
  163. package/compiler/expr-call.go +0 -413
  164. package/compiler/expr-selector.go +0 -343
  165. package/compiler/expr-star.go +0 -82
  166. package/compiler/expr-type.go +0 -442
  167. package/compiler/expr-value.go +0 -89
  168. package/compiler/expr.go +0 -773
  169. package/compiler/field.go +0 -183
  170. package/compiler/gs_dependencies_test.go +0 -298
  171. package/compiler/lit.go +0 -322
  172. package/compiler/output.go +0 -72
  173. package/compiler/primitive.go +0 -149
  174. package/compiler/protobuf.go +0 -697
  175. package/compiler/sanitize.go +0 -100
  176. package/compiler/spec-struct.go +0 -995
  177. package/compiler/spec-value.go +0 -540
  178. package/compiler/spec.go +0 -725
  179. package/compiler/stmt-assign.go +0 -664
  180. package/compiler/stmt-for.go +0 -266
  181. package/compiler/stmt-range.go +0 -475
  182. package/compiler/stmt-select.go +0 -262
  183. package/compiler/stmt-type-switch.go +0 -147
  184. package/compiler/stmt.go +0 -1308
  185. package/compiler/type-assert.go +0 -386
  186. package/compiler/type-info.go +0 -156
  187. package/compiler/type-utils.go +0 -207
  188. package/compiler/type.go +0 -892
@@ -0,0 +1,422 @@
1
+ package compiler
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "io"
7
+ "io/fs"
8
+ "os"
9
+ "path"
10
+ "path/filepath"
11
+ "slices"
12
+ "strings"
13
+
14
+ gs "github.com/aperturerobotics/goscript"
15
+ jsoniter "github.com/aperturerobotics/json-iterator-lite"
16
+ )
17
+
18
+ // OverrideMetadata describes compiler-visible facts from a package override.
19
+ type OverrideMetadata struct {
20
+ // Dependencies are override package dependencies.
21
+ Dependencies []string
22
+ // AsyncMethods maps Type.Method keys to async status.
23
+ AsyncMethods map[string]bool
24
+ }
25
+
26
+ // OverrideRegistryOwner owns GoScript override package metadata and copy plans.
27
+ type OverrideRegistryOwner struct {
28
+ metadata map[string]*OverrideMetadata
29
+ packageRootCache map[string]bool
30
+ }
31
+
32
+ // NewOverrideRegistryOwner creates the override registry owner.
33
+ func NewOverrideRegistryOwner() *OverrideRegistryOwner {
34
+ return &OverrideRegistryOwner{
35
+ metadata: make(map[string]*OverrideMetadata),
36
+ }
37
+ }
38
+
39
+ // Metadata returns compiler-visible override metadata for a package path.
40
+ func (o *OverrideRegistryOwner) Metadata(pkgPath string) (*OverrideMetadata, error) {
41
+ if o == nil {
42
+ o = NewOverrideRegistryOwner()
43
+ }
44
+ if metadata := o.metadata[pkgPath]; metadata != nil {
45
+ return metadata, nil
46
+ }
47
+
48
+ metadata := &OverrideMetadata{
49
+ AsyncMethods: make(map[string]bool),
50
+ }
51
+ data, err := gs.GsOverrides.ReadFile("gs/" + pkgPath + "/meta.json")
52
+ if err != nil {
53
+ if errors.Is(err, fs.ErrNotExist) {
54
+ o.metadata[pkgPath] = metadata
55
+ return metadata, nil
56
+ }
57
+ return nil, err
58
+ }
59
+
60
+ iter := jsoniter.ParseBytes(data)
61
+ for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
62
+ switch field {
63
+ case "dependencies":
64
+ for iter.ReadArray() {
65
+ metadata.Dependencies = append(metadata.Dependencies, iter.ReadString())
66
+ }
67
+ case "asyncMethods":
68
+ for method := iter.ReadObject(); method != ""; method = iter.ReadObject() {
69
+ metadata.AsyncMethods[method] = iter.ReadBool()
70
+ }
71
+ default:
72
+ iter.Skip()
73
+ }
74
+ }
75
+ if iter.Error != nil && !errors.Is(iter.Error, io.EOF) {
76
+ return nil, iter.Error
77
+ }
78
+
79
+ o.metadata[pkgPath] = metadata
80
+ return metadata, nil
81
+ }
82
+
83
+ // CopyPlan builds the ordered override package copy plan for a request.
84
+ func (o *OverrideRegistryOwner) CopyPlan(
85
+ ctx context.Context,
86
+ req *CompileRequest,
87
+ graph *PackageGraph,
88
+ ) (*overrideCopyPlan, []Diagnostic) {
89
+ if o == nil {
90
+ o = NewOverrideRegistryOwner()
91
+ }
92
+ if err := ctx.Err(); err != nil {
93
+ return nil, []Diagnostic{{
94
+ Severity: DiagnosticSeverityError,
95
+ Code: "goscript/context:canceled",
96
+ Message: err.Error(),
97
+ }}
98
+ }
99
+ plan := &overrideCopyPlan{}
100
+ if req == nil || req.RuntimeEmissionMode == RuntimeEmissionModeReference {
101
+ return plan, nil
102
+ }
103
+ if graph == nil {
104
+ return nil, []Diagnostic{{
105
+ Severity: DiagnosticSeverityError,
106
+ Code: "goscript/overrides:no-graph",
107
+ Message: "override copy planning requires a package graph",
108
+ }}
109
+ }
110
+ if _, err := o.packageRoots(); err != nil {
111
+ return nil, []Diagnostic{overrideError("discover override packages", "", err)}
112
+ }
113
+
114
+ var diagnostics []Diagnostic
115
+ visiting := make(map[string]bool)
116
+ visited := make(map[string]bool)
117
+ diagnostics = append(diagnostics, o.addPackageToPlan(ctx, plan, "builtin", visiting, visited)...)
118
+ for _, node := range graph.Nodes {
119
+ if node.OverrideCandidate {
120
+ diagnostics = append(diagnostics, o.addPackageToPlan(ctx, plan, node.PkgPath, visiting, visited)...)
121
+ }
122
+ }
123
+ if diagnosticsHaveErrors(diagnostics) {
124
+ return nil, diagnostics
125
+ }
126
+ return plan, diagnostics
127
+ }
128
+
129
+ // CopyPackages writes override packages from a copy plan to the request output tree.
130
+ func (o *OverrideRegistryOwner) CopyPackages(
131
+ ctx context.Context,
132
+ req *CompileRequest,
133
+ plan *overrideCopyPlan,
134
+ ) ([]string, []Diagnostic) {
135
+ if err := ctx.Err(); err != nil {
136
+ return nil, []Diagnostic{{
137
+ Severity: DiagnosticSeverityError,
138
+ Code: "goscript/context:canceled",
139
+ Message: err.Error(),
140
+ }}
141
+ }
142
+ if req == nil {
143
+ return nil, []Diagnostic{{
144
+ Severity: DiagnosticSeverityError,
145
+ Code: "goscript/overrides:no-request",
146
+ Message: "override package copying requires a compile request",
147
+ }}
148
+ }
149
+ if plan == nil || len(plan.packages) == 0 {
150
+ return nil, nil
151
+ }
152
+
153
+ var copied []string
154
+ var diagnostics []Diagnostic
155
+ for _, pkg := range plan.packages {
156
+ if err := ctx.Err(); err != nil {
157
+ diagnostics = append(diagnostics, Diagnostic{
158
+ Severity: DiagnosticSeverityError,
159
+ Code: "goscript/context:canceled",
160
+ Message: err.Error(),
161
+ })
162
+ break
163
+ }
164
+ for _, file := range pkg.files {
165
+ dest := filepath.Join(req.OutputPath, "@goscript", filepath.FromSlash(file.path))
166
+ if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
167
+ diagnostics = append(diagnostics, overrideError("create override output", file.path, err))
168
+ continue
169
+ }
170
+ if err := os.WriteFile(dest, file.data, 0o644); err != nil {
171
+ diagnostics = append(diagnostics, overrideError("write override file", file.path, err))
172
+ }
173
+ }
174
+ copied = append(copied, pkg.path)
175
+ }
176
+ if diagnosticsHaveErrors(diagnostics) {
177
+ return copied, diagnostics
178
+ }
179
+ return copied, diagnostics
180
+ }
181
+
182
+ // IsMethodAsync returns true when override metadata marks a method async.
183
+ func (o *OverrideRegistryOwner) IsMethodAsync(pkgPath, method string) (bool, error) {
184
+ metadata, err := o.Metadata(pkgPath)
185
+ if err != nil {
186
+ return false, err
187
+ }
188
+ return metadata.AsyncMethods[method], nil
189
+ }
190
+
191
+ type overrideCopyPlan struct {
192
+ packages []overrideCopyPackage
193
+ }
194
+
195
+ type overrideCopyPackage struct {
196
+ path string
197
+ files []overrideCopyFile
198
+ }
199
+
200
+ type overrideCopyFile struct {
201
+ path string
202
+ data []byte
203
+ }
204
+
205
+ func (o *OverrideRegistryOwner) addPackageToPlan(
206
+ ctx context.Context,
207
+ plan *overrideCopyPlan,
208
+ pkgPath string,
209
+ visiting map[string]bool,
210
+ visited map[string]bool,
211
+ ) []Diagnostic {
212
+ if err := ctx.Err(); err != nil {
213
+ return []Diagnostic{{
214
+ Severity: DiagnosticSeverityError,
215
+ Code: "goscript/context:canceled",
216
+ Message: err.Error(),
217
+ }}
218
+ }
219
+ if visited[pkgPath] {
220
+ return nil
221
+ }
222
+ if visiting[pkgPath] {
223
+ return []Diagnostic{{
224
+ Severity: DiagnosticSeverityError,
225
+ Code: "goscript/overrides:dependency-cycle",
226
+ Message: "override package dependencies contain a cycle",
227
+ Detail: pkgPath,
228
+ }}
229
+ }
230
+
231
+ pkg, dependencies, diagnostics := o.loadCopyPackage(pkgPath)
232
+ if diagnosticsHaveErrors(diagnostics) {
233
+ return diagnostics
234
+ }
235
+ visiting[pkgPath] = true
236
+ for _, dependency := range dependencies {
237
+ diagnostics = append(diagnostics, o.addPackageToPlan(ctx, plan, dependency, visiting, visited)...)
238
+ }
239
+ delete(visiting, pkgPath)
240
+ if diagnosticsHaveErrors(diagnostics) {
241
+ return diagnostics
242
+ }
243
+
244
+ visited[pkgPath] = true
245
+ plan.packages = append(plan.packages, pkg)
246
+ return diagnostics
247
+ }
248
+
249
+ func (o *OverrideRegistryOwner) loadCopyPackage(pkgPath string) (overrideCopyPackage, []string, []Diagnostic) {
250
+ roots, err := o.packageRoots()
251
+ if err != nil {
252
+ return overrideCopyPackage{}, nil, []Diagnostic{overrideError("discover override packages", "", err)}
253
+ }
254
+ if !roots[pkgPath] {
255
+ return overrideCopyPackage{}, nil, []Diagnostic{{
256
+ Severity: DiagnosticSeverityError,
257
+ Code: "goscript/overrides:missing-package",
258
+ Message: "override package does not exist",
259
+ Detail: pkgPath,
260
+ }}
261
+ }
262
+
263
+ metadata, err := o.Metadata(pkgPath)
264
+ if err != nil {
265
+ return overrideCopyPackage{}, nil, []Diagnostic{overrideError("read override metadata", pkgPath, err)}
266
+ }
267
+
268
+ copyPackage := overrideCopyPackage{path: pkgPath}
269
+ dependencySet := make(map[string]bool)
270
+ for _, dependency := range metadata.Dependencies {
271
+ dependency = strings.TrimSpace(dependency)
272
+ if dependency != "" && dependency != pkgPath {
273
+ dependencySet[dependency] = true
274
+ }
275
+ }
276
+
277
+ root := "gs/" + pkgPath
278
+ err = fs.WalkDir(gs.GsOverrides, root, func(filePath string, entry fs.DirEntry, walkErr error) error {
279
+ if walkErr != nil {
280
+ return walkErr
281
+ }
282
+ if entry.IsDir() {
283
+ nestedPkg := strings.TrimPrefix(filePath, "gs/")
284
+ if nestedPkg != pkgPath && roots[nestedPkg] {
285
+ return fs.SkipDir
286
+ }
287
+ return nil
288
+ }
289
+ if !isOverrideSourceFile(filePath) {
290
+ return nil
291
+ }
292
+ data, readErr := gs.GsOverrides.ReadFile(filePath)
293
+ if readErr != nil {
294
+ return readErr
295
+ }
296
+ rel := strings.TrimPrefix(filePath, "gs/")
297
+ copyPackage.files = append(copyPackage.files, overrideCopyFile{
298
+ path: rel,
299
+ data: data,
300
+ })
301
+ for _, imported := range scanOverrideImports(string(data)) {
302
+ dependency, ok := o.importPackageRoot(imported)
303
+ if ok && dependency != "builtin" && dependency != pkgPath {
304
+ dependencySet[dependency] = true
305
+ }
306
+ }
307
+ return nil
308
+ })
309
+ if err != nil {
310
+ return overrideCopyPackage{}, nil, []Diagnostic{overrideError("read override package", pkgPath, err)}
311
+ }
312
+
313
+ if len(copyPackage.files) == 0 {
314
+ return overrideCopyPackage{}, nil, []Diagnostic{{
315
+ Severity: DiagnosticSeverityError,
316
+ Code: "goscript/overrides:empty-package",
317
+ Message: "override package does not contain TypeScript source files",
318
+ Detail: pkgPath,
319
+ }}
320
+ }
321
+ slices.SortFunc(copyPackage.files, func(a, b overrideCopyFile) int {
322
+ return strings.Compare(a.path, b.path)
323
+ })
324
+
325
+ dependencies := make([]string, 0, len(dependencySet))
326
+ for dependency := range dependencySet {
327
+ dependencies = append(dependencies, dependency)
328
+ }
329
+ slices.Sort(dependencies)
330
+ return copyPackage, dependencies, nil
331
+ }
332
+
333
+ func (o *OverrideRegistryOwner) packageRoots() (map[string]bool, error) {
334
+ if o.packageRootCache != nil {
335
+ return o.packageRootCache, nil
336
+ }
337
+ roots := make(map[string]bool)
338
+ if err := fs.WalkDir(gs.GsOverrides, "gs", func(filePath string, entry fs.DirEntry, err error) error {
339
+ if err != nil {
340
+ return err
341
+ }
342
+ if entry.IsDir() || path.Base(filePath) != "index.ts" {
343
+ return nil
344
+ }
345
+ pkgPath := strings.TrimPrefix(path.Dir(filePath), "gs/")
346
+ if pkgPath != "." && pkgPath != "" {
347
+ roots[pkgPath] = true
348
+ }
349
+ return nil
350
+ }); err != nil {
351
+ return nil, err
352
+ }
353
+ o.packageRootCache = roots
354
+ return roots, nil
355
+ }
356
+
357
+ func (o *OverrideRegistryOwner) importPackageRoot(importPath string) (string, bool) {
358
+ roots, err := o.packageRoots()
359
+ if err != nil {
360
+ return "", false
361
+ }
362
+ importPath = strings.TrimPrefix(importPath, "@goscript/")
363
+ importPath = strings.TrimSuffix(importPath, ".js")
364
+ importPath = strings.TrimSuffix(importPath, ".ts")
365
+ if before, ok := strings.CutSuffix(importPath, "/index"); ok {
366
+ importPath = before
367
+ }
368
+ parts := strings.Split(importPath, "/")
369
+ for idx := len(parts); idx > 0; idx-- {
370
+ candidate := strings.Join(parts[:idx], "/")
371
+ if roots[candidate] {
372
+ return candidate, true
373
+ }
374
+ }
375
+ return "", false
376
+ }
377
+
378
+ func isOverrideSourceFile(filePath string) bool {
379
+ return strings.HasSuffix(filePath, ".ts") && !strings.HasSuffix(filePath, ".test.ts")
380
+ }
381
+
382
+ func scanOverrideImports(data string) []string {
383
+ imports := make(map[string]bool)
384
+ for line := range strings.SplitSeq(data, "\n") {
385
+ line = strings.TrimSpace(line)
386
+ if strings.HasPrefix(line, "//") {
387
+ continue
388
+ }
389
+ for _, quote := range []string{"\"", "'"} {
390
+ prefix := quote + "@goscript/"
391
+ remaining := line
392
+ for {
393
+ idx := strings.Index(remaining, prefix)
394
+ if idx < 0 {
395
+ break
396
+ }
397
+ remaining = remaining[idx+len(quote):]
398
+ end := strings.Index(remaining, quote)
399
+ if end < 0 {
400
+ break
401
+ }
402
+ imports[remaining[:end]] = true
403
+ remaining = remaining[end+len(quote):]
404
+ }
405
+ }
406
+ }
407
+ result := make([]string, 0, len(imports))
408
+ for imported := range imports {
409
+ result = append(result, imported)
410
+ }
411
+ slices.Sort(result)
412
+ return result
413
+ }
414
+
415
+ func overrideError(action, detail string, err error) Diagnostic {
416
+ return Diagnostic{
417
+ Severity: DiagnosticSeverityError,
418
+ Code: "goscript/overrides:io",
419
+ Message: "failed to " + action,
420
+ Detail: strings.TrimSpace(detail + " " + err.Error()),
421
+ }
422
+ }
@@ -0,0 +1,207 @@
1
+ package compiler
2
+
3
+ import (
4
+ "context"
5
+ "os"
6
+ "path/filepath"
7
+ "slices"
8
+ "strings"
9
+ "testing"
10
+ )
11
+
12
+ func TestOverrideRegistryPlansRuntimeAndOverrideDependencies(t *testing.T) {
13
+ owner := NewOverrideRegistryOwner()
14
+ plan, diagnostics := owner.CopyPlan(context.Background(), &CompileRequest{
15
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
16
+ }, &PackageGraph{Nodes: []*PackageGraphNode{{
17
+ PkgPath: "fmt",
18
+ OverrideCandidate: true,
19
+ }}})
20
+ if diagnosticsHaveErrors(diagnostics) {
21
+ t.Fatalf("copy plan failed: %#v", diagnostics)
22
+ }
23
+
24
+ var packages []string
25
+ for _, pkg := range plan.packages {
26
+ packages = append(packages, pkg.path)
27
+ }
28
+ for _, pkg := range []string{"builtin", "errors", "fmt"} {
29
+ if !slices.Contains(packages, pkg) {
30
+ t.Fatalf("missing %s in copy plan: %v", pkg, packages)
31
+ }
32
+ }
33
+ if slices.Index(packages, "errors") > slices.Index(packages, "fmt") {
34
+ t.Fatalf("dependency order is wrong: %v", packages)
35
+ }
36
+ }
37
+
38
+ func TestOverrideRegistryCopiesRuntimeAndOverrides(t *testing.T) {
39
+ owner := NewOverrideRegistryOwner()
40
+ req := &CompileRequest{
41
+ OutputPath: filepath.Join(t.TempDir(), "out"),
42
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
43
+ }
44
+ plan, diagnostics := owner.CopyPlan(context.Background(), req, &PackageGraph{Nodes: []*PackageGraphNode{{
45
+ PkgPath: "fmt",
46
+ OverrideCandidate: true,
47
+ }}})
48
+ if diagnosticsHaveErrors(diagnostics) {
49
+ t.Fatalf("copy plan failed: %#v", diagnostics)
50
+ }
51
+ copied, diagnostics := owner.CopyPackages(context.Background(), req, plan)
52
+ if diagnosticsHaveErrors(diagnostics) {
53
+ t.Fatalf("copy failed: %#v", diagnostics)
54
+ }
55
+ for _, pkg := range []string{"builtin", "errors", "fmt"} {
56
+ if !slices.Contains(copied, pkg) {
57
+ t.Fatalf("missing copied package %s in %v", pkg, copied)
58
+ }
59
+ }
60
+ for _, path := range []string{
61
+ "@goscript/builtin/index.ts",
62
+ "@goscript/errors/index.ts",
63
+ "@goscript/fmt/index.ts",
64
+ } {
65
+ if _, err := os.Stat(filepath.Join(req.OutputPath, filepath.FromSlash(path))); err != nil {
66
+ t.Fatalf("expected copied file %s: %v", path, err)
67
+ }
68
+ }
69
+ if _, err := os.Stat(filepath.Join(req.OutputPath, "@goscript", "fmt", "fmt.test.ts")); !os.IsNotExist(err) {
70
+ t.Fatalf("override copy should not include test files")
71
+ }
72
+ }
73
+
74
+ func TestOverrideRegistryReportsMissingOverridePackage(t *testing.T) {
75
+ _, diagnostics := NewOverrideRegistryOwner().CopyPlan(context.Background(), &CompileRequest{
76
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
77
+ }, &PackageGraph{Nodes: []*PackageGraphNode{{
78
+ PkgPath: "does/not/exist",
79
+ OverrideCandidate: true,
80
+ }}})
81
+ requireDiagnosticCode(t, diagnostics, "goscript/overrides:missing-package")
82
+ }
83
+
84
+ func TestOverrideRegistryPlansOsOverrideDependencies(t *testing.T) {
85
+ owner := NewOverrideRegistryOwner()
86
+ plan, diagnostics := owner.CopyPlan(context.Background(), &CompileRequest{
87
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
88
+ }, &PackageGraph{Nodes: []*PackageGraphNode{{
89
+ PkgPath: "os",
90
+ OverrideCandidate: true,
91
+ }}})
92
+ if diagnosticsHaveErrors(diagnostics) {
93
+ t.Fatalf("copy plan failed: %#v", diagnostics)
94
+ }
95
+
96
+ var packages []string
97
+ for _, pkg := range plan.packages {
98
+ packages = append(packages, pkg.path)
99
+ }
100
+ if !slices.Contains(packages, "os") {
101
+ t.Fatalf("missing os in copy plan: %v", packages)
102
+ }
103
+ if slices.Contains(packages, "internal/poll") {
104
+ t.Fatalf("os copy plan includes stale internal/poll dependency: %v", packages)
105
+ }
106
+ }
107
+
108
+ func TestOverrideRegistryPlansNestedOverrideMetadataDependencies(t *testing.T) {
109
+ owner := NewOverrideRegistryOwner()
110
+ plan, diagnostics := owner.CopyPlan(context.Background(), &CompileRequest{
111
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
112
+ }, &PackageGraph{Nodes: []*PackageGraphNode{{
113
+ PkgPath: "github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser",
114
+ OverrideCandidate: true,
115
+ }}})
116
+ if diagnosticsHaveErrors(diagnostics) {
117
+ t.Fatalf("copy plan failed: %#v", diagnostics)
118
+ }
119
+
120
+ var packages []string
121
+ for _, pkg := range plan.packages {
122
+ packages = append(packages, pkg.path)
123
+ }
124
+
125
+ runtimePkg := "github.com/aperturerobotics/wasivm/wazero/kernel/runtime"
126
+ browserPkg := "github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser"
127
+ for _, pkg := range []string{"builtin", runtimePkg, browserPkg} {
128
+ if !slices.Contains(packages, pkg) {
129
+ t.Fatalf("missing %s in copy plan: %v", pkg, packages)
130
+ }
131
+ }
132
+ if slices.Index(packages, runtimePkg) > slices.Index(packages, browserPkg) {
133
+ t.Fatalf("nested override dependency order is wrong: %v", packages)
134
+ }
135
+ }
136
+
137
+ func TestCompilePackagesCopiesRuntimeOverrides(t *testing.T) {
138
+ moduleDir := writePackageGraphFixture(t, map[string]string{
139
+ "go.mod": "module example.test/overridecopy\n\ngo 1.25.3\n",
140
+ "main.go": "package main\nimport \"fmt\"\nfunc main() { fmt.Println(\"ok\") }\n",
141
+ })
142
+ out := filepath.Join(t.TempDir(), "out")
143
+ comp, err := NewCompiler(&Config{
144
+ Dir: moduleDir,
145
+ OutputPath: out,
146
+ AllDependencies: true,
147
+ }, nil, nil)
148
+ if err != nil {
149
+ t.Fatal(err.Error())
150
+ }
151
+
152
+ result, err := comp.CompilePackages(context.Background(), ".")
153
+ if err != nil {
154
+ t.Fatalf("compile failed: %v\n%#v", err, result.Diagnostics)
155
+ }
156
+ for _, pkg := range []string{"builtin", "errors", "fmt"} {
157
+ if !slices.Contains(result.CopiedPackages, pkg) {
158
+ t.Fatalf("missing copied package %s in %#v", pkg, result.CopiedPackages)
159
+ }
160
+ }
161
+ for _, path := range []string{
162
+ "@goscript/example.test/overridecopy/main.gs.ts",
163
+ "@goscript/builtin/index.ts",
164
+ "@goscript/fmt/index.ts",
165
+ } {
166
+ if _, err := os.Stat(filepath.Join(out, filepath.FromSlash(path))); err != nil {
167
+ t.Fatalf("expected output file %s: %v", path, err)
168
+ }
169
+ }
170
+ }
171
+
172
+ func TestCompilePackagesAwaitsOverrideAsyncMethods(t *testing.T) {
173
+ moduleDir := writePackageGraphFixture(t, map[string]string{
174
+ "go.mod": "module example.test/overrideasync\n\ngo 1.25.3\n",
175
+ "main.go": strings.Join([]string{
176
+ "package main",
177
+ "import \"sync\"",
178
+ "func main() {",
179
+ " var m sync.Map",
180
+ " if value, ok := m.Load(\"key\"); ok {",
181
+ " println(value)",
182
+ " }",
183
+ "}",
184
+ "",
185
+ }, "\n"),
186
+ })
187
+ out := filepath.Join(t.TempDir(), "out")
188
+ comp, err := NewCompiler(&Config{
189
+ Dir: moduleDir,
190
+ OutputPath: out,
191
+ AllDependencies: true,
192
+ }, nil, nil)
193
+ if err != nil {
194
+ t.Fatal(err.Error())
195
+ }
196
+
197
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
198
+ t.Fatal(err.Error())
199
+ }
200
+ content, err := os.ReadFile(filepath.Join(out, "@goscript", "example.test", "overrideasync", "main.gs.ts"))
201
+ if err != nil {
202
+ t.Fatal(err.Error())
203
+ }
204
+ if !strings.Contains(string(content), "let [value, ok] = await m.value.Load(\"key\")") {
205
+ t.Fatalf("override async method call was not awaited:\n%s", string(content))
206
+ }
207
+ }