goscript 0.2.6 → 0.2.7

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 (264) hide show
  1. package/cmd/goscript/cmd-compile.go +7 -0
  2. package/cmd/goscript/cmd_compile_test.go +83 -0
  3. package/compiler/compile-request.go +3 -0
  4. package/compiler/compiler-cache.go +828 -0
  5. package/compiler/compiler-cache_test.go +705 -0
  6. package/compiler/config.go +2 -0
  7. package/compiler/index.test.ts +26 -1
  8. package/compiler/index.ts +5 -0
  9. package/compiler/lowered-program.go +31 -20
  10. package/compiler/lowering.go +349 -93
  11. package/compiler/lowering_bench_test.go +1 -0
  12. package/compiler/override-facts.go +309 -8
  13. package/compiler/override-parity-verifier.go +45 -1
  14. package/compiler/override-parity-verifier_test.go +100 -0
  15. package/compiler/override-registry_test.go +1 -0
  16. package/compiler/package-graph.go +40 -12
  17. package/compiler/package-graph_test.go +29 -0
  18. package/compiler/runtime-contract.go +8 -0
  19. package/compiler/service.go +98 -11
  20. package/compiler/skeleton_test.go +110 -14
  21. package/compiler/typescript-emitter.go +120 -23
  22. package/dist/compiler/index.d.ts +2 -0
  23. package/dist/compiler/index.js +3 -0
  24. package/dist/compiler/index.js.map +1 -1
  25. package/dist/gs/builtin/builtin.d.ts +24 -33
  26. package/dist/gs/builtin/builtin.js +54 -61
  27. package/dist/gs/builtin/builtin.js.map +1 -1
  28. package/dist/gs/builtin/hostio.d.ts +1 -0
  29. package/dist/gs/builtin/hostio.js +1 -1
  30. package/dist/gs/builtin/hostio.js.map +1 -1
  31. package/dist/gs/builtin/index.d.ts +1 -0
  32. package/dist/gs/builtin/index.js +1 -0
  33. package/dist/gs/builtin/index.js.map +1 -1
  34. package/dist/gs/builtin/panic.d.ts +18 -0
  35. package/dist/gs/builtin/panic.js +98 -0
  36. package/dist/gs/builtin/panic.js.map +1 -0
  37. package/dist/gs/builtin/slice.d.ts +10 -0
  38. package/dist/gs/builtin/slice.js +110 -53
  39. package/dist/gs/builtin/slice.js.map +1 -1
  40. package/dist/gs/builtin/type.js +15 -3
  41. package/dist/gs/builtin/type.js.map +1 -1
  42. package/dist/gs/builtin/varRef.d.ts +1 -1
  43. package/dist/gs/builtin/varRef.js +3 -2
  44. package/dist/gs/builtin/varRef.js.map +1 -1
  45. package/dist/gs/bytes/bytes.gs.js +51 -38
  46. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  47. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  48. package/dist/gs/bytes/reader.gs.js +6 -7
  49. package/dist/gs/bytes/reader.gs.js.map +1 -1
  50. package/dist/gs/cmp/index.d.ts +1 -1
  51. package/dist/gs/cmp/index.js +43 -10
  52. package/dist/gs/cmp/index.js.map +1 -1
  53. package/dist/gs/context/context.d.ts +2 -2
  54. package/dist/gs/context/context.js +1 -1
  55. package/dist/gs/context/context.js.map +1 -1
  56. package/dist/gs/embed/index.js +1 -1
  57. package/dist/gs/embed/index.js.map +1 -1
  58. package/dist/gs/encoding/binary/index.js +201 -8
  59. package/dist/gs/encoding/binary/index.js.map +1 -1
  60. package/dist/gs/encoding/json/index.d.ts +5 -0
  61. package/dist/gs/encoding/json/index.js +388 -25
  62. package/dist/gs/encoding/json/index.js.map +1 -1
  63. package/dist/gs/errors/errors.js +17 -24
  64. package/dist/gs/errors/errors.js.map +1 -1
  65. package/dist/gs/fmt/fmt.js +129 -35
  66. package/dist/gs/fmt/fmt.js.map +1 -1
  67. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1 -1
  68. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -1
  69. package/dist/gs/internal/bytealg/index.js +43 -8
  70. package/dist/gs/internal/bytealg/index.js.map +1 -1
  71. package/dist/gs/internal/byteorder/index.d.ts +2 -2
  72. package/dist/gs/internal/byteorder/index.js +2 -2
  73. package/dist/gs/internal/byteorder/index.js.map +1 -1
  74. package/dist/gs/io/fs/format.js +2 -2
  75. package/dist/gs/io/fs/format.js.map +1 -1
  76. package/dist/gs/io/fs/fs.d.ts +1 -1
  77. package/dist/gs/io/fs/fs.js +1 -1
  78. package/dist/gs/io/fs/fs.js.map +1 -1
  79. package/dist/gs/io/io.d.ts +21 -21
  80. package/dist/gs/io/io.js +49 -50
  81. package/dist/gs/io/io.js.map +1 -1
  82. package/dist/gs/math/bits/index.js +26 -8
  83. package/dist/gs/math/bits/index.js.map +1 -1
  84. package/dist/gs/math/copysign.gs.js +10 -17
  85. package/dist/gs/math/copysign.gs.js.map +1 -1
  86. package/dist/gs/math/pow.gs.js +5 -0
  87. package/dist/gs/math/pow.gs.js.map +1 -1
  88. package/dist/gs/math/signbit.gs.js +6 -2
  89. package/dist/gs/math/signbit.gs.js.map +1 -1
  90. package/dist/gs/mime/index.js +1 -0
  91. package/dist/gs/mime/index.js.map +1 -1
  92. package/dist/gs/net/http/index.d.ts +6 -6
  93. package/dist/gs/net/http/index.js +507 -43
  94. package/dist/gs/net/http/index.js.map +1 -1
  95. package/dist/gs/os/stat.gs.d.ts +2 -2
  96. package/dist/gs/os/types.gs.d.ts +1 -1
  97. package/dist/gs/os/types.gs.js +1 -1
  98. package/dist/gs/os/types.gs.js.map +1 -1
  99. package/dist/gs/os/types_js.gs.d.ts +1 -1
  100. package/dist/gs/os/types_js.gs.js +7 -7
  101. package/dist/gs/os/types_js.gs.js.map +1 -1
  102. package/dist/gs/os/types_unix.gs.d.ts +1 -1
  103. package/dist/gs/os/types_unix.gs.js +1 -1
  104. package/dist/gs/os/types_unix.gs.js.map +1 -1
  105. package/dist/gs/os/zero_copy_posix.gs.d.ts +1 -1
  106. package/dist/gs/os/zero_copy_posix.gs.js +1 -1
  107. package/dist/gs/os/zero_copy_posix.gs.js.map +1 -1
  108. package/dist/gs/path/filepath/match.js +8 -4
  109. package/dist/gs/path/filepath/match.js.map +1 -1
  110. package/dist/gs/path/filepath/path.js +216 -42
  111. package/dist/gs/path/filepath/path.js.map +1 -1
  112. package/dist/gs/path/match.js +6 -3
  113. package/dist/gs/path/match.js.map +1 -1
  114. package/dist/gs/reflect/type.d.ts +5 -4
  115. package/dist/gs/reflect/type.js +29 -11
  116. package/dist/gs/reflect/type.js.map +1 -1
  117. package/dist/gs/slices/slices.js +11 -11
  118. package/dist/gs/slices/slices.js.map +1 -1
  119. package/dist/gs/strconv/atof.gs.js +156 -43
  120. package/dist/gs/strconv/atof.gs.js.map +1 -1
  121. package/dist/gs/strconv/atoi.gs.d.ts +3 -2
  122. package/dist/gs/strconv/atoi.gs.js +86 -67
  123. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  124. package/dist/gs/strconv/ftoa.gs.js +73 -3
  125. package/dist/gs/strconv/ftoa.gs.js.map +1 -1
  126. package/dist/gs/strconv/itoa.gs.d.ts +4 -4
  127. package/dist/gs/strconv/itoa.gs.js +5 -4
  128. package/dist/gs/strconv/itoa.gs.js.map +1 -1
  129. package/dist/gs/strconv/quote.gs.d.ts +1 -1
  130. package/dist/gs/strconv/quote.gs.js +311 -103
  131. package/dist/gs/strconv/quote.gs.js.map +1 -1
  132. package/dist/gs/strings/reader.d.ts +1 -1
  133. package/dist/gs/strings/reader.js +8 -8
  134. package/dist/gs/strings/reader.js.map +1 -1
  135. package/dist/gs/strings/strings.js +87 -61
  136. package/dist/gs/strings/strings.js.map +1 -1
  137. package/dist/gs/sync/atomic/doc_64.gs.d.ts +14 -14
  138. package/dist/gs/sync/atomic/doc_64.gs.js +10 -10
  139. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  140. package/dist/gs/sync/atomic/type.gs.d.ts +22 -22
  141. package/dist/gs/sync/atomic/type.gs.js +4 -4
  142. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  143. package/dist/gs/sync/sync.js +50 -12
  144. package/dist/gs/sync/sync.js.map +1 -1
  145. package/dist/gs/syscall/fs.d.ts +6 -6
  146. package/dist/gs/syscall/fs.js +1 -1
  147. package/dist/gs/syscall/fs.js.map +1 -1
  148. package/dist/gs/time/time.d.ts +18 -18
  149. package/dist/gs/time/time.js +58 -55
  150. package/dist/gs/time/time.js.map +1 -1
  151. package/dist/gs/unicode/tables.d.ts +11 -0
  152. package/dist/gs/unicode/tables.js +635 -0
  153. package/dist/gs/unicode/tables.js.map +1 -0
  154. package/dist/gs/unicode/unicode.d.ts +58 -38
  155. package/dist/gs/unicode/unicode.js +362 -278
  156. package/dist/gs/unicode/unicode.js.map +1 -1
  157. package/go.sum +13 -0
  158. package/gs/builtin/builtin.ts +83 -93
  159. package/gs/builtin/hostio.ts +1 -1
  160. package/gs/builtin/index.ts +1 -0
  161. package/gs/builtin/panic.test.ts +189 -0
  162. package/gs/builtin/panic.ts +107 -0
  163. package/gs/builtin/runtime-contract.test.ts +5 -5
  164. package/gs/builtin/slice.test.ts +23 -0
  165. package/gs/builtin/slice.ts +133 -95
  166. package/gs/builtin/type.ts +16 -3
  167. package/gs/builtin/varRef.ts +4 -2
  168. package/gs/builtin/wide-int.test.ts +41 -0
  169. package/gs/bytes/bytes.gs.ts +54 -41
  170. package/gs/bytes/bytes.test.ts +18 -1
  171. package/gs/bytes/reader.gs.ts +7 -8
  172. package/gs/cmp/index.test.ts +55 -0
  173. package/gs/cmp/index.ts +45 -9
  174. package/gs/context/context.ts +3 -3
  175. package/gs/embed/index.ts +2 -2
  176. package/gs/encoding/binary/index.test.ts +104 -0
  177. package/gs/encoding/binary/index.ts +259 -11
  178. package/gs/encoding/json/index.test.ts +107 -0
  179. package/gs/encoding/json/index.ts +400 -29
  180. package/gs/errors/errors.test.ts +44 -1
  181. package/gs/errors/errors.ts +15 -31
  182. package/gs/fmt/fmt.test.ts +70 -2
  183. package/gs/fmt/fmt.ts +128 -34
  184. package/gs/golang.org/x/crypto/cryptobyte/index.ts +1 -1
  185. package/gs/internal/bytealg/index.test.ts +26 -1
  186. package/gs/internal/bytealg/index.ts +44 -8
  187. package/gs/internal/byteorder/index.ts +6 -4
  188. package/gs/io/fs/format.ts +2 -2
  189. package/gs/io/fs/fs.ts +2 -2
  190. package/gs/io/fs/stat.test.ts +2 -2
  191. package/gs/io/fs/sub.test.ts +2 -2
  192. package/gs/io/fs/walk.test.ts +2 -2
  193. package/gs/io/io.test.ts +47 -5
  194. package/gs/io/io.ts +73 -73
  195. package/gs/io/limit.test.ts +103 -0
  196. package/gs/math/bits/index.test.ts +128 -0
  197. package/gs/math/bits/index.ts +26 -8
  198. package/gs/math/copysign.gs.test.ts +3 -1
  199. package/gs/math/copysign.gs.ts +10 -22
  200. package/gs/math/pow.gs.test.ts +4 -5
  201. package/gs/math/pow.gs.ts +5 -0
  202. package/gs/math/signbit.gs.test.ts +2 -1
  203. package/gs/math/signbit.gs.ts +6 -3
  204. package/gs/mime/index.ts +1 -0
  205. package/gs/net/http/index.test.ts +683 -2
  206. package/gs/net/http/index.ts +598 -57
  207. package/gs/net/http/meta.json +3 -0
  208. package/gs/os/stat.gs.ts +2 -2
  209. package/gs/os/types.gs.ts +2 -2
  210. package/gs/os/types_js.gs.ts +9 -9
  211. package/gs/os/types_unix.gs.ts +2 -2
  212. package/gs/os/zero_copy_posix.gs.ts +2 -2
  213. package/gs/path/filepath/match.test.ts +16 -0
  214. package/gs/path/filepath/match.ts +8 -4
  215. package/gs/path/filepath/path.test.ts +91 -9
  216. package/gs/path/filepath/path.ts +223 -49
  217. package/gs/path/match.test.ts +32 -0
  218. package/gs/path/match.ts +6 -3
  219. package/gs/reflect/deepequal.test.ts +1 -1
  220. package/gs/reflect/field.test.ts +1 -1
  221. package/gs/reflect/function-types.test.ts +6 -6
  222. package/gs/reflect/sliceat.test.ts +13 -13
  223. package/gs/reflect/structof.test.ts +4 -4
  224. package/gs/reflect/type.ts +34 -14
  225. package/gs/reflect/typefor.test.ts +5 -5
  226. package/gs/runtime/pprof/index.test.ts +20 -0
  227. package/gs/runtime/trace/index.test.ts +3 -0
  228. package/gs/slices/slices.test.ts +31 -0
  229. package/gs/slices/slices.ts +11 -11
  230. package/gs/strconv/append.test.ts +99 -0
  231. package/gs/strconv/atof.gs.ts +156 -42
  232. package/gs/strconv/atof.test.ts +45 -0
  233. package/gs/strconv/atoi.gs.ts +87 -69
  234. package/gs/strconv/atoi.test.ts +49 -0
  235. package/gs/strconv/ftoa.gs.ts +85 -10
  236. package/gs/strconv/ftoa.test.ts +43 -0
  237. package/gs/strconv/itoa.gs.ts +10 -9
  238. package/gs/strconv/quote.gs.ts +335 -108
  239. package/gs/strconv/quote.test.ts +111 -0
  240. package/gs/strings/reader.test.ts +10 -10
  241. package/gs/strings/reader.ts +9 -9
  242. package/gs/strings/strings.test.ts +18 -5
  243. package/gs/strings/strings.ts +81 -68
  244. package/gs/sync/atomic/doc_64.gs.ts +24 -24
  245. package/gs/sync/atomic/doc_64.test.ts +5 -5
  246. package/gs/sync/atomic/type.gs.ts +28 -28
  247. package/gs/sync/sync.test.ts +109 -1
  248. package/gs/sync/sync.ts +46 -12
  249. package/gs/syscall/fs.ts +8 -8
  250. package/gs/syscall/net.test.ts +1 -1
  251. package/gs/time/parse.test.ts +45 -0
  252. package/gs/time/time.test.ts +46 -23
  253. package/gs/time/time.ts +69 -66
  254. package/gs/unicode/gen.go +198 -0
  255. package/gs/unicode/tables.ts +646 -0
  256. package/gs/unicode/unicode.test.ts +69 -0
  257. package/gs/unicode/unicode.ts +396 -312
  258. package/package.json +1 -1
  259. package/dist/gs/github.com/aperturerobotics/util/conc/index.d.ts +0 -20
  260. package/dist/gs/github.com/aperturerobotics/util/conc/index.js +0 -134
  261. package/dist/gs/github.com/aperturerobotics/util/conc/index.js.map +0 -1
  262. package/gs/github.com/aperturerobotics/util/conc/index.test.ts +0 -30
  263. package/gs/github.com/aperturerobotics/util/conc/index.ts +0 -172
  264. package/gs/github.com/aperturerobotics/util/conc/meta.json +0 -9
