goscript 0.0.83 → 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 (197) 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 +27 -7
  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 +86 -0
  39. package/dist/gs/builtin/hostio.js +266 -0
  40. package/dist/gs/builtin/hostio.js.map +1 -0
  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/print.d.ts +8 -0
  45. package/dist/gs/builtin/print.js +111 -0
  46. package/dist/gs/builtin/print.js.map +1 -0
  47. package/dist/gs/builtin/slice.d.ts +1 -1
  48. package/dist/gs/builtin/slice.js.map +1 -1
  49. package/dist/gs/builtin/type.d.ts +11 -0
  50. package/dist/gs/builtin/type.js +55 -1
  51. package/dist/gs/builtin/type.js.map +1 -1
  52. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  53. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  54. package/dist/gs/bytes/reader.gs.js.map +1 -1
  55. package/dist/gs/context/context.js.map +1 -1
  56. package/dist/gs/crypto/rand/index.d.ts +5 -0
  57. package/dist/gs/crypto/rand/index.js +77 -0
  58. package/dist/gs/crypto/rand/index.js.map +1 -0
  59. package/dist/gs/encoding/json/index.d.ts +3 -0
  60. package/dist/gs/encoding/json/index.js +160 -0
  61. package/dist/gs/encoding/json/index.js.map +1 -0
  62. package/dist/gs/fmt/fmt.js +2 -22
  63. package/dist/gs/fmt/fmt.js.map +1 -1
  64. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  65. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  66. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  67. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  68. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  69. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  70. package/dist/gs/go/scanner/index.d.ts +29 -0
  71. package/dist/gs/go/scanner/index.js +120 -0
  72. package/dist/gs/go/scanner/index.js.map +1 -0
  73. package/dist/gs/go/token/index.d.ts +31 -0
  74. package/dist/gs/go/token/index.js +82 -0
  75. package/dist/gs/go/token/index.js.map +1 -0
  76. package/dist/gs/internal/abi/index.js.map +1 -1
  77. package/dist/gs/io/fs/fs.js.map +1 -1
  78. package/dist/gs/io/fs/readdir.js.map +1 -1
  79. package/dist/gs/io/fs/readfile.js.map +1 -1
  80. package/dist/gs/io/fs/stat.js.map +1 -1
  81. package/dist/gs/io/fs/sub.js.map +1 -1
  82. package/dist/gs/io/io.js.map +1 -1
  83. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  84. package/dist/gs/os/error.gs.js +2 -4
  85. package/dist/gs/os/error.gs.js.map +1 -1
  86. package/dist/gs/os/exec.gs.js.map +1 -1
  87. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  88. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  89. package/dist/gs/os/root_js.gs.js.map +1 -1
  90. package/dist/gs/os/tempfile.gs.js +66 -9
  91. package/dist/gs/os/tempfile.gs.js.map +1 -1
  92. package/dist/gs/os/types.gs.js.map +1 -1
  93. package/dist/gs/os/types_js.gs.d.ts +2 -51
  94. package/dist/gs/os/types_js.gs.js +67 -105
  95. package/dist/gs/os/types_js.gs.js.map +1 -1
  96. package/dist/gs/os/types_unix.gs.js.map +1 -1
  97. package/dist/gs/path/filepath/match.js.map +1 -1
  98. package/dist/gs/path/match.js.map +1 -1
  99. package/dist/gs/path/path.js.map +1 -1
  100. package/dist/gs/reflect/index.d.ts +2 -2
  101. package/dist/gs/reflect/index.js +1 -1
  102. package/dist/gs/reflect/index.js.map +1 -1
  103. package/dist/gs/reflect/map.js.map +1 -1
  104. package/dist/gs/reflect/type.d.ts +2 -1
  105. package/dist/gs/reflect/type.js +85 -14
  106. package/dist/gs/reflect/type.js.map +1 -1
  107. package/dist/gs/reflect/types.js.map +1 -1
  108. package/dist/gs/reflect/visiblefields.js.map +1 -1
  109. package/dist/gs/runtime/runtime.js.map +1 -1
  110. package/dist/gs/sort/sort.gs.js.map +1 -1
  111. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  112. package/dist/gs/strconv/quote.gs.js.map +1 -1
  113. package/dist/gs/strings/builder.js.map +1 -1
  114. package/dist/gs/strings/reader.js.map +1 -1
  115. package/dist/gs/strings/replace.js.map +1 -1
  116. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  117. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  118. package/dist/gs/sync/sync.d.ts +1 -0
  119. package/dist/gs/sync/sync.js +12 -0
  120. package/dist/gs/sync/sync.js.map +1 -1
  121. package/dist/gs/time/time.js.map +1 -1
  122. package/dist/gs/unicode/unicode.js.map +1 -1
  123. package/go.mod +2 -2
  124. package/gs/builtin/builtin.ts +31 -6
  125. package/gs/builtin/hostio.test.ts +246 -0
  126. package/gs/builtin/hostio.ts +413 -0
  127. package/gs/builtin/index.ts +1 -0
  128. package/gs/builtin/print.test.ts +48 -0
  129. package/gs/builtin/print.ts +154 -0
  130. package/gs/builtin/runtime-contract.test.ts +230 -0
  131. package/gs/builtin/type.ts +84 -1
  132. package/gs/crypto/rand/index.test.ts +32 -0
  133. package/gs/crypto/rand/index.ts +90 -0
  134. package/gs/crypto/rand/meta.json +5 -0
  135. package/gs/encoding/json/index.test.ts +65 -0
  136. package/gs/encoding/json/index.ts +186 -0
  137. package/gs/fmt/fmt.test.ts +41 -30
  138. package/gs/fmt/fmt.ts +2 -22
  139. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  140. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  141. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  142. package/gs/go/scanner/index.test.ts +50 -0
  143. package/gs/go/scanner/index.ts +157 -0
  144. package/gs/go/token/index.test.ts +21 -0
  145. package/gs/go/token/index.ts +120 -0
  146. package/gs/os/file_unix_js.test.ts +103 -0
  147. package/gs/os/meta.json +1 -2
  148. package/gs/os/tempfile.gs.test.ts +85 -0
  149. package/gs/os/tempfile.gs.ts +71 -11
  150. package/gs/os/types_js.gs.ts +74 -153
  151. package/gs/reflect/index.ts +1 -1
  152. package/gs/reflect/type.ts +106 -17
  153. package/gs/reflect/typefor.test.ts +75 -0
  154. package/gs/sync/sync.test.ts +24 -0
  155. package/gs/sync/sync.ts +12 -0
  156. package/package.json +13 -13
  157. package/compiler/analysis.go +0 -3475
  158. package/compiler/analysis_test.go +0 -338
  159. package/compiler/assignment.go +0 -580
  160. package/compiler/builtin_test.go +0 -92
  161. package/compiler/code-writer.go +0 -115
  162. package/compiler/compiler_test.go +0 -149
  163. package/compiler/composite-lit.go +0 -779
  164. package/compiler/config_test.go +0 -62
  165. package/compiler/constraint.go +0 -86
  166. package/compiler/decl.go +0 -801
  167. package/compiler/expr-call-async.go +0 -188
  168. package/compiler/expr-call-builtins.go +0 -208
  169. package/compiler/expr-call-helpers.go +0 -382
  170. package/compiler/expr-call-make.go +0 -318
  171. package/compiler/expr-call-type-conversion.go +0 -520
  172. package/compiler/expr-call.go +0 -413
  173. package/compiler/expr-selector.go +0 -343
  174. package/compiler/expr-star.go +0 -82
  175. package/compiler/expr-type.go +0 -442
  176. package/compiler/expr-value.go +0 -89
  177. package/compiler/expr.go +0 -773
  178. package/compiler/field.go +0 -183
  179. package/compiler/gs_dependencies_test.go +0 -298
  180. package/compiler/lit.go +0 -322
  181. package/compiler/output.go +0 -72
  182. package/compiler/primitive.go +0 -149
  183. package/compiler/protobuf.go +0 -697
  184. package/compiler/sanitize.go +0 -100
  185. package/compiler/spec-struct.go +0 -995
  186. package/compiler/spec-value.go +0 -540
  187. package/compiler/spec.go +0 -725
  188. package/compiler/stmt-assign.go +0 -664
  189. package/compiler/stmt-for.go +0 -266
  190. package/compiler/stmt-range.go +0 -475
  191. package/compiler/stmt-select.go +0 -262
  192. package/compiler/stmt-type-switch.go +0 -147
  193. package/compiler/stmt.go +0 -1308
  194. package/compiler/type-assert.go +0 -386
  195. package/compiler/type-info.go +0 -156
  196. package/compiler/type-utils.go +0 -207
  197. 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
+ }