goscript 0.2.3 → 0.2.5

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 (160) hide show
  1. package/README.md +8 -8
  2. package/cmd/go_js_wasm_exec/main.go +1 -1
  3. package/cmd/go_js_wasm_exec/main_test.go +1 -1
  4. package/cmd/goscript/cmd-compile.go +9 -1
  5. package/cmd/goscript/cmd-test.go +1 -1
  6. package/cmd/goscript/cmd_compile_test.go +44 -0
  7. package/cmd/goscript/deps.go +1 -1
  8. package/cmd/goscript-wasm/main.go +2 -2
  9. package/compiler/compile-request.go +19 -0
  10. package/compiler/compile_bench_test.go +121 -0
  11. package/compiler/compliance_test.go +17 -1
  12. package/compiler/config.go +2 -0
  13. package/compiler/gotest/result.go +1 -1
  14. package/compiler/gotest/runner.go +2 -2
  15. package/compiler/gotest/runner_test.go +4 -7
  16. package/compiler/index.test.ts +28 -0
  17. package/compiler/index.ts +32 -16
  18. package/compiler/lowering.go +1236 -143
  19. package/compiler/lowering_bench_test.go +4 -0
  20. package/compiler/override-facts.go +1 -1
  21. package/compiler/override-registry_test.go +125 -0
  22. package/compiler/package-graph.go +92 -0
  23. package/compiler/package-graph_test.go +113 -0
  24. package/compiler/runtime-contract.go +1 -1
  25. package/compiler/semantic-model.go +32 -0
  26. package/compiler/skeleton_test.go +284 -11
  27. package/compiler/wasm/compile.go +1 -1
  28. package/compiler/wasm/compile_test.go +1 -1
  29. package/dist/compiler/index.d.ts +4 -0
  30. package/dist/compiler/index.js +26 -15
  31. package/dist/compiler/index.js.map +1 -1
  32. package/dist/gs/compress/gzip/index.d.ts +41 -0
  33. package/dist/gs/compress/gzip/index.js +235 -0
  34. package/dist/gs/compress/gzip/index.js.map +1 -0
  35. package/dist/gs/database/sql/driver/index.d.ts +165 -0
  36. package/dist/gs/database/sql/driver/index.js +432 -0
  37. package/dist/gs/database/sql/driver/index.js.map +1 -0
  38. package/dist/gs/encoding/binary/index.d.ts +71 -0
  39. package/dist/gs/encoding/binary/index.js +778 -0
  40. package/dist/gs/encoding/binary/index.js.map +1 -0
  41. package/dist/gs/fmt/fmt.js +156 -57
  42. package/dist/gs/fmt/fmt.js.map +1 -1
  43. package/dist/gs/github.com/klauspost/cpuid/v2/index.d.ts +11 -0
  44. package/dist/gs/github.com/klauspost/cpuid/v2/index.js +28 -0
  45. package/dist/gs/github.com/klauspost/cpuid/v2/index.js.map +1 -0
  46. package/dist/gs/github.com/pkg/errors/errors.d.ts +0 -2
  47. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  48. package/dist/gs/github.com/pkg/errors/index.d.ts +2 -1
  49. package/dist/gs/github.com/pkg/errors/index.js +1 -1
  50. package/dist/gs/github.com/pkg/errors/index.js.map +1 -1
  51. package/dist/gs/github.com/pkg/errors/stack.d.ts +8 -19
  52. package/dist/gs/github.com/pkg/errors/stack.js +26 -61
  53. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  54. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.d.ts +19 -0
  55. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js +25 -0
  56. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js.map +1 -0
  57. package/dist/gs/golang.org/x/crypto/cryptobyte/index.d.ts +104 -0
  58. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1107 -0
  59. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -0
  60. package/dist/gs/golang.org/x/crypto/internal/alias/index.d.ts +3 -0
  61. package/dist/gs/golang.org/x/crypto/internal/alias/index.js +39 -0
  62. package/dist/gs/golang.org/x/crypto/internal/alias/index.js.map +1 -0
  63. package/dist/gs/io/fs/glob.js +1 -1
  64. package/dist/gs/io/fs/glob.js.map +1 -1
  65. package/dist/gs/io/fs/readlink.d.ts +1 -1
  66. package/dist/gs/io/fs/readlink.js +2 -2
  67. package/dist/gs/io/fs/readlink.js.map +1 -1
  68. package/dist/gs/io/fs/stat.d.ts +4 -2
  69. package/dist/gs/io/fs/stat.js +12 -73
  70. package/dist/gs/io/fs/stat.js.map +1 -1
  71. package/dist/gs/io/fs/sub.d.ts +2 -2
  72. package/dist/gs/io/fs/sub.js +7 -7
  73. package/dist/gs/io/fs/sub.js.map +1 -1
  74. package/dist/gs/io/fs/walk.js +1 -1
  75. package/dist/gs/io/fs/walk.js.map +1 -1
  76. package/dist/gs/net/http/index.d.ts +18 -14
  77. package/dist/gs/net/http/index.js +44 -23
  78. package/dist/gs/net/http/index.js.map +1 -1
  79. package/dist/gs/net/http/pprof/index.d.ts +5 -5
  80. package/dist/gs/net/http/pprof/index.js +21 -21
  81. package/dist/gs/net/http/pprof/index.js.map +1 -1
  82. package/dist/gs/runtime/runtime.d.ts +6 -1
  83. package/dist/gs/runtime/runtime.js +15 -8
  84. package/dist/gs/runtime/runtime.js.map +1 -1
  85. package/dist/gs/runtime/trace/index.d.ts +8 -5
  86. package/dist/gs/runtime/trace/index.js +324 -23
  87. package/dist/gs/runtime/trace/index.js.map +1 -1
  88. package/dist/gs/slices/slices.d.ts +2 -1
  89. package/dist/gs/slices/slices.js +9 -3
  90. package/dist/gs/slices/slices.js.map +1 -1
  91. package/dist/gs/sort/search.gs.d.ts +3 -1
  92. package/dist/gs/sort/search.gs.js +18 -53
  93. package/dist/gs/sort/search.gs.js.map +1 -1
  94. package/dist/gs/sync/sync.d.ts +1 -1
  95. package/dist/gs/sync/sync.js +3 -0
  96. package/dist/gs/sync/sync.js.map +1 -1
  97. package/dist/gs/time/time.d.ts +22 -29
  98. package/dist/gs/time/time.js +111 -32
  99. package/dist/gs/time/time.js.map +1 -1
  100. package/dist/gs/unsafe/unsafe.d.ts +3 -2
  101. package/dist/gs/unsafe/unsafe.js.map +1 -1
  102. package/go.mod +7 -5
  103. package/go.sum +12 -26
  104. package/gs/builtin/runtime-contract.test.ts +25 -0
  105. package/gs/compress/gzip/index.test.ts +86 -0
  106. package/gs/compress/gzip/index.ts +297 -0
  107. package/gs/compress/gzip/meta.json +6 -0
  108. package/gs/compress/gzip/parity.json +45 -0
  109. package/gs/database/sql/driver/index.test.ts +88 -0
  110. package/gs/database/sql/driver/index.ts +675 -0
  111. package/gs/database/sql/driver/meta.json +3 -0
  112. package/gs/database/sql/driver/parity.json +144 -0
  113. package/gs/embed/index.test.ts +1 -1
  114. package/gs/encoding/binary/index.test.ts +239 -0
  115. package/gs/encoding/binary/index.ts +999 -0
  116. package/gs/encoding/binary/meta.json +9 -0
  117. package/gs/encoding/binary/parity.json +72 -0
  118. package/gs/fmt/fmt.test.ts +28 -0
  119. package/gs/fmt/fmt.ts +198 -61
  120. package/gs/fmt/meta.json +2 -1
  121. package/gs/github.com/klauspost/cpuid/v2/index.ts +38 -0
  122. package/gs/github.com/klauspost/cpuid/v2/meta.json +3 -0
  123. package/gs/github.com/pkg/errors/errors.ts +1 -2
  124. package/gs/github.com/pkg/errors/index.ts +2 -1
  125. package/gs/github.com/pkg/errors/stack.ts +34 -62
  126. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.test.ts +19 -0
  127. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.ts +29 -0
  128. package/gs/golang.org/x/crypto/cryptobyte/index.test.ts +255 -0
  129. package/gs/golang.org/x/crypto/cryptobyte/index.ts +1441 -0
  130. package/gs/golang.org/x/crypto/cryptobyte/meta.json +3 -0
  131. package/gs/golang.org/x/crypto/internal/alias/index.test.ts +40 -0
  132. package/gs/golang.org/x/crypto/internal/alias/index.ts +40 -0
  133. package/gs/io/fs/glob.ts +1 -1
  134. package/gs/io/fs/meta.json +3 -0
  135. package/gs/io/fs/readlink.test.ts +2 -2
  136. package/gs/io/fs/readlink.ts +5 -2
  137. package/gs/io/fs/stat.test.ts +79 -0
  138. package/gs/io/fs/stat.ts +24 -10
  139. package/gs/io/fs/sub.test.ts +93 -0
  140. package/gs/io/fs/sub.ts +9 -9
  141. package/gs/io/fs/walk.ts +1 -1
  142. package/gs/net/http/index.test.ts +207 -2
  143. package/gs/net/http/index.ts +68 -37
  144. package/gs/net/http/meta.json +3 -1
  145. package/gs/net/http/pprof/index.test.ts +4 -4
  146. package/gs/net/http/pprof/index.ts +30 -27
  147. package/gs/runtime/runtime.test.ts +16 -0
  148. package/gs/runtime/runtime.ts +17 -9
  149. package/gs/runtime/trace/index.test.ts +113 -14
  150. package/gs/runtime/trace/index.ts +384 -34
  151. package/gs/runtime/trace/meta.json +1 -0
  152. package/gs/slices/slices.test.ts +24 -1
  153. package/gs/slices/slices.ts +14 -4
  154. package/gs/sort/meta.json +1 -0
  155. package/gs/sort/search.gs.ts +20 -5
  156. package/gs/sync/sync.ts +4 -1
  157. package/gs/time/time.test.ts +79 -2
  158. package/gs/time/time.ts +133 -33
  159. package/gs/unsafe/unsafe.ts +4 -2
  160. package/package.json +2 -2
