goscript 0.2.4 → 0.2.6

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 (117) 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 +1238 -194
  19. package/compiler/lowering_bench_test.go +4 -0
  20. package/compiler/override-facts.go +1 -1
  21. package/compiler/package-graph.go +92 -0
  22. package/compiler/package-graph_test.go +113 -0
  23. package/compiler/runtime-contract.go +1 -1
  24. package/compiler/semantic-model.go +32 -0
  25. package/compiler/skeleton_test.go +241 -15
  26. package/compiler/wasm/compile.go +1 -1
  27. package/compiler/wasm/compile_test.go +1 -1
  28. package/dist/compiler/index.d.ts +4 -0
  29. package/dist/compiler/index.js +26 -15
  30. package/dist/compiler/index.js.map +1 -1
  31. package/dist/gs/database/sql/driver/index.d.ts +165 -0
  32. package/dist/gs/database/sql/driver/index.js +432 -0
  33. package/dist/gs/database/sql/driver/index.js.map +1 -0
  34. package/dist/gs/encoding/binary/index.d.ts +71 -0
  35. package/dist/gs/encoding/binary/index.js +778 -0
  36. package/dist/gs/encoding/binary/index.js.map +1 -0
  37. package/dist/gs/fmt/fmt.js +156 -57
  38. package/dist/gs/fmt/fmt.js.map +1 -1
  39. package/dist/gs/github.com/klauspost/cpuid/v2/index.d.ts +11 -0
  40. package/dist/gs/github.com/klauspost/cpuid/v2/index.js +28 -0
  41. package/dist/gs/github.com/klauspost/cpuid/v2/index.js.map +1 -0
  42. package/dist/gs/github.com/pkg/errors/errors.d.ts +0 -2
  43. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  44. package/dist/gs/github.com/pkg/errors/index.d.ts +2 -1
  45. package/dist/gs/github.com/pkg/errors/index.js +1 -1
  46. package/dist/gs/github.com/pkg/errors/index.js.map +1 -1
  47. package/dist/gs/github.com/pkg/errors/stack.d.ts +8 -19
  48. package/dist/gs/github.com/pkg/errors/stack.js +26 -61
  49. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  50. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.d.ts +19 -0
  51. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js +25 -0
  52. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js.map +1 -0
  53. package/dist/gs/golang.org/x/crypto/cryptobyte/index.d.ts +104 -0
  54. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1107 -0
  55. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -0
  56. package/dist/gs/golang.org/x/crypto/internal/alias/index.d.ts +3 -0
  57. package/dist/gs/golang.org/x/crypto/internal/alias/index.js +39 -0
  58. package/dist/gs/golang.org/x/crypto/internal/alias/index.js.map +1 -0
  59. package/dist/gs/runtime/runtime.d.ts +6 -1
  60. package/dist/gs/runtime/runtime.js +15 -8
  61. package/dist/gs/runtime/runtime.js.map +1 -1
  62. package/dist/gs/runtime/trace/index.d.ts +8 -5
  63. package/dist/gs/runtime/trace/index.js +324 -23
  64. package/dist/gs/runtime/trace/index.js.map +1 -1
  65. package/dist/gs/slices/slices.d.ts +2 -1
  66. package/dist/gs/slices/slices.js +9 -3
  67. package/dist/gs/slices/slices.js.map +1 -1
  68. package/dist/gs/sort/search.gs.d.ts +3 -1
  69. package/dist/gs/sort/search.gs.js +18 -53
  70. package/dist/gs/sort/search.gs.js.map +1 -1
  71. package/dist/gs/sync/sync.d.ts +1 -1
  72. package/dist/gs/sync/sync.js +3 -0
  73. package/dist/gs/sync/sync.js.map +1 -1
  74. package/dist/gs/time/time.d.ts +22 -29
  75. package/dist/gs/time/time.js +111 -32
  76. package/dist/gs/time/time.js.map +1 -1
  77. package/dist/gs/unsafe/unsafe.d.ts +3 -2
  78. package/dist/gs/unsafe/unsafe.js.map +1 -1
  79. package/go.mod +7 -5
  80. package/go.sum +12 -26
  81. package/gs/database/sql/driver/index.test.ts +88 -0
  82. package/gs/database/sql/driver/index.ts +675 -0
  83. package/gs/database/sql/driver/meta.json +3 -0
  84. package/gs/database/sql/driver/parity.json +144 -0
  85. package/gs/encoding/binary/index.test.ts +239 -0
  86. package/gs/encoding/binary/index.ts +999 -0
  87. package/gs/encoding/binary/meta.json +9 -0
  88. package/gs/encoding/binary/parity.json +72 -0
  89. package/gs/fmt/fmt.test.ts +28 -0
  90. package/gs/fmt/fmt.ts +198 -61
  91. package/gs/fmt/meta.json +2 -1
  92. package/gs/github.com/klauspost/cpuid/v2/index.ts +38 -0
  93. package/gs/github.com/klauspost/cpuid/v2/meta.json +3 -0
  94. package/gs/github.com/pkg/errors/errors.ts +1 -2
  95. package/gs/github.com/pkg/errors/index.ts +2 -1
  96. package/gs/github.com/pkg/errors/stack.ts +34 -62
  97. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.test.ts +19 -0
  98. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.ts +29 -0
  99. package/gs/golang.org/x/crypto/cryptobyte/index.test.ts +255 -0
  100. package/gs/golang.org/x/crypto/cryptobyte/index.ts +1441 -0
  101. package/gs/golang.org/x/crypto/cryptobyte/meta.json +3 -0
  102. package/gs/golang.org/x/crypto/internal/alias/index.test.ts +40 -0
  103. package/gs/golang.org/x/crypto/internal/alias/index.ts +40 -0
  104. package/gs/runtime/runtime.test.ts +16 -0
  105. package/gs/runtime/runtime.ts +17 -9
  106. package/gs/runtime/trace/index.test.ts +113 -14
  107. package/gs/runtime/trace/index.ts +384 -34
  108. package/gs/runtime/trace/meta.json +1 -0
  109. package/gs/slices/slices.test.ts +24 -1
  110. package/gs/slices/slices.ts +14 -4
  111. package/gs/sort/meta.json +1 -0
  112. package/gs/sort/search.gs.ts +20 -5
  113. package/gs/sync/sync.ts +4 -1
  114. package/gs/time/time.test.ts +79 -2
  115. package/gs/time/time.ts +133 -33
  116. package/gs/unsafe/unsafe.ts +4 -2
  117. package/package.json +3 -3