@@ -92,6 +92,7 @@ func BenchmarkLoweringFile(b *testing.B) {
92
92
  make(map[*types.Func]bool),
93
93
  make(runtimeMethodSetCache),
94
94
  false,
95
+ false,
95
96
  "",
96
97
  ); diagnosticsHaveErrors(diagnostics) {
97
98
  b.Fatal(diagnostics)
@@ -4,12 +4,16 @@ import (
4
4
  "bytes"
5
5
  "context"
6
6
  "errors"
7
+ "go/ast"
8
+ "go/parser"
9
+ "go/token"
7
10
  "io"
8
11
  "io/fs"
9
12
  "maps"
10
13
  "os"
11
14
  "path"
12
15
  "path/filepath"
16
+ "regexp"
13
17
  "slices"
14
18
  "strings"
15
19
 
@@ -17,16 +21,23 @@ import (
17
21
  gs "github.com/s4wave/goscript"
18
22
  )
19
23
 
24
+ var (
25
+ overrideBehaviorTestIdentifierPattern = regexp.MustCompile(`\b[A-Za-z_$][A-Za-z0-9_$]*\b`)
26
+ overrideBehaviorTestNamedImportPattern = regexp.MustCompile(`(?s)import\s+(type\s+)?\{([^}]*)\}\s+from\s+['"]\.[^'"]*['"]`)
27
+ overrideBehaviorTestNamespaceImportPattern = regexp.MustCompile(`import\s+\*\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)\s+from\s+['"]\.[^'"]*['"]`)
28
+ )
29
+
20
30
  // OverrideFacts is the immutable compiler-visible view of GoScript overrides.
21
31
  type OverrideFacts struct {
22
32
  packages map[string]overridePackageFacts
23
33
  }
24
34
 
25
35
  type overridePackageFacts struct {
26
- metadata OverrideMetadata
27
- parity overrideParityLedger
28
- copyPackage overrideCopyPackage
29
- dependencies []string
36
+ metadata OverrideMetadata
37
+ parity overrideParityLedger
38
+ behaviorTestSymbols map[string]bool
39
+ copyPackage overrideCopyPackage
40
+ dependencies []string
30
41
  }
31
42
 
32
43
  type overridePackageRoot struct {
@@ -61,6 +72,14 @@ func (f *OverrideFacts) parityLedger(pkgPath string) overrideParityLedger {
61
72
  return cloneOverrideParityLedger(pkg.parity)
62
73
  }
63
74
 
75
+ func (f *OverrideFacts) behaviorTestSymbols(pkgPath string) map[string]bool {
76
+ if f == nil {
77
+ return nil
78
+ }
79
+ pkg := f.packages[pkgPath]
80
+ return cloneBoolMap(pkg.behaviorTestSymbols)
81
+ }
82
+
64
83
  // IsMethodAsync returns true when override metadata marks a method async.
65
84
  func (f *OverrideFacts) IsMethodAsync(pkgPath, method string) bool {
66
85
  if f == nil {
@@ -122,6 +141,7 @@ func buildOverrideFacts(ctx context.Context, overrideDirs []string) (*OverrideFa
122
141
  slices.Sort(paths)
123
142
 
124
143
  facts := &OverrideFacts{packages: make(map[string]overridePackageFacts, len(paths))}
144
+ goldenBehaviorTests := loadGoldenBehaviorTests()
125
145
  for _, pkgPath := range paths {
126
146
  if err := ctx.Err(); err != nil {
127
147
  return facts, []Diagnostic{contextCanceledDiagnostic(err)}
@@ -141,11 +161,18 @@ func buildOverrideFacts(ctx context.Context, overrideDirs []string) (*OverrideFa
141
161
  if diagnosticsHaveErrors(packageDiagnostics) {
142
162
  continue
143
163
  }
164
+ behaviorTests, err := loadOverrideBehaviorTests(roots[pkgPath], roots)
165
+ if err != nil {
166
+ diagnostics = append(diagnostics, overrideError("read override behavior tests", pkgPath, err))
167
+ continue
168
+ }
169
+ maps.Copy(behaviorTests, goldenBehaviorTests[pkgPath])
144
170
  facts.packages[pkgPath] = overridePackageFacts{
145
- metadata: cloneOverrideMetadata(metadata),
146
- parity: cloneOverrideParityLedger(parity),
147
- copyPackage: copyPackage,
148
- dependencies: dependencies,
171
+ metadata: cloneOverrideMetadata(metadata),
172
+ parity: cloneOverrideParityLedger(parity),
173
+ behaviorTestSymbols: behaviorTests,
174
+ copyPackage: copyPackage,
175
+ dependencies: dependencies,
149
176
  }
150
177
  }
151
178
  if diagnosticsHaveErrors(diagnostics) {
@@ -336,6 +363,280 @@ func loadOverrideCopyPackage(
336
363
  return copyPackage, dependencies, nil
337
364
  }
338
365
 
366
+ func loadOverrideBehaviorTests(
367
+ root overridePackageRoot,
368
+ roots map[string]overridePackageRoot,
369
+ ) (map[string]bool, error) {
370
+ symbols := make(map[string]bool)
371
+ err := fs.WalkDir(root.fsys, root.dir, func(filePath string, entry fs.DirEntry, walkErr error) error {
372
+ if walkErr != nil {
373
+ return walkErr
374
+ }
375
+ if entry.IsDir() {
376
+ nestedPkg := strings.TrimPrefix(filePath, root.dir+"/")
377
+ if root.dir == "." {
378
+ nestedPkg = filePath
379
+ } else if nestedPkg != filePath {
380
+ nestedPkg = path.Join(root.pkgPath, nestedPkg)
381
+ }
382
+ if _, ok := roots[nestedPkg]; nestedPkg != root.pkgPath && ok {
383
+ return fs.SkipDir
384
+ }
385
+ return nil
386
+ }
387
+ if !strings.HasSuffix(filePath, ".test.ts") {
388
+ return nil
389
+ }
390
+ data, readErr := fs.ReadFile(root.fsys, filePath)
391
+ if readErr != nil {
392
+ return readErr
393
+ }
394
+ maps.Copy(symbols, scanOverrideBehaviorTestSymbols(string(data)))
395
+ return nil
396
+ })
397
+ if err != nil {
398
+ return nil, err
399
+ }
400
+ return symbols, nil
401
+ }
402
+
403
+ func scanOverrideBehaviorTestSymbols(data string) map[string]bool {
404
+ body := stripTypeScriptCommentsAndStrings(typeScriptBodyWithoutImports(data))
405
+ bodyIdentifiers := make(map[string]bool)
406
+ for _, match := range overrideBehaviorTestIdentifierPattern.FindAllString(body, -1) {
407
+ bodyIdentifiers[match] = true
408
+ }
409
+
410
+ symbols := make(map[string]bool)
411
+ for _, match := range overrideBehaviorTestNamedImportPattern.FindAllStringSubmatch(data, -1) {
412
+ if match[1] != "" {
413
+ continue
414
+ }
415
+ for _, namedImport := range parseNamedTypeScriptExports(match[2], false) {
416
+ if bodyIdentifiers[namedImport.target] {
417
+ symbols[namedImport.source] = true
418
+ }
419
+ }
420
+ }
421
+ for _, match := range overrideBehaviorTestNamespaceImportPattern.FindAllStringSubmatch(data, -1) {
422
+ namespaceSelectorPattern := regexp.MustCompile(`\b` + regexp.QuoteMeta(match[1]) + `\.([A-Za-z_$][A-Za-z0-9_$]*)\b`)
423
+ for _, selector := range namespaceSelectorPattern.FindAllStringSubmatch(body, -1) {
424
+ symbols[selector[1]] = true
425
+ }
426
+ }
427
+ return symbols
428
+ }
429
+
430
+ func typeScriptBodyWithoutImports(data string) string {
431
+ var body strings.Builder
432
+ inImport := false
433
+ for line := range strings.SplitAfterSeq(data, "\n") {
434
+ trimmed := strings.TrimSpace(line)
435
+ if !inImport && strings.HasPrefix(trimmed, "import ") {
436
+ if !strings.Contains(trimmed, " from ") &&
437
+ !strings.HasPrefix(trimmed, `import "`) &&
438
+ !strings.HasPrefix(trimmed, `import '`) {
439
+ inImport = true
440
+ }
441
+ body.WriteByte('\n')
442
+ continue
443
+ }
444
+ if inImport {
445
+ if strings.Contains(trimmed, " from ") {
446
+ inImport = false
447
+ }
448
+ body.WriteByte('\n')
449
+ continue
450
+ }
451
+ body.WriteString(line)
452
+ }
453
+ return body.String()
454
+ }
455
+
456
+ func stripTypeScriptCommentsAndStrings(data string) string {
457
+ var body strings.Builder
458
+ const (
459
+ modeCode = iota
460
+ modeLineComment
461
+ modeBlockComment
462
+ modeSingleString
463
+ modeDoubleString
464
+ modeTemplateString
465
+ )
466
+ mode := modeCode
467
+ escaped := false
468
+ for i := 0; i < len(data); i++ {
469
+ ch := data[i]
470
+ next := byte(0)
471
+ if i+1 < len(data) {
472
+ next = data[i+1]
473
+ }
474
+ switch mode {
475
+ case modeCode:
476
+ switch {
477
+ case ch == '/' && next == '/':
478
+ body.WriteByte(' ')
479
+ body.WriteByte(' ')
480
+ i++
481
+ mode = modeLineComment
482
+ case ch == '/' && next == '*':
483
+ body.WriteByte(' ')
484
+ body.WriteByte(' ')
485
+ i++
486
+ mode = modeBlockComment
487
+ case ch == '\'':
488
+ body.WriteByte(' ')
489
+ mode = modeSingleString
490
+ case ch == '"':
491
+ body.WriteByte(' ')
492
+ mode = modeDoubleString
493
+ case ch == '`':
494
+ body.WriteByte(' ')
495
+ mode = modeTemplateString
496
+ default:
497
+ body.WriteByte(ch)
498
+ }
499
+ case modeLineComment:
500
+ if ch == '\n' {
501
+ body.WriteByte(ch)
502
+ mode = modeCode
503
+ continue
504
+ }
505
+ body.WriteByte(' ')
506
+ case modeBlockComment:
507
+ if ch == '*' && next == '/' {
508
+ body.WriteByte(' ')
509
+ body.WriteByte(' ')
510
+ i++
511
+ mode = modeCode
512
+ continue
513
+ }
514
+ if ch == '\n' {
515
+ body.WriteByte(ch)
516
+ continue
517
+ }
518
+ body.WriteByte(' ')
519
+ case modeSingleString, modeDoubleString, modeTemplateString:
520
+ quote := byte('\'')
521
+ if mode == modeDoubleString {
522
+ quote = '"'
523
+ }
524
+ if mode == modeTemplateString {
525
+ quote = '`'
526
+ }
527
+ if ch == '\n' {
528
+ body.WriteByte(ch)
529
+ escaped = false
530
+ if mode != modeTemplateString {
531
+ mode = modeCode
532
+ }
533
+ continue
534
+ }
535
+ body.WriteByte(' ')
536
+ if escaped {
537
+ escaped = false
538
+ continue
539
+ }
540
+ if ch == '\\' {
541
+ escaped = true
542
+ continue
543
+ }
544
+ if ch == quote {
545
+ mode = modeCode
546
+ }
547
+ }
548
+ }
549
+ return body.String()
550
+ }
551
+
552
+ func loadGoldenBehaviorTests() map[string]map[string]bool {
553
+ testsDir, ok := findGoldenTestsDir()
554
+ if !ok {
555
+ return nil
556
+ }
557
+ result := make(map[string]map[string]bool)
558
+ _ = filepath.WalkDir(testsDir, func(filePath string, entry fs.DirEntry, err error) error {
559
+ if err != nil || entry.IsDir() || filepath.Ext(filePath) != ".go" {
560
+ return nil
561
+ }
562
+ file, parseErr := parser.ParseFile(token.NewFileSet(), filePath, nil, parser.SkipObjectResolution)
563
+ if parseErr != nil {
564
+ return nil
565
+ }
566
+ importAliases := make(map[string]string)
567
+ dotImports := make(map[string]bool)
568
+ for _, spec := range file.Imports {
569
+ if spec.Path == nil {
570
+ continue
571
+ }
572
+ pkgPath := strings.Trim(spec.Path.Value, `"`)
573
+ if spec.Name != nil {
574
+ switch spec.Name.Name {
575
+ case ".":
576
+ dotImports[pkgPath] = true
577
+ case "_":
578
+ default:
579
+ importAliases[spec.Name.Name] = pkgPath
580
+ }
581
+ continue
582
+ }
583
+ importAliases[path.Base(pkgPath)] = pkgPath
584
+ }
585
+ if len(importAliases) == 0 && len(dotImports) == 0 {
586
+ return nil
587
+ }
588
+ ast.Inspect(file, func(node ast.Node) bool {
589
+ switch expr := node.(type) {
590
+ case *ast.SelectorExpr:
591
+ ident, ok := expr.X.(*ast.Ident)
592
+ if !ok {
593
+ return true
594
+ }
595
+ pkgPath, ok := importAliases[ident.Name]
596
+ if !ok {
597
+ return true
598
+ }
599
+ addBehaviorTestSymbol(result, pkgPath, expr.Sel.Name)
600
+ case *ast.Ident:
601
+ for pkgPath := range dotImports {
602
+ addBehaviorTestSymbol(result, pkgPath, expr.Name)
603
+ }
604
+ }
605
+ return true
606
+ })
607
+ return nil
608
+ })
609
+ return result
610
+ }
611
+
612
+ func findGoldenTestsDir() (string, bool) {
613
+ wd, err := os.Getwd()
614
+ if err != nil {
615
+ return "", false
616
+ }
617
+ for {
618
+ candidate := filepath.Join(wd, "tests", "tests")
619
+ if info, statErr := os.Stat(candidate); statErr == nil && info.IsDir() {
620
+ return candidate, true
621
+ }
622
+ parent := filepath.Dir(wd)
623
+ if parent == wd {
624
+ return "", false
625
+ }
626
+ wd = parent
627
+ }
628
+ }
629
+
630
+ func addBehaviorTestSymbol(index map[string]map[string]bool, pkgPath, symbol string) {
631
+ if pkgPath == "" || symbol == "" {
632
+ return
633
+ }
634
+ if index[pkgPath] == nil {
635
+ index[pkgPath] = make(map[string]bool)
636
+ }
637
+ index[pkgPath][symbol] = true
638
+ }
639
+
339
640
  func newOverrideMetadata() OverrideMetadata {
340
641
  return OverrideMetadata{
341
642
  AsyncFunctions: make(map[string]bool),
@@ -90,12 +90,41 @@ func (v *OverrideParityVerifier) Verify(
90
90
  })
91
91
  continue
92
92
  }
93
- diagnostics = append(diagnostics, verifyOverrideParityPackage(node.PkgPath, pkg.Types, ledger, exports)...)
93
+ diagnostics = append(diagnostics, verifyOverrideParityPackage(
94
+ node.PkgPath,
95
+ pkg.Types,
96
+ ledger,
97
+ exports,
98
+ facts.behaviorTestSymbols(node.PkgPath),
99
+ )...)
94
100
  }
95
101
  diagnostics = append(diagnostics, verifyBlockedOverrideUses(graph, facts)...)
96
102
  return diagnostics
97
103
  }
98
104
 
105
+ func overrideParityRequiresTypedGraph(graph *PackageGraph, facts *OverrideFacts) bool {
106
+ if graph == nil || facts == nil {
107
+ return false
108
+ }
109
+ for _, node := range graph.Nodes {
110
+ if node == nil || !node.OverrideCandidate {
111
+ continue
112
+ }
113
+ ledger := facts.parityLedger(node.PkgPath)
114
+ if ledger.Strict || len(ledger.Symbols) != 0 {
115
+ return true
116
+ }
117
+ }
118
+ for _, pkg := range facts.packages {
119
+ for _, entry := range pkg.parity.Symbols {
120
+ if entry.Status == overrideParityStatusBlocked {
121
+ return true
122
+ }
123
+ }
124
+ }
125
+ return false
126
+ }
127
+
99
128
  // VerifyNoDeferred reports transient parity entries that remain in packages
100
129
  // whose parity surface is expected to be closed.
101
130
  func (v *OverrideParityVerifier) VerifyNoDeferred(facts *OverrideFacts, pkgPaths ...string) []Diagnostic {
@@ -131,12 +160,14 @@ func verifyOverrideParityPackage(
131
160
  goPkg *types.Package,
132
161
  ledger overrideParityLedger,
133
162
  tsExports map[string]typeScriptExport,
163
+ behaviorTests map[string]bool,
134
164
  ) []Diagnostic {
135
165
  goExports := exportedPackageSymbols(goPkg)
136
166
  goExportSet := make(map[string]bool, len(goExports))
137
167
  var diagnostics []Diagnostic
138
168
  for _, symbol := range goExports {
139
169
  goExportSet[symbol.name] = true
170
+ obj := goPkg.Scope().Lookup(symbol.name)
140
171
  entry, ok := ledger.Symbols[symbol.name]
141
172
  if ledger.Strict && !ok {
142
173
  diagnostics = append(diagnostics, Diagnostic{
@@ -158,6 +189,14 @@ func verifyOverrideParityPackage(
158
189
  Detail: pkgPath + "." + symbol.name + " is classified as " + string(entry.Status),
159
190
  })
160
191
  }
192
+ if entry.Status == overrideParityStatusReal && isPackageLevelFunc(obj) && !behaviorTests[symbol.name] {
193
+ diagnostics = append(diagnostics, Diagnostic{
194
+ Severity: DiagnosticSeverityWarning,
195
+ Code: "goscript/overrides:parity-missing-behavior-test",
196
+ Message: "real override function is missing a locatable behavior test",
197
+ Detail: pkgPath + "." + symbol.name,
198
+ })
199
+ }
161
200
  if entry.Status.forbidsExport() && tsExports[symbol.name].present() {
162
201
  diagnostics = append(diagnostics, Diagnostic{
163
202
  Severity: DiagnosticSeverityError,
@@ -180,6 +219,11 @@ func verifyOverrideParityPackage(
180
219
  return diagnostics
181
220
  }
182
221
 
222
+ func isPackageLevelFunc(obj types.Object) bool {
223
+ _, ok := obj.(*types.Func)
224
+ return ok
225
+ }
226
+
183
227
  func (symbol goPackageExport) satisfiedBy(export typeScriptExport) bool {
184
228
  if symbol.requiresValue {
185
229
  return export.value
@@ -0,0 +1,100 @@
1
+ package compiler
2
+
3
+ import (
4
+ "context"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ func TestOverrideParityVerifierWarnsForMissingBehaviorTest(t *testing.T) {
11
+ result, err := compileBehaviorParityFixture(t, "")
12
+ if err != nil {
13
+ t.Fatalf("expected compile without behavior test to warn, not fail: %v\n%#v", err, result.Diagnostics)
14
+ }
15
+ requireDiagnosticCode(t, result.Diagnostics, "goscript/overrides:parity-missing-behavior-test")
16
+ requireDiagnosticSeverity(t, result.Diagnostics, "goscript/overrides:parity-missing-behavior-test", DiagnosticSeverityWarning)
17
+
18
+ result, err = compileBehaviorParityFixture(t, strings.Join([]string{
19
+ "import { Present } from './index.js'",
20
+ "Present()",
21
+ "",
22
+ }, "\n"))
23
+ if err != nil {
24
+ t.Fatalf("expected compile with behavior test to pass: %v\n%#v", err, result.Diagnostics)
25
+ }
26
+ }
27
+
28
+ func TestOverrideParityVerifierIgnoresImportOnlyBehaviorReference(t *testing.T) {
29
+ result, err := compileBehaviorParityFixture(t, strings.Join([]string{
30
+ "import { Present } from './index.js'",
31
+ "const name = 'Present'",
32
+ "void name",
33
+ "",
34
+ }, "\n"))
35
+ if err != nil {
36
+ t.Fatalf("expected import-only behavior test to warn, not fail: %v\n%#v", err, result.Diagnostics)
37
+ }
38
+ requireDiagnosticCode(t, result.Diagnostics, "goscript/overrides:parity-missing-behavior-test")
39
+ requireDiagnosticSeverity(t, result.Diagnostics, "goscript/overrides:parity-missing-behavior-test", DiagnosticSeverityWarning)
40
+ }
41
+
42
+ func TestOverrideParityVerifierAcceptsNamespaceBehaviorReference(t *testing.T) {
43
+ result, err := compileBehaviorParityFixture(t, strings.Join([]string{
44
+ "import * as lib from './index.js'",
45
+ "lib.Present()",
46
+ "",
47
+ }, "\n"))
48
+ if err != nil {
49
+ t.Fatalf("expected namespace behavior test to pass: %v\n%#v", err, result.Diagnostics)
50
+ }
51
+ }
52
+
53
+ func compileBehaviorParityFixture(t *testing.T, behaviorTest string) (*CompilationResult, error) {
54
+ t.Helper()
55
+
56
+ moduleDir := writePackageGraphFixture(t, map[string]string{
57
+ "go.mod": "module example.test/behaviorparity\n\ngo 1.25.3\n",
58
+ "main.go": strings.Join([]string{
59
+ "package main",
60
+ "import \"example.test/behaviorparity/lib\"",
61
+ "func main() { lib.Present() }",
62
+ "",
63
+ }, "\n"),
64
+ "lib/lib.go": strings.Join([]string{
65
+ "package lib",
66
+ "func Present() {}",
67
+ "",
68
+ }, "\n"),
69
+ })
70
+ overrideDir := filepath.Join(t.TempDir(), "gs")
71
+ writeFixtureFile(t, overrideDir, "example.test/behaviorparity/lib/index.ts", "export function Present(): void {}\n")
72
+ if behaviorTest != "" {
73
+ writeFixtureFile(t, overrideDir, "example.test/behaviorparity/lib/index.test.ts", behaviorTest)
74
+ }
75
+ writeFixtureFile(t, overrideDir, "example.test/behaviorparity/lib/parity.json", parityFixtureJSON(t, map[string]overrideParityEntry{
76
+ "Present": {Status: overrideParityStatusReal},
77
+ }))
78
+
79
+ comp, err := NewCompiler(&Config{
80
+ Dir: moduleDir,
81
+ OutputPath: filepath.Join(t.TempDir(), "out"),
82
+ OverrideDirs: []string{overrideDir},
83
+ AllDependencies: true,
84
+ }, nil, nil)
85
+ if err != nil {
86
+ t.Fatal(err.Error())
87
+ }
88
+ return comp.CompilePackages(context.Background(), ".")
89
+ }
90
+
91
+ func requireDiagnosticSeverity(t *testing.T, diagnostics []Diagnostic, code string, severity DiagnosticSeverity) {
92
+ t.Helper()
93
+
94
+ for _, diagnostic := range diagnostics {
95
+ if diagnostic.Code == code && diagnostic.Severity == severity {
96
+ return
97
+ }
98
+ }
99
+ t.Fatalf("missing diagnostic %q with severity %q in %#v", code, severity, diagnostics)
100
+ }
@@ -1123,6 +1123,7 @@ func compileParityFixture(
1123
1123
  })
1124
1124
  overrideDir := filepath.Join(t.TempDir(), "gs")
1125
1125
  writeFixtureFile(t, overrideDir, "example.test/parity/lib/index.ts", index)
1126
+ writeFixtureFile(t, overrideDir, "example.test/parity/lib/index.test.ts", "import { Present } from './index.js'\nPresent()\n")
1126
1127
  writeFixtureFile(t, overrideDir, "example.test/parity/lib/parity.json", parityFixtureJSON(t, symbols))
1127
1128
 
1128
1129
  comp, err := NewCompiler(&Config{
@@ -56,6 +56,13 @@ type PackageGraphOwner struct {
56
56
  overrideOwner *OverrideRegistryOwner
57
57
  }
58
58
 
59
+ type packageGraphLoadShape int
60
+
61
+ const (
62
+ packageGraphLoadFull packageGraphLoadShape = iota
63
+ packageGraphLoadIdentity
64
+ )
65
+
59
66
  // NewPackageGraphOwner creates the package graph owner.
60
67
  func NewPackageGraphOwner(overrideOwners ...*OverrideRegistryOwner) *PackageGraphOwner {
61
68
  overrideOwner := NewOverrideRegistryOwner()
@@ -67,6 +74,15 @@ func NewPackageGraphOwner(overrideOwners ...*OverrideRegistryOwner) *PackageGrap
67
74
 
68
75
  // Load builds the package graph for a validated request.
69
76
  func (o *PackageGraphOwner) Load(ctx context.Context, req *CompileRequest) (*PackageGraph, []Diagnostic) {
77
+ return o.load(ctx, req, packageGraphLoadFull)
78
+ }
79
+
80
+ // LoadIdentity builds the package identity graph needed for cache lookup.
81
+ func (o *PackageGraphOwner) LoadIdentity(ctx context.Context, req *CompileRequest) (*PackageGraph, []Diagnostic) {
82
+ return o.load(ctx, req, packageGraphLoadIdentity)
83
+ }
84
+
85
+ func (o *PackageGraphOwner) load(ctx context.Context, req *CompileRequest, shape packageGraphLoadShape) (*PackageGraph, []Diagnostic) {
70
86
  if err := ctx.Err(); err != nil {
71
87
  return nil, []Diagnostic{{
72
88
  Severity: DiagnosticSeverityError,
@@ -81,18 +97,7 @@ func (o *PackageGraphOwner) Load(ctx context.Context, req *CompileRequest) (*Pac
81
97
  Env: append(os.Environ(), "GOOS=js", "GOARCH=wasm"),
82
98
  BuildFlags: goScriptBuildFlags(req.BuildFlags),
83
99
  Tests: req.Tests,
84
- Mode: packages.NeedName |
85
- packages.NeedFiles |
86
- packages.NeedCompiledGoFiles |
87
- packages.NeedImports |
88
- packages.NeedDeps |
89
- packages.NeedExportFile |
90
- packages.NeedTypes |
91
- packages.NeedSyntax |
92
- packages.NeedTypesInfo |
93
- packages.NeedTypesSizes |
94
- packages.NeedForTest |
95
- packages.NeedModule,
100
+ Mode: packageGraphLoadMode(shape),
96
101
  }
97
102
  pkgs, err := packages.Load(cfg, req.Patterns...)
98
103
  if err != nil {
@@ -170,6 +175,25 @@ func (o *PackageGraphOwner) Load(ctx context.Context, req *CompileRequest) (*Pac
170
175
  return graph, diagnostics
171
176
  }
172
177
 
178
+ func packageGraphLoadMode(shape packageGraphLoadShape) packages.LoadMode {
179
+ mode := packages.NeedName |
180
+ packages.NeedFiles |
181
+ packages.NeedCompiledGoFiles |
182
+ packages.NeedImports |
183
+ packages.NeedDeps |
184
+ packages.NeedForTest |
185
+ packages.NeedModule
186
+ if shape == packageGraphLoadIdentity {
187
+ return mode
188
+ }
189
+ return mode |
190
+ packages.NeedExportFile |
191
+ packages.NeedTypes |
192
+ packages.NeedSyntax |
193
+ packages.NeedTypesInfo |
194
+ packages.NeedTypesSizes
195
+ }
196
+
173
197
  func (o *PackageGraphOwner) collect(
174
198
  graph *PackageGraph,
175
199
  pkg *packages.Package,
@@ -326,6 +350,10 @@ func packageBlocklistChain(graph *PackageGraph, blocklist []string) []string {
326
350
  return nil
327
351
  }
328
352
 
353
+ func packageGraphContainsPackage(graph *PackageGraph, packagePath string) bool {
354
+ return len(packageBlocklistChain(graph, []string{packagePath})) != 0
355
+ }
356
+
329
357
  func packagePathBlocklisted(path string, blocklist []string) bool {
330
358
  for _, blocked := range blocklist {
331
359
  // Match the package exactly or any subpackage, on a path-segment
@@ -466,6 +466,35 @@ func TestPackageGraphOverrideCandidatesRequirePackageIndex(t *testing.T) {
466
466
  }
467
467
  }
468
468
 
469
+ func TestPackageGraphLoadIdentityOmitsTypedFacts(t *testing.T) {
470
+ moduleDir := writePackageGraphFixture(t, map[string]string{
471
+ "go.mod": "module example.test/identitygraph\n\ngo 1.25.3\n",
472
+ "main.go": "package identitygraph\nconst Value = 1\n",
473
+ })
474
+ graph, diagnostics := NewPackageGraphOwner().LoadIdentity(context.Background(), &CompileRequest{
475
+ Dir: moduleDir,
476
+ OutputPath: filepath.Join(t.TempDir(), "out"),
477
+ Patterns: []string{"."},
478
+ })
479
+ if diagnosticsHaveErrors(diagnostics) {
480
+ t.Fatalf("identity graph load failed: %#v", diagnostics)
481
+ }
482
+ node := graph.NodesByPackagePath["example.test/identitygraph"]
483
+ if node == nil {
484
+ t.Fatalf("missing identity graph node: %#v", graph.NodesByPackagePath)
485
+ }
486
+ if len(node.CompiledGoFiles) != 1 || filepath.Base(node.CompiledGoFiles[0]) != "main.go" {
487
+ t.Fatalf("compiled files = %#v, want main.go", node.CompiledGoFiles)
488
+ }
489
+ pkg := graph.packagesByPath["example.test/identitygraph"]
490
+ if pkg == nil {
491
+ t.Fatal("missing loaded package")
492
+ }
493
+ if pkg.Types != nil || pkg.TypesInfo != nil || len(pkg.Syntax) != 0 {
494
+ t.Fatalf("identity graph loaded typed facts: Types=%v TypesInfo=%v Syntax=%d", pkg.Types, pkg.TypesInfo, len(pkg.Syntax))
495
+ }
496
+ }
497
+
469
498
  func loadPackageGraph(t *testing.T, req *CompileRequest) *PackageGraph {
470
499
  t.Helper()
471
500