@@ -47,6 +47,8 @@ func BenchmarkLoweringPackage(b *testing.B) {
47
47
  fixture.model,
48
48
  fixture.semPkg,
49
49
  make(map[string]map[types.Object]bool),
50
+ make(map[*types.Func]bool),
51
+ make(map[*types.Func]bool),
50
52
  make(runtimeMethodSetCache),
51
53
  LoweringOptions{},
52
54
  ); diagnosticsHaveErrors(diagnostics) {
@@ -86,6 +88,8 @@ func BenchmarkLoweringFile(b *testing.B) {
86
88
  fixture.file.outputNames,
87
89
  fixture.file.lazyPackageVars,
88
90
  fixture.lazyPackageVarsByPkg,
91
+ make(map[*types.Func]bool),
92
+ make(map[*types.Func]bool),
89
93
  make(runtimeMethodSetCache),
90
94
  false,
91
95
  "",
@@ -13,8 +13,8 @@ import (
13
13
  "slices"
14
14
  "strings"
15
15
 
16
- gs "github.com/aperturerobotics/goscript"
17
16
  jsoniter "github.com/aperturerobotics/json-iterator-lite"
17
+ gs "github.com/s4wave/goscript"
18
18
  )
19
19
 
20
20
  // OverrideFacts is the immutable compiler-visible view of GoScript overrides.
@@ -488,6 +488,131 @@ func TestCompilePackagesAwaitsOverrideAsyncFunctions(t *testing.T) {
488
488
  }
489
489
  }
490
490
 
491
+ func TestCompilePackagesAwaitsIOFSStatOverride(t *testing.T) {
492
+ moduleDir := writePackageGraphFixture(t, map[string]string{
493
+ "go.mod": "module example.test/iofsstat\n\ngo 1.25.3\n",
494
+ "main.go": strings.Join([]string{
495
+ "package main",
496
+ "import \"io/fs\"",
497
+ "type memFS struct{}",
498
+ "func (memFS) Open(name string) (fs.File, error) { return nil, fs.ErrNotExist }",
499
+ "func Use(fsys fs.FS) error {",
500
+ " _, err := fs.Stat(fsys, \"pkg\")",
501
+ " return err",
502
+ "}",
503
+ "func main() { _ = Use(memFS{}) }",
504
+ "",
505
+ }, "\n"),
506
+ })
507
+ out := filepath.Join(t.TempDir(), "out")
508
+ comp, err := NewCompiler(&Config{
509
+ Dir: moduleDir,
510
+ OutputPath: out,
511
+ AllDependencies: true,
512
+ }, nil, nil)
513
+ if err != nil {
514
+ t.Fatal(err.Error())
515
+ }
516
+
517
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
518
+ t.Fatal(err.Error())
519
+ }
520
+ content, err := os.ReadFile(filepath.Join(out, "@goscript", "example.test", "iofsstat", "main.gs.ts"))
521
+ if err != nil {
522
+ t.Fatal(err.Error())
523
+ }
524
+ text := string(content)
525
+ if !strings.Contains(text, "export async function Use(") {
526
+ t.Fatalf("fs.Stat caller was not marked async:\n%s", text)
527
+ }
528
+ if !strings.Contains(text, "await fs.Stat(") {
529
+ t.Fatalf("io/fs Stat override call was not awaited:\n%s", text)
530
+ }
531
+ }
532
+
533
+ func TestCompilePackagesAwaitsIOFSLstatOverride(t *testing.T) {
534
+ moduleDir := writePackageGraphFixture(t, map[string]string{
535
+ "go.mod": "module example.test/iofslstat\n\ngo 1.25.3\n",
536
+ "main.go": strings.Join([]string{
537
+ "package main",
538
+ "import \"io/fs\"",
539
+ "type memFS struct{}",
540
+ "func (memFS) Open(name string) (fs.File, error) { return nil, fs.ErrNotExist }",
541
+ "func Use(fsys fs.FS) error {",
542
+ " _, err := fs.Lstat(fsys, \"pkg\")",
543
+ " return err",
544
+ "}",
545
+ "func main() { _ = Use(memFS{}) }",
546
+ "",
547
+ }, "\n"),
548
+ })
549
+ out := filepath.Join(t.TempDir(), "out")
550
+ comp, err := NewCompiler(&Config{
551
+ Dir: moduleDir,
552
+ OutputPath: out,
553
+ AllDependencies: true,
554
+ }, nil, nil)
555
+ if err != nil {
556
+ t.Fatal(err.Error())
557
+ }
558
+
559
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
560
+ t.Fatal(err.Error())
561
+ }
562
+ content, err := os.ReadFile(filepath.Join(out, "@goscript", "example.test", "iofslstat", "main.gs.ts"))
563
+ if err != nil {
564
+ t.Fatal(err.Error())
565
+ }
566
+ text := string(content)
567
+ if !strings.Contains(text, "export async function Use(") {
568
+ t.Fatalf("fs.Lstat caller was not marked async:\n%s", text)
569
+ }
570
+ if !strings.Contains(text, "await fs.Lstat(") {
571
+ t.Fatalf("io/fs Lstat override call was not awaited:\n%s", text)
572
+ }
573
+ }
574
+
575
+ func TestCompilePackagesAwaitsIOFSSubOverride(t *testing.T) {
576
+ moduleDir := writePackageGraphFixture(t, map[string]string{
577
+ "go.mod": "module example.test/iofssub\n\ngo 1.25.3\n",
578
+ "main.go": strings.Join([]string{
579
+ "package main",
580
+ "import \"io/fs\"",
581
+ "type memFS struct{}",
582
+ "func (memFS) Open(name string) (fs.File, error) { return nil, fs.ErrNotExist }",
583
+ "func Use(fsys fs.FS) (fs.FS, error) {",
584
+ " return fs.Sub(fsys, \"pkg\")",
585
+ "}",
586
+ "func main() { _, _ = Use(memFS{}) }",
587
+ "",
588
+ }, "\n"),
589
+ })
590
+ out := filepath.Join(t.TempDir(), "out")
591
+ comp, err := NewCompiler(&Config{
592
+ Dir: moduleDir,
593
+ OutputPath: out,
594
+ AllDependencies: true,
595
+ }, nil, nil)
596
+ if err != nil {
597
+ t.Fatal(err.Error())
598
+ }
599
+
600
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
601
+ t.Fatal(err.Error())
602
+ }
603
+ content, err := os.ReadFile(filepath.Join(out, "@goscript", "example.test", "iofssub", "main.gs.ts"))
604
+ if err != nil {
605
+ t.Fatal(err.Error())
606
+ }
607
+ text := string(content)
608
+ if !strings.Contains(text, "export async function Use(") {
609
+ t.Fatalf("fs.Sub caller was not marked async:\n%s", text)
610
+ }
611
+ if !strings.Contains(text, "return await fs.Sub(") {
612
+ t.Fatalf("io/fs Sub override call was not awaited:\n%s", text)
613
+ }
614
+ }
615
+
491
616
  func TestCompilePackagesAwaitsAsyncSlicesSortFuncComparator(t *testing.T) {
492
617
  moduleDir := writePackageGraphFixture(t, map[string]string{
493
618
  "go.mod": "module example.test/slicesasyncsort\n\ngo 1.25.3\n",
@@ -5,6 +5,7 @@ import (
5
5
  "go/ast"
6
6
  "os"
7
7
  "slices"
8
+ "strconv"
8
9
  "strings"
9
10
 
10
11
  "golang.org/x/tools/go/packages"
@@ -163,6 +164,9 @@ func (o *PackageGraphOwner) Load(ctx context.Context, req *CompileRequest) (*Pac
163
164
  Message: "package graph did not contain any package nodes",
164
165
  })
165
166
  }
167
+ if len(req.PackageBlocklist) != 0 {
168
+ diagnostics = append(diagnostics, packageBlocklistDiagnostics(graph, req.PackageBlocklist)...)
169
+ }
166
170
  return graph, diagnostics
167
171
  }
168
172
 
@@ -245,6 +249,94 @@ func newPackageGraphNode(pkg *packages.Package, requested bool, overrideFacts *O
245
249
  }
246
250
  }