@@ -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.
@@ -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,
@@ -789,6 +789,61 @@ func TestCompilePackagesReadsShadowedVarRefStructFieldsOnce(t *testing.T) {
789
789
  }
790
790
  }
791
791
 
792
+ func TestCompilePackagesUnwrapsAnonymousStructPointerFieldReceivers(t *testing.T) {
793
+ moduleDir := writePackageGraphFixture(t, map[string]string{
794
+ "go.mod": "module example.test/anonstructptr\n\ngo 1.25.3\n",
795
+ "main.go": strings.Join([]string{
796
+ "package anonstructptr",
797
+ "func mergeCore(dst, src *struct {",
798
+ " IsBare bool",
799
+ " Worktree string",
800
+ "}) {",
801
+ " if src.IsBare {",
802
+ " dst.IsBare = true",
803
+ " }",
804
+ " if src.Worktree != \"\" {",
805
+ " dst.Worktree = src.Worktree",
806
+ " }",
807
+ "}",
808
+ "",
809
+ }, "\n"),
810
+ })
811
+ outputDir := filepath.Join(t.TempDir(), "output")
812
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
813
+ if err != nil {
814
+ t.Fatal(err.Error())
815
+ }
816
+
817
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
818
+ t.Fatal(err.Error())
819
+ }
820
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "anonstructptr", "main.gs.ts")
821
+ content, err := os.ReadFile(outputFile)
822
+ if err != nil {
823
+ t.Fatal(err.Error())
824
+ }
825
+ text := string(content)
826
+ for _, want := range []string{
827
+ `$.pointerValue<{"IsBare": boolean, "Worktree": string}>(src).IsBare`,
828
+ `$.pointerValue<{"IsBare": boolean, "Worktree": string}>(dst).IsBare = true`,
829
+ `$.pointerValue<{"IsBare": boolean, "Worktree": string}>(dst).Worktree = $.pointerValue<{"IsBare": boolean, "Worktree": string}>(src).Worktree`,
830
+ } {
831
+ if !strings.Contains(text, want) {
832
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
833
+ }
834
+ }
835
+ for _, bad := range []string{
836
+ "src.IsBare",
837
+ "dst.IsBare",
838
+ "src.Worktree",
839
+ "dst.Worktree",
840
+ } {
841
+ if strings.Contains(text, bad) {
842
+ t.Fatalf("anonymous struct pointer field receiver stayed wrapped at %q:\n%s", bad, text)
843
+ }
844
+ }
845
+ }
846
+
792
847
  func TestCompilePackagesWrapsChannelSendInterfaceValues(t *testing.T) {
793
848
  moduleDir := writePackageGraphFixture(t, map[string]string{
794
849
  "go.mod": "module example.test/chansendiface\n\ngo 1.25.3\n",
@@ -1436,7 +1491,7 @@ func TestCompilePackagesEmitsSideEffectImportsForInterfaceRegistry(t *testing.T)
1436
1491
  "import * as dep from \"@goscript/example.test/interface-registry/dep/index.js\"",
1437
1492
  "import \"@goscript/example.test/interface-registry/dep/index.js\"",
1438
1493
  "import type * as __goscript_local from \"./local.gs.ts\"",
1439
- "case $.typeAssert<Exclude<__goscript_local.Local, null>>(__goscriptTypeSwitchValue, \"main.Local\").ok",
1494
+ "case $.typeAssert<__goscript_local.Local | null>(__goscriptTypeSwitchValue, \"main.Local\").ok",
1440
1495
  "$.typeAssertTuple<dep.Remote | null>(v, \"dep.Remote\")",
1441
1496
  } {
1442
1497
  if !strings.Contains(mainText, want) {
@@ -1546,6 +1601,56 @@ func TestCompilePackagesLowersUnsafeBytePointerArithmetic(t *testing.T) {
1546
1601
  }
1547
1602
  }
1548
1603
 
1604
+ func TestCompilePackagesLowersGMSUnsafeArrayPointerConversions(t *testing.T) {
1605
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1606
+ "go.mod": "module example.test/gmsunsafe\n\ngo 1.25.3\n",
1607
+ "main.go": strings.Join([]string{
1608
+ "package main",
1609
+ "import (",
1610
+ " \"reflect\"",
1611
+ " \"unsafe\"",
1612
+ ")",
1613
+ "func IntBytes(n int64) byte {",
1614
+ " mem := (*[8]byte)(unsafe.Pointer(&n))",
1615
+ " bytes := *mem",
1616
+ " return bytes[0]",
1617
+ "}",
1618
+ "func StringToBytes(str string) []byte {",
1619
+ " if len(str) == 0 {",
1620
+ " return []byte{}",
1621
+ " }",
1622
+ " return (*[0x7fff0000]byte)(unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&str)).Data))[:len(str):len(str)]",
1623
+ "}",
1624
+ "",
1625
+ }, "\n"),
1626
+ })
1627
+ outputDir := filepath.Join(t.TempDir(), "output")
1628
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1629
+ if err != nil {
1630
+ t.Fatal(err.Error())
1631
+ }
1632
+
1633
+ _, err = comp.CompilePackages(context.Background(), ".")
1634
+ if err != nil {
1635
+ t.Fatal(err.Error())
1636
+ }
1637
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "gmsunsafe", "main.gs.ts")
1638
+ content, err := os.ReadFile(outputFile)
1639
+ if err != nil {
1640
+ t.Fatal(err.Error())
1641
+ }
1642
+ text := string(content)
1643
+ if !strings.Contains(text, "$.arrayPointerFromIndexRef<number>($.indexRef([n.value], 0), 8, 8, 1)") {
1644
+ t.Fatalf("missing scalar unsafe array pointer conversion:\n%s", text)
1645
+ }
1646
+ if !strings.Contains(text, "$.arrayPointerFromIndexRef<number>($.indexRef($.stringToBytes(str.value), 0), 2147418112, 1, 1)") {
1647
+ t.Fatalf("missing string data unsafe array pointer conversion:\n%s", text)
1648
+ }
1649
+ if strings.Contains(text, "unsafe.Pointer(&n) as any") || strings.Contains(text, "StringHeader)(unsafe.Pointer(&str)).Data) as any") {
1650
+ t.Fatalf("unsafe array pointer conversion fell back to any cast:\n%s", text)
1651
+ }
1652
+ }
1653
+
1549
1654
  func TestCompilePackagesEmitsStructMethodsAndPointerAssertions(t *testing.T) {
1550
1655
  moduleDir := writePackageGraphFixture(t, map[string]string{
1551
1656
  "go.mod": "module example.test/structs\n\ngo 1.25.3\n",
@@ -1669,6 +1774,124 @@ func TestCompilePackagesEscapesReservedTypeNames(t *testing.T) {
1669
1774
  }
1670
1775
  }
1671
1776
 
1777
+ func TestCompilePackagesEscapesStrictModeRestrictedIdentifiers(t *testing.T) {
1778
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1779
+ "go.mod": "module example.test/strictidents\n\ngo 1.25.3\n",
1780
+ "main.go": strings.Join([]string{
1781
+ "package main",
1782
+ "func choose(arguments int) (eval int) {",
1783
+ " eval = arguments + 1",
1784
+ " arguments = eval + arguments",
1785
+ " return",
1786
+ "}",
1787
+ "func main() {",
1788
+ " eval := choose(1)",
1789
+ " arguments := eval + 1",
1790
+ " println(eval, arguments)",
1791
+ "}",
1792
+ "",
1793
+ }, "\n"),
1794
+ })
1795
+ outputDir := filepath.Join(t.TempDir(), "output")
1796
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1797
+ if err != nil {
1798
+ t.Fatal(err.Error())
1799
+ }
1800
+
1801
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1802
+ t.Fatal(err.Error())
1803
+ }
1804
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "strictidents", "main.gs.ts")
1805
+ content, err := os.ReadFile(outputFile)
1806
+ if err != nil {
1807
+ t.Fatal(err.Error())
1808
+ }
1809
+ text := string(content)
1810
+ for _, want := range []string{
1811
+ "function choose(_arguments: number): number",
1812
+ "let _eval: number = 0",
1813
+ "_eval = _arguments + 1",
1814
+ "_arguments = _eval + _arguments",
1815
+ "let _eval = choose(1)",
1816
+ "let _arguments = _eval + 1",
1817
+ } {
1818
+ if !strings.Contains(text, want) {
1819
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1820
+ }
1821
+ }
1822
+ for _, bad := range []string{
1823
+ "let eval",
1824
+ " eval =",
1825
+ "function choose(arguments",
1826
+ "let arguments",
1827
+ " arguments =",
1828
+ } {
1829
+ if strings.Contains(text, bad) {
1830
+ t.Fatalf("strict-mode restricted identifier was not escaped, found %q:\n%s", bad, text)
1831
+ }
1832
+ }
1833
+ }
1834
+
1835
+ func TestCompilePackagesDoesNotEmitHiddenEmbeddedMethodOverField(t *testing.T) {
1836
+ moduleDir := writePackageGraphFixture(t, map[string]string{
1837
+ "go.mod": "module example.test/hiddenembeddedmethod\n\ngo 1.25.3\n",
1838
+ "main.go": strings.Join([]string{
1839
+ "package main",
1840
+ "type embedded struct{}",
1841
+ "func (embedded) Database() string {",
1842
+ " return \"method\"",
1843
+ "}",
1844
+ "type holder struct {",
1845
+ " Database string",
1846
+ " embedded",
1847
+ "}",
1848
+ "func value(h holder) string {",
1849
+ " return h.Database",
1850
+ "}",
1851
+ "",
1852
+ }, "\n"),
1853
+ })
1854
+ outputDir := filepath.Join(t.TempDir(), "output")
1855
+ comp, err := NewCompiler(&Config{Dir: moduleDir, OutputPath: outputDir}, nil, nil)
1856
+ if err != nil {
1857
+ t.Fatal(err.Error())
1858
+ }
1859
+
1860
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
1861
+ t.Fatal(err.Error())
1862
+ }
1863
+ outputFile := filepath.Join(outputDir, "@goscript", "example.test", "hiddenembeddedmethod", "main.gs.ts")
1864
+ content, err := os.ReadFile(outputFile)
1865
+ if err != nil {
1866
+ t.Fatal(err.Error())
1867
+ }
1868
+ text := string(content)
1869
+ for _, want := range []string{
1870
+ "public get Database(): string",
1871
+ "public set Database(value: string)",
1872
+ "return h.Database",
1873
+ } {
1874
+ if !strings.Contains(text, want) {
1875
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
1876
+ }
1877
+ }
1878
+ if got := strings.Count(text, "public Database("); got != 1 {
1879
+ t.Fatalf("expected only embedded.Database method, got %d declarations:\n%s", got, text)
1880
+ }
1881
+ holderStart := strings.Index(text, "export class holder")
1882
+ embeddedStart := strings.Index(text, "export class embedded")
1883
+ if holderStart < 0 || embeddedStart < 0 {
1884
+ t.Fatalf("missing holder or embedded class:\n%s", text)
1885
+ }
1886
+ holderText := text[holderStart:]
1887
+ if embeddedStart > holderStart {
1888
+ holderText = text[holderStart:embeddedStart]
1889
+ }
1890
+ if strings.Contains(holderText, "public Database(") {
1891
+ t.Fatalf("hidden embedded method was emitted on holder:\n%s", holderText)
1892
+ }
1893
+ }
1894
+
1672
1895
  func TestCompilePackagesAvoidsPointerMethodTypeNameShadow(t *testing.T) {
1673
1896
  moduleDir := writePackageGraphFixture(t, map[string]string{
1674
1897
  "go.mod": "module example.test/methodshadow\n\ngo 1.25.3\n",
@@ -1907,9 +2130,9 @@ func TestCompilePackagesEmitsArraySliceMapStringAndNamedMethods(t *testing.T) {
1907
2130
  "let literal: $.Slice<number> = $.arrayToSlice<number>([1, 2])",
1908
2131
  "literal = $.append(literal, 3)",
1909
2132
  "slice![0] = arr[1]",
1910
- "let m: Map<string, number> | null = $.makeMap<string, number>()",
2133
+ "let m: globalThis.Map<string, number> | null = $.makeMap<string, number>()",
1911
2134
  "$.mapSet(m, \"one\", 1)",
1912
- "let [value, ok] = $.mapGet(m, \"missing\", 0)",
2135
+ "let [value, ok] = $.mapGet<string, number, number>(m, \"missing\", 0)",
1913
2136
  "slice![0]",
1914
2137
  "literal![2]",
1915
2138
  "let list: $.VarRef<MySlice> = $.varRef(null as MySlice)",
@@ -2447,8 +2670,8 @@ func TestCompilePackagesEmitsInterfacesMethodValuesTypeSwitchesAndFunctionAssert
2447
2670
  "elemType: { kind: $.TypeKind.Struct, methods: [], fields: [{ name: \"Name\", key: \"Name\", type: { kind: $.TypeKind.Basic, name: \"string\" }",
2448
2671
  "let fn = __goscriptTuple",
2449
2672
  "switch (true)",
2450
- "case $.typeAssert<Exclude<ReadCloser, null>>(__goscriptTypeSwitchValue, \"main.ReadCloser\").ok",
2451
- "let v: Exclude<ReadCloser, null> = $.typeAssert<Exclude<ReadCloser, null>>(__goscriptTypeSwitchValue, \"main.ReadCloser\").value",
2673
+ "case $.typeAssert<ReadCloser | null>(__goscriptTypeSwitchValue, \"main.ReadCloser\").ok",
2674
+ "let v: ReadCloser | null = $.typeAssert<ReadCloser | null>(__goscriptTypeSwitchValue, \"main.ReadCloser\").value",
2452
2675
  } {
2453
2676
  if !strings.Contains(text, want) {
2454
2677
  t.Fatalf("missing %q in generated output:\n%s", want, text)
@@ -2541,8 +2764,8 @@ func TestCompilePackagesUsesNonNilInterfaceTypeSwitchCaseVarRefs(t *testing.T) {
2541
2764
  text := string(content)
2542
2765
  for _, want := range []string{
2543
2766
  "export type tcpConn = {",
2544
- "case $.typeAssert<Exclude<tcpConn, null>>(__goscriptTypeSwitchValue, \"main.tcpConn\").ok",
2545
- "let c: $.VarRef<Exclude<tcpConn, null>> = $.varRef($.typeAssert<Exclude<tcpConn, null>>(__goscriptTypeSwitchValue, \"main.tcpConn\").value)",
2767
+ "case $.typeAssert<tcpConn | null>(__goscriptTypeSwitchValue, \"main.tcpConn\").ok",
2768
+ "let c: $.VarRef<tcpConn | null> = $.varRef($.typeAssert<tcpConn | null>(__goscriptTypeSwitchValue, \"main.tcpConn\").value)",
2546
2769
  "$.pointerValue<Exclude<tcpConn, null>>(c.value).SyscallConn()",
2547
2770
  } {
2548
2771
  if !strings.Contains(text, want) {
@@ -3454,7 +3677,7 @@ func TestCompilePackagesEmitsAsyncChannelsSelectAndDefer(t *testing.T) {
3454
3677
  "await $.chanSend($.pointerValue<Worker>(w).ch, v)",
3455
3678
  "return await $.chanRecv($.pointerValue<Worker>(w).ch)",
3456
3679
  "await using __defer = new $.AsyncDisposableStack()",
3457
- "queueMicrotask(async () => { await ($.functionValue(async (): globalThis.Promise<void> => {",
3680
+ "queueMicrotask(async () => { await (async (): globalThis.Promise<void> => {",
3458
3681
  "$.selectStatement<any, void>([",
3459
3682
  "let v = __goscriptSelect1Result.value",
3460
3683
  "return $.selectVoidReturn()",
@@ -4755,7 +4978,7 @@ func TestCompilePackagesLowersMethodValuesWithFixedParameters(t *testing.T) {
4755
4978
  }
4756
4979
  }
4757
4980
 
4758
- func TestCompilePackagesLowersSyncOverrideCallbackWithoutAsyncWrapper(t *testing.T) {
4981
+ func TestCompilePackagesLowersSortSearchCallbackAsAsyncCompatible(t *testing.T) {
4759
4982
  moduleDir := writePackageGraphFixture(t, map[string]string{
4760
4983
  "go.mod": "module example.test/sync-callback\n\ngo 1.25.3\n",
4761
4984
  "main.go": strings.Join([]string{
@@ -4794,11 +5017,14 @@ func TestCompilePackagesLowersSyncOverrideCallbackWithoutAsyncWrapper(t *testing
4794
5017
  t.Fatal(err.Error())
4795
5018
  }
4796
5019
  text := string(content)
4797
- if strings.Contains(text, "$.functionValue(async (i: number)") {
4798
- t.Fatalf("sync override callback lowered as async:\n%s", text)
4799
- }
4800
- if !strings.Contains(text, "$.functionValue((i: number): boolean => {") {
4801
- t.Fatalf("sync override callback was not lowered as a synchronous function value:\n%s", text)
5020
+ for _, want := range []string{
5021
+ "return await sort.Search(",
5022
+ "$.functionValue(async (i: number): globalThis.Promise<boolean> => {",
5023
+ "let item = await $.pointerValue<Exclude<Items, null>>(items).Get(i)",
5024
+ } {
5025
+ if !strings.Contains(text, want) {
5026
+ t.Fatalf("missing %q in generated output:\n%s", want, text)
5027
+ }
4802
5028
  }
4803
5029
  }
4804
5030
 
@@ -5379,7 +5605,7 @@ func TestCompilePackagesLowersNamedStructConversionWithTypedAsyncFact(t *testing
5379
5605
  if !strings.Contains(text, "$.markAsStructValue(new Target({Value: __goscriptConvert") {
5380
5606
  t.Fatalf("missing typed named struct conversion target:\n%s", text)
5381
5607
  }
5382
- if !strings.Contains(text, "const __goscriptConvert1 = await ($.functionValue(async ") {
5608
+ if !strings.Contains(text, "const __goscriptConvert1 = await (async ") {
5383
5609
  t.Fatalf("missing async fact from function literal conversion source:\n%s", text)
5384
5610
  }
5385
5611
  }
@@ -2,7 +2,7 @@
2
2
  package wasm
3
3
 
4
4
  import (
5
- "github.com/aperturerobotics/goscript/compiler"
5
+ "github.com/s4wave/goscript/compiler"
6
6
  )
7
7
 
8
8
  // CompileSource compiles import-free browser source strings to TypeScript.
@@ -5,7 +5,7 @@ import (
5
5
  "strings"
6
6
  "testing"
7
7
 
8
- "github.com/aperturerobotics/goscript/compiler"
8
+ "github.com/s4wave/goscript/compiler"
9
9
  )
10
10
 
11
11
  func TestCompileSourceCompilesSingleFile(t *testing.T) {
@@ -8,6 +8,10 @@ export interface CompileConfig {
8
8
  output?: string;
9
9
  /** The working directory for the compiler. Defaults to the current working directory. */
10
10
  dir?: string;
11
+ /** Compile all transitive dependencies of the requested package. */
12
+ allDependencies?: boolean;
13
+ /** Go import paths to reject from the compiled package graph. */
14
+ packageBlocklist?: string[] | string;
11
15
  /** The path to the goscript executable. Defaults to `go run ./cmd/goscript`. */
12
16
  goscriptPath?: string;
13
17
  }