247
251
 
252
+ func packageBlocklistDiagnostics(graph *PackageGraph, blocklist []string) []Diagnostic {
253
+ chain := packageBlocklistChain(graph, blocklist)
254
+ if len(chain) == 0 {
255
+ return nil
256
+ }
257
+ blocked := chain[len(chain)-1]
258
+ return []Diagnostic{{
259
+ Severity: DiagnosticSeverityError,
260
+ Code: "goscript/package-graph:blocklisted-package",
261
+ Message: "package graph contains blocklisted package " + strconv.Quote(blocked),
262
+ Detail: "import chain: " + strings.Join(chain, " -> "),
263
+ }}
264
+ }
265
+
266
+ func packageBlocklistChain(graph *PackageGraph, blocklist []string) []string {
267
+ if graph == nil || len(graph.RequestedPackagePaths) == 0 {
268
+ return nil
269
+ }
270
+
271
+ roots := slices.Clone(graph.RequestedPackagePaths)
272
+ slices.Sort(roots)
273
+
274
+ type queueEntry struct {
275
+ path string
276
+ chain []string
277
+ }
278
+ queue := make([]queueEntry, 0, len(roots))
279
+ seen := make(map[string]bool)
280
+ for _, root := range roots {
281
+ if graph.NodesByPackagePath[root] == nil || seen[root] {
282
+ continue
283
+ }
284
+ seen[root] = true
285
+ queue = append(queue, queueEntry{
286
+ path: root,
287
+ chain: []string{root},
288
+ })
289
+ }
290
+
291
+ for len(queue) != 0 {
292
+ entry := queue[0]
293
+ queue = queue[1:]
294
+ if packagePathBlocklisted(entry.path, blocklist) {
295
+ return entry.chain
296
+ }
297
+
298
+ node := graph.NodesByPackagePath[entry.path]
299
+ if node == nil {
300
+ continue
301
+ }
302
+ // Override candidates are compiled from their GoScript override, whose
303
+ // TypeScript imports replace the native Go imports. collect prunes their
304
+ // native dependencies from the graph, so the blocklist walk must treat
305
+ // them as leaves; otherwise an overridden package (for example a
306
+ // reflect-free encoding/json override) would falsely chain to a
307
+ // blocklisted package it no longer imports in the compiled output.
308
+ if node.OverrideCandidate {
309
+ continue
310
+ }
311
+ imports := slices.Clone(node.Imports)
312
+ slices.Sort(imports)
313
+ for _, importPath := range imports {
314
+ if graph.NodesByPackagePath[importPath] == nil || seen[importPath] {
315
+ continue
316
+ }
317
+ seen[importPath] = true
318
+ nextChain := slices.Clone(entry.chain)
319
+ nextChain = append(nextChain, importPath)
320
+ queue = append(queue, queueEntry{
321
+ path: importPath,
322
+ chain: nextChain,
323
+ })
324
+ }
325
+ }
326
+ return nil
327
+ }
328
+
329
+ func packagePathBlocklisted(path string, blocklist []string) bool {
330
+ for _, blocked := range blocklist {
331
+ // Match the package exactly or any subpackage, on a path-segment
332
+ // boundary so "crypto" blocks "crypto/ecdsa" but not "cryptobyte".
333
+ if path == blocked || strings.HasPrefix(path, blocked+"/") {
334
+ return true
335
+ }
336
+ }
337
+ return false
338
+ }
339
+
248
340
  func normalizePackageFileOrder(pkg *packages.Package) {
249
341
  if pkg == nil {
250
342
  return
@@ -266,6 +266,119 @@ func TestPackageGraphLoadsLocalReplacement(t *testing.T) {
266
266
  }
267
267
  }
268
268
 
269
+ func TestPackageBlocklistAllowsCleanFixture(t *testing.T) {
270
+ moduleDir := writePackageGraphFixture(t, map[string]string{
271
+ "go.mod": "module example.test/blockclean\n\ngo 1.25.3\n",
272
+ "main.go": "package blockclean\nimport \"example.test/blockclean/dep\"\nfunc Value() int { return dep.Value() }\n",
273
+ "dep/dep.go": "package dep\nfunc Value() int { return 1 }\n",
274
+ "other/doc.go": "package other\n",
275
+ })
276
+ comp, err := NewCompiler(&Config{
277
+ Dir: moduleDir,
278
+ OutputPath: filepath.Join(t.TempDir(), "out"),
279
+ AllDependencies: true,
280
+ PackageBlocklist: []string{"example.test/blockclean/other"},
281
+ }, nil, nil)
282
+ if err != nil {
283
+ t.Fatal(err.Error())
284
+ }
285
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
286
+ t.Fatalf("compile with clean blocklist failed: %v", err)
287
+ }
288
+ }
289
+
290
+ func TestPackageBlocklistReportsShortestImportChain(t *testing.T) {
291
+ moduleDir := writePackageGraphFixture(t, map[string]string{
292
+ "go.mod": "module example.test/blockchain\n\ngo 1.25.3\n",
293
+ "main.go": "package blockchain\nimport \"example.test/blockchain/dep\"\nfunc Value() int { return dep.Value() }\n",
294
+ "dep/dep.go": "package dep\nimport \"example.test/blockchain/mid/blocked\"\nfunc Value() int { return blocked.Value() }\n",
295
+ "mid/leaf.go": "package mid\nimport \"example.test/blockchain/mid/blocked\"\nfunc Leaf() int { return blocked.Value() }\n",
296
+ "mid/blocked/blocked.go": "package blocked\nfunc Value() int { return 1 }\n",
297
+ })
298
+ comp, err := NewCompiler(&Config{
299
+ Dir: moduleDir,
300
+ OutputPath: filepath.Join(t.TempDir(), "out"),
301
+ AllDependencies: true,
302
+ PackageBlocklist: []string{"example.test/blockchain/mid/blocked"},
303
+ }, nil, nil)
304
+ if err != nil {
305
+ t.Fatal(err.Error())
306
+ }
307
+ _, err = comp.CompilePackages(context.Background(), ".")
308
+ if err == nil {
309
+ t.Fatal("expected blocklisted package diagnostic")
310
+ }
311
+ text := err.Error()
312
+ if !strings.Contains(text, "goscript/package-graph:blocklisted-package") {
313
+ t.Fatalf("expected blocklist diagnostic, got %q", text)
314
+ }
315
+ if !strings.Contains(text, `package graph contains blocklisted package "example.test/blockchain/mid/blocked"`) {
316
+ t.Fatalf("expected blocklisted package name, got %q", text)
317
+ }
318
+ expected := "example.test/blockchain -> example.test/blockchain/dep -> example.test/blockchain/mid/blocked"
319
+ if !strings.Contains(text, expected) {
320
+ t.Fatalf("expected shortest import chain %q, got %q", expected, text)
321
+ }
322
+ }
323
+
324
+ func TestPackageBlocklistIgnoresOverrideCandidateImports(t *testing.T) {
325
+ moduleDir := writePackageGraphFixture(t, map[string]string{
326
+ "go.mod": strings.Join([]string{
327
+ "module example.test/blockoverride",
328
+ "",
329
+ "go 1.25.3",
330
+ "",
331
+ "require example.test/over v0.0.0",
332
+ "replace example.test/over => ./over",
333
+ "",
334
+ }, "\n"),
335
+ "main.go": "package blockoverride\nimport (\n\t\"example.test/over\"\n\t\"example.test/blockoverride/dep\"\n)\nfunc Value() int { return over.Value() + dep.Value() }\n",
336
+ "dep/dep.go": "package dep\nimport \"example.test/blockoverride/mid\"\nfunc Value() int { return mid.Value() }\n",
337
+ "mid/mid.go": "package mid\nimport \"example.test/blockoverride/mid/blocked\"\nfunc Value() int { return blocked.Value() }\n",
338
+ "mid/blocked/blocked.go": "package blocked\nfunc Value() int { return 1 }\n",
339
+ "over/go.mod": strings.Join([]string{
340
+ "module example.test/over",
341
+ "",
342
+ "go 1.25.3",
343
+ "",
344
+ "require example.test/blockoverride v0.0.0",
345
+ "replace example.test/blockoverride => ../",
346
+ "",
347
+ }, "\n"),
348
+ "over/over.go": "package over\nimport \"example.test/blockoverride/mid/blocked\"\nfunc Value() int { return blocked.Value() }\n",
349
+ })
350
+ overrideDir := filepath.Join(t.TempDir(), "gs")
351
+ writeFixtureFile(t, overrideDir, "example.test/over/index.ts", "export function Value(): number { return 0 }\n")
352
+
353
+ overrideOwner := NewOverrideRegistryOwner(overrideDir)
354
+ req := &CompileRequest{
355
+ Patterns: []string{"."},
356
+ Dir: moduleDir,
357
+ OutputPath: filepath.Join(t.TempDir(), "out"),
358
+ DependencyMode: DependencyModeAll,
359
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
360
+ PackageBlocklist: []string{"example.test/blockoverride/mid/blocked"},
361
+ }
362
+ graph, diagnostics := NewPackageGraphOwner(overrideOwner).Load(context.Background(), req)
363
+ if diagnosticsHaveErrors(diagnostics) {
364
+ chain := packageBlocklistChain(graph, req.PackageBlocklist)
365
+ if slices.Contains(chain, "example.test/over") {
366
+ t.Fatalf("blocklist chain routed through override candidate: %v", chain)
367
+ }
368
+ expected := []string{
369
+ "example.test/blockoverride",
370
+ "example.test/blockoverride/dep",
371
+ "example.test/blockoverride/mid",
372
+ "example.test/blockoverride/mid/blocked",
373
+ }
374
+ if !slices.Equal(chain, expected) {
375
+ t.Fatalf("expected real import chain %v, got %v", expected, chain)
376
+ }
377
+ } else {
378
+ t.Fatal("expected blocklisted package diagnostic via the real import path")
379
+ }
380
+ }
381
+
269
382
  func TestPackageGraphDetectsOverrideCandidates(t *testing.T) {
270
383
  moduleDir := writePackageGraphFixture(t, map[string]string{
271
384
  "go.mod": "module example.test/override\n\ngo 1.25.3\n",
@@ -5,8 +5,8 @@ import (
5
5
  "slices"
6
6
  "strings"
7
7
 
8
- gs "github.com/aperturerobotics/goscript"
9
8
  "github.com/pkg/errors"
9
+ gs "github.com/s4wave/goscript"
10
10
  )
11
11
 
12
12
  // RuntimeHelperCategory names a runtime helper family owned by the contract.
@@ -756,6 +756,15 @@ func (o *SemanticModelOwner) collectFunctionFacts(
756
756
  if typed.Op == token.ARROW {
757
757
  markFunctionAsync(semFn, "channel-receive")
758
758
  }
759
+ case *ast.RangeStmt:
760
+ if signatureForType(pkg.TypesInfo.TypeOf(typed.X)) != nil {
761
+ if called := calledFunction(pkg, typed.X); called != nil {
762
+ semFn.calls[functionOriginOrSelf(called)] = true
763
+ }
764
+ if rangeFunctionExprNeedsAwait(model, pkg, overrideFacts, typed.X) {
765
+ markFunctionAsync(semFn, "range-function")
766
+ }
767
+ }
759
768
  case *ast.CallExpr:
760
769
  if called := calledFunction(pkg, typed.Fun); called != nil {
761
770
  semFn.calls[functionOriginOrSelf(called)] = true
@@ -785,6 +794,29 @@ func (o *SemanticModelOwner) collectFunctionFacts(
785
794
  return diagnostics
786
795
  }
787
796
 
797
+ func rangeFunctionExprNeedsAwait(
798
+ model *SemanticModel,
799
+ pkg *packages.Package,
800
+ overrideFacts *OverrideFacts,
801
+ expr ast.Expr,
802
+ ) bool {
803
+ if model == nil || pkg == nil || signatureForType(pkg.TypesInfo.TypeOf(expr)) == nil {
804
+ return false
805
+ }
806
+ if called := calledFunction(pkg, expr); called != nil {
807
+ if semFn := semanticFunctionFor(model, called); semFn != nil && semFn.async {
808
+ return true
809
+ }
810
+ if called.Pkg() != nil && overrideFacts.IsFunctionAsync(called.Pkg().Path(), called.Name()) {
811
+ return true
812
+ }
813
+ }
814
+ if overrideFacts.IsMethodAsync(overrideCallPackage(pkg, expr), overrideCallMethod(pkg, expr)) {
815
+ return true
816
+ }
817
+ return callUsesFunctionValue(pkg, expr)
818
+ }
819
+
788
820
  func recordImmediateFuncLitAsyncFacts(
789
821
  model *SemanticModel,
790
822
  pkg *packages.Package,