@ttsc/wasm 0.30.3 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +3 -3
  2. package/build/build-wasm.cjs +5 -4
  3. package/build/go-build-cache.cjs +347 -0
  4. package/build/pack-prepare.cjs +3 -2
  5. package/cmd/ttsc-wasm/main.go +22 -10
  6. package/cmd/ttsc-wasm/main_wasm.go +2 -2
  7. package/dist/.ttsc-wasm-build.json +138 -58
  8. package/dist/go.mod +7 -5
  9. package/dist/ttsc.wasm +2 -2
  10. package/go.mod +7 -5
  11. package/go.sum +10 -10
  12. package/host/api.go +34 -41
  13. package/host/doc.go +7 -6
  14. package/host/expose.go +42 -0
  15. package/host/fountain.go +192 -22
  16. package/host/host.go +10 -25
  17. package/host/host_native.go +3 -5
  18. package/host/invoke.go +133 -0
  19. package/host/plugin.go +37 -72
  20. package/host/result.go +108 -0
  21. package/lib/src/BootTtscWorkerTerminationError.d.ts +20 -0
  22. package/lib/src/BootTtscWorkerTerminationError.js +20 -0
  23. package/lib/src/BootTtscWorkerTerminationError.js.map +1 -1
  24. package/lib/src/MemFSError.d.ts +21 -2
  25. package/lib/src/MemFSError.js +25 -2
  26. package/lib/src/MemFSError.js.map +1 -1
  27. package/lib/src/bootTtsc.d.ts +49 -2
  28. package/lib/src/bootTtsc.js +89 -23
  29. package/lib/src/bootTtsc.js.map +1 -1
  30. package/lib/src/createMemFS.d.ts +45 -0
  31. package/lib/src/createMemFS.js +269 -66
  32. package/lib/src/createMemFS.js.map +1 -1
  33. package/lib/src/parseResult.d.ts +19 -0
  34. package/lib/src/parseResult.js +19 -0
  35. package/lib/src/parseResult.js.map +1 -1
  36. package/lib/src/structures/IBootResult.d.ts +20 -2
  37. package/lib/src/structures/IBootTtscOptions.d.ts +28 -6
  38. package/lib/src/structures/IFileStats.d.ts +51 -0
  39. package/lib/src/structures/IMemFSHost.d.ts +89 -0
  40. package/lib/src/structures/ITtscApi.d.ts +154 -9
  41. package/lib/src/structures/ITtscBuildOpts.d.ts +18 -1
  42. package/lib/src/structures/ITtscCompileResult.d.ts +20 -2
  43. package/lib/src/structures/ITtscDiagnostic.d.ts +25 -1
  44. package/lib/src/structures/ITtscFileQuery.d.ts +19 -1
  45. package/lib/src/structures/ITtscFountainDiagnosticsResult.d.ts +20 -1
  46. package/lib/src/structures/ITtscNodeAtPositionResult.d.ts +18 -1
  47. package/lib/src/structures/ITtscNodeInfo.d.ts +23 -2
  48. package/lib/src/structures/ITtscPluginOpts.d.ts +24 -3
  49. package/lib/src/structures/ITtscPositionQuery.d.ts +21 -4
  50. package/lib/src/structures/ITtscReleaseSnapshotResult.d.ts +22 -2
  51. package/lib/src/structures/ITtscResult.d.ts +31 -5
  52. package/lib/src/structures/ITtscSnapshotHandle.d.ts +19 -1
  53. package/lib/src/structures/ITtscSnapshotResult.d.ts +22 -1
  54. package/lib/src/structures/ITtscSnapshotsResult.d.ts +19 -1
  55. package/lib/src/structures/ITtscSourceFileTextResult.d.ts +19 -1
  56. package/lib/src/structures/ITtscSourceFilesResult.d.ts +23 -2
  57. package/lib/src/structures/ITtscSymbolAtPositionResult.d.ts +18 -1
  58. package/lib/src/structures/ITtscSymbolDeclaration.d.ts +25 -2
  59. package/lib/src/structures/ITtscSymbolInfo.d.ts +19 -1
  60. package/lib/src/structures/ITtscTransformResult.d.ts +21 -3
  61. package/lib/src/structures/ITtscTypeAtPositionResult.d.ts +18 -1
  62. package/lib/src/structures/ITtscTypeInfo.d.ts +19 -1
  63. package/lib/src/structures/ITtscVersion.d.ts +28 -1
  64. package/lib/src/structures/IWasmExecFS.d.ts +310 -0
  65. package/package.json +4 -2
  66. package/shim-vendor/shim/ast/enums_gen.go +4 -2
  67. package/shim-vendor/shim/ast/go.mod +5 -5
  68. package/shim-vendor/shim/ast/go.sum +10 -10
  69. package/shim-vendor/shim/ast/lint.go +1088 -20
  70. package/shim-vendor/shim/ast/parent.go +38 -22
  71. package/shim-vendor/shim/ast/safe_text.go +48 -26
  72. package/shim-vendor/shim/ast/shim.go +1307 -20
  73. package/shim-vendor/shim/ast/test/next_jsdoc_comment_location_reaches_variable_statement_test.go +51 -0
  74. package/shim-vendor/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +24 -9
  75. package/shim-vendor/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +9 -5
  76. package/shim-vendor/shim/ast/test/node_text_returns_empty_for_nil_test.go +13 -5
  77. package/shim-vendor/shim/ast/test/node_text_returns_identifier_text_test.go +12 -5
  78. package/shim-vendor/shim/ast/test/node_text_skips_empty_qualified_left_test.go +35 -5
  79. package/shim-vendor/shim/astnav/go.mod +5 -5
  80. package/shim-vendor/shim/astnav/go.sum +12 -10
  81. package/shim-vendor/shim/astnav/shim.go +9 -0
  82. package/shim-vendor/shim/bundled/go.mod +7 -1
  83. package/shim-vendor/shim/bundled/go.sum +12 -0
  84. package/shim-vendor/shim/checker/enums_gen.go +4 -2
  85. package/shim-vendor/shim/checker/extra-shim.json +6 -0
  86. package/shim-vendor/shim/checker/go.mod +5 -5
  87. package/shim-vendor/shim/checker/go.sum +10 -10
  88. package/shim-vendor/shim/checker/node_builder_fields_gen.go +19 -0
  89. package/shim-vendor/shim/checker/shim.go +827 -114
  90. package/shim-vendor/shim/compiler/emit_sourcemap.go +68 -29
  91. package/shim-vendor/shim/compiler/emitassemble.go +36 -2
  92. package/shim-vendor/shim/compiler/emithook.go +13 -3
  93. package/shim-vendor/shim/compiler/go.mod +5 -5
  94. package/shim-vendor/shim/compiler/go.sum +10 -10
  95. package/shim-vendor/shim/compiler/incremental.go +56 -20
  96. package/shim-vendor/shim/compiler/resolution.go +92 -9
  97. package/shim-vendor/shim/core/enums_gen.go +2 -2
  98. package/shim-vendor/shim/core/go.mod +6 -1
  99. package/shim-vendor/shim/core/go.sum +10 -0
  100. package/shim-vendor/shim/core/modulekind.go +9 -0
  101. package/shim-vendor/shim/core/shim.go +157 -6
  102. package/shim-vendor/shim/diagnosticwriter/go.mod +10 -1
  103. package/shim-vendor/shim/diagnosticwriter/go.sum +20 -0
  104. package/shim-vendor/shim/diagnosticwriter/lint.go +188 -10
  105. package/shim-vendor/shim/diagnosticwriter/shim.go +14 -0
  106. package/shim-vendor/shim/lsp/go.mod +5 -5
  107. package/shim-vendor/shim/lsp/go.sum +10 -10
  108. package/shim-vendor/shim/lsp/shim.go +78 -2
  109. package/shim-vendor/shim/parser/go.mod +10 -1
  110. package/shim-vendor/shim/parser/go.sum +22 -0
  111. package/shim-vendor/shim/printer/emithost.go +46 -1
  112. package/shim-vendor/shim/printer/go.mod +5 -5
  113. package/shim-vendor/shim/printer/go.sum +10 -10
  114. package/shim-vendor/shim/printer/shim.go +114 -7
  115. package/shim-vendor/shim/printer/sourcemap.go +55 -0
  116. package/shim-vendor/shim/scanner/go.mod +10 -1
  117. package/shim-vendor/shim/scanner/go.sum +20 -0
  118. package/shim-vendor/shim/scanner/regexp.go +19 -4
  119. package/shim-vendor/shim/scanner/shim.go +3 -0
  120. package/shim-vendor/shim/stringutil/extra-shim.json +1 -0
  121. package/shim-vendor/shim/stringutil/go.mod +5 -0
  122. package/shim-vendor/shim/stringutil/go.sum +2 -0
  123. package/shim-vendor/shim/stringutil/shim.go +119 -0
  124. package/shim-vendor/shim/transformers/go.mod +5 -5
  125. package/shim-vendor/shim/transformers/go.sum +12 -10
  126. package/shim-vendor/shim/transformers/shim.go +12 -3
  127. package/shim-vendor/shim/tsoptions/command_line_raw.go +44 -0
  128. package/shim-vendor/shim/tsoptions/go.mod +10 -1
  129. package/shim-vendor/shim/tsoptions/go.sum +22 -0
  130. package/shim-vendor/shim/tsoptions/shim.go +1 -1
  131. package/shim-vendor/shim/tspath/go.mod +1 -1
  132. package/shim-vendor/shim/tspath/go.sum +6 -0
  133. package/shim-vendor/shim/vfs/cachedvfs/go.mod +3 -1
  134. package/shim-vendor/shim/vfs/cachedvfs/go.sum +12 -0
  135. package/shim-vendor/shim/vfs/decode.go +26 -0
  136. package/shim-vendor/shim/vfs/decode_test.go +42 -0
  137. package/shim-vendor/shim/vfs/go.mod +7 -1
  138. package/shim-vendor/shim/vfs/go.sum +12 -0
  139. package/shim-vendor/shim/vfs/osvfs/go.mod +7 -1
  140. package/shim-vendor/shim/vfs/osvfs/go.sum +12 -0
  141. package/src/BootTtscWorkerTerminationError.ts +22 -0
  142. package/src/MemFSError.ts +29 -2
  143. package/src/bootTtsc.ts +104 -28
  144. package/src/createMemFS.ts +291 -64
  145. package/src/parseResult.ts +19 -0
  146. package/src/structures/IBootResult.ts +21 -2
  147. package/src/structures/IBootTtscOptions.ts +32 -6
  148. package/src/structures/IFileStats.ts +65 -0
  149. package/src/structures/IMemFSHost.ts +97 -0
  150. package/src/structures/ITtscApi.ts +154 -9
  151. package/src/structures/ITtscBuildOpts.ts +19 -1
  152. package/src/structures/ITtscCompileResult.ts +21 -2
  153. package/src/structures/ITtscDiagnostic.ts +32 -1
  154. package/src/structures/ITtscFileQuery.ts +19 -1
  155. package/src/structures/ITtscFountainDiagnosticsResult.ts +20 -1
  156. package/src/structures/ITtscNodeAtPositionResult.ts +18 -1
  157. package/src/structures/ITtscNodeInfo.ts +27 -2
  158. package/src/structures/ITtscPluginOpts.ts +28 -3
  159. package/src/structures/ITtscPositionQuery.ts +21 -4
  160. package/src/structures/ITtscReleaseSnapshotResult.ts +22 -2
  161. package/src/structures/ITtscResult.ts +34 -5
  162. package/src/structures/ITtscSnapshotHandle.ts +19 -1
  163. package/src/structures/ITtscSnapshotResult.ts +22 -1
  164. package/src/structures/ITtscSnapshotsResult.ts +19 -1
  165. package/src/structures/ITtscSourceFileTextResult.ts +19 -1
  166. package/src/structures/ITtscSourceFilesResult.ts +23 -2
  167. package/src/structures/ITtscSymbolAtPositionResult.ts +18 -1
  168. package/src/structures/ITtscSymbolDeclaration.ts +27 -2
  169. package/src/structures/ITtscSymbolInfo.ts +23 -1
  170. package/src/structures/ITtscTransformResult.ts +22 -3
  171. package/src/structures/ITtscTypeAtPositionResult.ts +18 -1
  172. package/src/structures/ITtscTypeInfo.ts +20 -1
  173. package/src/structures/ITtscVersion.ts +33 -1
  174. package/src/structures/IWasmExecFS.ts +336 -0
package/host/api.go CHANGED
@@ -16,50 +16,22 @@ import (
16
16
  "github.com/samchon/ttsc/packages/ttsc/driver"
17
17
  )
18
18
 
19
- // APIResult is the stdout/stderr capture returned by InvokePlugin. The
20
- // js/wasm binding wraps it in the same JS result envelope that build/check/
21
- // transform use, adding `result` when an endpoint has a JSON payload. `code`
22
- // follows the native CLI exit-code contract (0 success, 2 compiler/config/
23
- // usage error, 3 runtime error).
24
- type APIResult struct {
25
- Code int `json:"code"`
26
- Stdout string `json:"stdout"`
27
- Stderr string `json:"stderr"`
28
- }
29
-
30
- // CompileResult mirrors `ttsc api-compile`. Field names are TypeScript-style
31
- // so JS callers can use it directly without remapping.
32
- type CompileResult struct {
33
- Diagnostics []CompileDiagnostic `json:"diagnostics,omitempty"`
34
- // Output maps cwd-relative paths to emitted JS / d.ts text. Empty when
35
- // the program produced no files (e.g. `noEmit` projects).
36
- Output map[string]string `json:"output"`
37
- }
38
-
39
- // TransformResult is the no-emit companion. The `typescript` map keys files
40
- // the same way `output` does in compile mode.
41
- type TransformResult struct {
42
- Diagnostics []CompileDiagnostic `json:"diagnostics,omitempty"`
43
- TypeScript map[string]string `json:"typescript"`
44
- }
45
-
46
- // CompileDiagnostic is the public TypeScript-side diagnostic DTO. Mirrors the
47
- // shape `ttsc api-compile` writes so the JS host can render it without
48
- // remapping fields.
49
- type CompileDiagnostic struct {
50
- File *string `json:"file"`
51
- Category string `json:"category"`
52
- Code int32 `json:"code"`
53
- Start *int `json:"start,omitempty"`
54
- Length *int `json:"length,omitempty"`
55
- Line int `json:"line,omitempty"`
56
- Character int `json:"character,omitempty"`
57
- MessageText string `json:"messageText"`
58
- }
59
-
60
19
  // Build runs `ttsc build`-shaped emit: load the project, emit JS, return the
61
20
  // emitted text map + diagnostics as JSON. `cwd` must be an absolute path
62
21
  // inside the host filesystem; `tsconfigPath` may be relative to cwd.
22
+ //
23
+ // The loaded Program is closed on every return after acquisition. Compiler
24
+ // diagnostics still accompany emitted files; code 2 reports compiler errors,
25
+ // while code 3 reports emit or serialization failures.
26
+ //
27
+ // @evidence contracts/common.md#principled-implementation LoadProgram and EmitAll own compiler behavior; encoding/json projects their results through the documented envelope.
28
+ // @evidence contracts/common.md#clear-and-simple-design One emit pipeline owns project acquisition, supported WriteFile capture and diagnostics; shared projection helpers centralize path and diagnostic policy.
29
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Emit uses supported compiler hooks, without patching foreign emitters or synthesizing expected outputs.
30
+ // @evidence contracts/common.md#meaningful-documentation Native paragraphs explain paths, ownership and failure stages under the documentation skill's rationale guidance.
31
+ // @evidence contracts/portability.md#os-neutral-implementation LoadProgram owns filesystem access through the driver's supported host. Native filepath.Rel identifies inside/outside destinations, including volume differences, and ToSlash changes JSON spelling without lowercasing identity.
32
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Build closes its Program before returning and transfers the output to the caller, so it retains nothing.
33
+ // @evidenceExclude contracts/performance.md#efficient-algorithms Build delegates parsing, checking and emit to the driver and adds one pass over the diagnostics and the emitted output map.
34
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work Build loads a fresh Program per call because the virtual filesystem may have changed; sharing a loaded Program is the snapshot verbs' purpose.
63
35
  func Build(cwd, tsconfigPath string) ([]byte, int, error) {
64
36
  prog, diags, err := driver.LoadProgram(cwd, tsconfigPath, driver.LoadProgramOptions{
65
37
  ForceEmit: true,
@@ -106,6 +78,15 @@ func Build(cwd, tsconfigPath string) ([]byte, int, error) {
106
78
 
107
79
  // Check runs the typecheck pipeline without emitting JS. The returned JSON
108
80
  // has the same shape as Build but with an empty `output` map.
81
+ //
82
+ // @evidence contracts/common.md#principled-implementation ForceNoEmit selects the driver's check pipeline and a deferred Close owns the loaded program.
83
+ // @evidence contracts/common.md#clear-and-simple-design The no-emit entry keeps loading and diagnostic projection explicit while sharing DTO and path helpers with the emit entry.
84
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts No-emit is requested from the compiler rather than hiding an unnecessary emit behind an empty result.
85
+ // @evidence contracts/common.md#meaningful-documentation Native comments explain no-emit and payload equivalence under the documentation skill's context guidance.
86
+ // @evidence contracts/portability.md#os-neutral-implementation The driver resolves cwd/config through its filesystem host; diagnostic paths use filepath.ToSlash only for wire spelling. The adapter neither guesses case policy nor constructs a shell command.
87
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Check closes its Program before returning and transfers the diagnostics to the caller, so it retains nothing.
88
+ // @evidenceExclude contracts/performance.md#efficient-algorithms Check delegates loading and checking to the driver and adds one pass over the diagnostics.
89
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work Check loads a fresh Program per call because the virtual filesystem may have changed; sharing a loaded Program is the snapshot verbs' purpose.
109
90
  func Check(cwd, tsconfigPath string) ([]byte, int, error) {
110
91
  prog, diags, err := driver.LoadProgram(cwd, tsconfigPath, driver.LoadProgramOptions{
111
92
  ForceNoEmit: true,
@@ -138,6 +119,18 @@ func Check(cwd, tsconfigPath string) ([]byte, int, error) {
138
119
  // Transform returns every source file the program saw, keyed by
139
120
  // project-relative path. The playground uses this to render the TS view
140
121
  // after source rewrites (e.g. paths rewriting) have been applied.
122
+ //
123
+ // Sources inside cwd use relative keys; outside sources retain absolute slash
124
+ // paths. The invocation closes the loaded Program after copying its source text.
125
+ //
126
+ // @evidence contracts/common.md#principled-implementation LoadProgram supplies post-plugin SourceFiles and encoding/json preserves their source-text projection.
127
+ // @evidence contracts/common.md#clear-and-simple-design The source projection is separate from emit, and common path/diagnostic helpers keep their shared representation policy in one place.
128
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The program's own text is authoritative, without a secondary emitter-based reconstruction.
129
+ // @evidence contracts/common.md#meaningful-documentation Separate paragraphs explain stage, path convention and ownership under the documentation skill.
130
+ // @evidence contracts/portability.md#os-neutral-implementation Source identities come from LoadProgram, then the shared filepath relative/absolute mapping preserves native case and handles outside/other-volume paths before slash serialization.
131
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Transform closes its Program after copying the source text, so it retains nothing.
132
+ // @evidenceExclude contracts/performance.md#efficient-algorithms Transform delegates loading to the driver and adds one pass over the source files and diagnostics.
133
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work Transform loads a fresh Program per call because the virtual filesystem may have changed; sharing a loaded Program is the snapshot verbs' purpose.
141
134
  func Transform(cwd, tsconfigPath string) ([]byte, int, error) {
142
135
  prog, diags, err := driver.LoadProgram(cwd, tsconfigPath, driver.LoadProgramOptions{
143
136
  ForceNoEmit: true,
package/host/doc.go CHANGED
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // A consumer wasm looks like this:
5
5
  //
6
- // //go:build js
6
+ // //go:build js && wasm
7
7
  // package main
8
8
  //
9
9
  // import (
@@ -12,14 +12,15 @@
12
12
  // )
13
13
  //
14
14
  // func main() {
15
- // // Expose never returns; it installs the JS API and blocks forever.
15
+ // // A valid registration installs the JS API until Worker termination.
16
16
  // host.Expose("yourApi", host.Config{
17
17
  // Plugins: []host.Plugin{yourplugin.New()},
18
18
  // })
19
19
  // }
20
20
  //
21
21
  // Expose binds `globalThis[name]` to an object that exposes ttsc's base
22
- // project commands (build, check, transform, version) plus
22
+ // project commands (build, check, transform, version), the retained-program
23
+ // queries (snapshot and the get* verbs), `plugins()`, and
23
24
  // `plugin({ name, command, ...opts })`, which routes into a Plugin's
24
25
  // CLI-shaped Run callback. Every async endpoint returns the JS result envelope;
25
26
  // build/check/transform place JSON payloads in `result`, while plugin output
@@ -27,7 +28,7 @@
27
28
  // contract — the same shape the native sidecars implement — so the wasm and
28
29
  // the native CLI can share their Run* entry points byte-for-byte.
29
30
  //
30
- // The package is browser-agnostic: every JS-facing helper sits behind
31
- // //go:build js, so a consumer can `go build ./...` natively without GOOS=js
32
- // to keep CI happy.
31
+ // JavaScript bindings use //go:build js && wasm. A native build selects a
32
+ // no-op Expose with the same signature and the shared project/plugin helpers;
33
+ // checking the bodies of js/wasm-only files requires a js/wasm build.
33
34
  package host
package/host/expose.go ADDED
@@ -0,0 +1,42 @@
1
+ package host
2
+
3
+ // API stability: experimental until v1.0; signatures may change between
4
+ // minor releases. Pin exact versions in production playgrounds.
5
+ //
6
+ // Expose installs `globalThis[apiName]` with the base API endpoints and the
7
+ // plugin dispatcher, then keeps the Go runtime alive until Worker termination.
8
+ // Invalid or duplicate registration reports failure and returns before binding.
9
+ // A native build (without js/wasm) selects an alternative that installs nothing
10
+ // and returns immediately, so native entrypoints can import the package.
11
+ //
12
+ // The contract:
13
+ //
14
+ // - globalThis[apiName].version() → { version, commit, date, go, goos, goarch }
15
+ // - globalThis[apiName].build({ cwd, tsconfig }) → Promise<ITtscResult>
16
+ // - globalThis[apiName].check({ cwd, tsconfig }) → Promise<ITtscResult>
17
+ // - globalThis[apiName].transform({ cwd, tsconfig }) → Promise<ITtscResult>
18
+ // - globalThis[apiName].plugin({ name, command, ...opts}) → Promise<ITtscResult>
19
+ // - globalThis[apiName].plugins() → string[] of registered names
20
+ //
21
+ // The retained-program verbs (snapshot, releaseSnapshot, snapshots,
22
+ // getSourceFiles, getSourceFileText, getDiagnostics, getNodeAtPosition,
23
+ // getTypeAtPosition, getSymbolAtPosition) are installed beside them and return
24
+ // the same envelope.
25
+ //
26
+ // build/check/transform encode their structured payloads as JSON in
27
+ // ITtscResult.result. Plugin stdout/stderr are captured in the envelope streams.
28
+ //
29
+ // A matching readiness resolver is invoked: `globalThis[`${apiName}Ready`]`.
30
+ // JS callers register this BEFORE go.run begins so they can await wasm boot.
31
+ //
32
+ // @evidence contracts/common.md#principled-implementation syscall/js bindings and explicit Ready/Failed callbacks implement the Go wasm host protocol; an atomic gate owns one installation per instance, and build constraints select a no-op native alternative behind the same signature.
33
+ // @evidence contracts/common.md#clear-and-simple-design Expose owns one registration and readiness boundary; project adapters, plugin invocation and fountain queries remain named helpers with their own state instead of duplicating binding policy.
34
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Registered plugins use owned invocation streams rather than foreign global-output replacement; rejected duplicate installation is reported through the boot failure boundary.
35
+ // @evidence contracts/common.md#meaningful-documentation Native paragraphs explain setup order, payload channels and runtime lifetime under the documentation skill's context guidance.
36
+ // @evidence contracts/performance.md#efficient-algorithms Registration visits P supplied plugins once with map duplicate checks and creates a fixed set of endpoint bindings. Snapshot lookup uses a map; source lookup uses the driver's existing compiler index, with path normalization and hashing proportional to path length. A relative path may require a second indexed lookup. List/diagnostic queries traverse their requested results; parsing, checking and printing remain driver Program work.
37
+ // @evidence contracts/performance.md#reuse-equivalent-work Fountain queries reuse the explicitly acquired Program and its checker under one opaque handle; registry and entry locks keep queries within that snapshot's lifetime. Editing MemFS does not refresh an existing snapshot, so callers release and reacquire when they need a new project view rather than sharing by path alone.
38
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The wasm instance owns P plugin registrations, pinned js.Func bindings and its keepalive until Worker termination. Each acquired snapshot owns a Program; registry readers finish before release deletes and closes it. Snapshot count, Program bytes and concurrent invocation tasks have no fixed bound. Promise executors release their js.Func after settlement, and InvokePlugin owns its own streams and child work.
39
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation Expose binds a JavaScript global in the wasm runtime and performs no native filesystem, path or process operation; the native alternative does nothing.
40
+ func Expose(apiName string, cfg Config) {
41
+ expose(apiName, cfg)
42
+ }
package/host/fountain.go CHANGED
@@ -22,6 +22,7 @@ package host
22
22
  import (
23
23
  "encoding/json"
24
24
  "fmt"
25
+ "math"
25
26
  "path/filepath"
26
27
  "sync"
27
28
  "sync/atomic"
@@ -73,79 +74,234 @@ func fountainAPIMap() map[string]any {
73
74
  }
74
75
 
75
76
  // SnapshotResult is the response shape for `snapshot()`.
77
+ // The caller owns Handle and must pass it to releaseSnapshot after its queries.
78
+ //
79
+ // @evidence contracts/common.md#principled-implementation An opaque string projects registry identity without exposing a Go Program through JSON.
80
+ // @evidence contracts/common.md#clear-and-simple-design One opaque identity transfers query access while the Program and its ownership remain in the native registry.
81
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Identity refers to an actual retained program rather than a reconstructed project path.
82
+ // @evidence contracts/common.md#meaningful-documentation The Go comment explains opaque identity and caller release ownership under the documentation skill.
83
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources SnapshotResult is a data type and acquires no handle, task or retained state.
84
+ // @evidenceExclude contracts/performance.md#efficient-algorithms SnapshotResult is a data type and chooses no algorithm or processing strategy.
85
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work SnapshotResult is a data type and coordinates no shared or repeated computation.
86
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation SnapshotResult is a data type and performs no native filesystem, path or process operation.
76
87
  type SnapshotResult struct {
88
+ // Handle is opaque and valid until released in this wasm instance.
77
89
  Handle string `json:"handle"`
78
90
  }
79
91
 
80
92
  // ReleaseSnapshotResult is the response shape for `releaseSnapshot()`.
93
+ //
94
+ // @evidence contracts/common.md#principled-implementation A boolean distinguishes a removed registry entry from idempotent absent-handle release.
95
+ // @evidence contracts/common.md#clear-and-simple-design One outcome bit reports removal without duplicating the request handle or native cleanup state.
96
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts An absent entry is not reported as a fabricated successful removal.
97
+ // @evidence contracts/common.md#meaningful-documentation Native comments explain the release outcome under the documentation skill's absence guidance.
98
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources ReleaseSnapshotResult is a data type and acquires no handle, task or retained state.
99
+ // @evidenceExclude contracts/performance.md#efficient-algorithms ReleaseSnapshotResult is a data type and chooses no algorithm or processing strategy.
100
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work ReleaseSnapshotResult is a data type and coordinates no shared or repeated computation.
101
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation ReleaseSnapshotResult is a data type and performs no native filesystem, path or process operation.
81
102
  type ReleaseSnapshotResult struct {
103
+ // Released is false when the handle was never present or already removed.
82
104
  Released bool `json:"released"`
83
105
  }
84
106
 
85
107
  // ListSnapshotsResult is the response shape for `snapshots()`.
108
+ //
109
+ // @evidence contracts/common.md#principled-implementation A string slice projects registry identities without a second public program representation.
110
+ // @evidence contracts/common.md#clear-and-simple-design A flat current-handle list exposes membership without expanding each entry into an unused snapshot descriptor.
111
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Current map membership supplies the list rather than a history of expected handles.
112
+ // @evidence contracts/common.md#meaningful-documentation Native member comments identify live state and unspecified order under the documentation skill.
113
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources ListSnapshotsResult is a data type and acquires no handle, task or retained state.
114
+ // @evidenceExclude contracts/performance.md#efficient-algorithms ListSnapshotsResult is a data type and chooses no algorithm or processing strategy.
115
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work ListSnapshotsResult is a data type and coordinates no shared or repeated computation.
116
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation ListSnapshotsResult is a data type and performs no native filesystem, path or process operation.
86
117
  type ListSnapshotsResult struct {
118
+ // Handles lists live registry keys in unspecified order; empty encodes as [].
87
119
  Handles []string `json:"handles"`
88
120
  }
89
121
 
90
122
  // GetSourceFilesResult is the response shape for `getSourceFiles()`.
123
+ //
124
+ // @evidence contracts/common.md#principled-implementation The program's SourceFiles are projected into path strings through the shared output-key policy.
125
+ // @evidence contracts/common.md#clear-and-simple-design File identities are separate from source text and semantic metadata, keeping listing focused on program membership.
126
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Compiler membership determines files instead of a guessed filesystem glob.
127
+ // @evidence contracts/common.md#meaningful-documentation The member comment explains path bases and declaration exclusion under the documentation skill.
128
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources GetSourceFilesResult is a data type and acquires no handle, task or retained state.
129
+ // @evidenceExclude contracts/performance.md#efficient-algorithms GetSourceFilesResult is a data type and chooses no algorithm or processing strategy.
130
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work GetSourceFilesResult is a data type and coordinates no shared or repeated computation.
131
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation GetSourceFilesResult is a data type and performs no native filesystem, path or process operation.
91
132
  type GetSourceFilesResult struct {
133
+ // Files excludes declarations; paths outside cwd remain absolute slash paths.
92
134
  Files []string `json:"files"`
93
135
  }
94
136
 
95
137
  // GetSourceFileTextResult is the response shape for `getSourceFileText()`.
138
+ //
139
+ // @evidence contracts/common.md#principled-implementation A required string projects program text while file lookup failures use the error envelope.
140
+ // @evidence contracts/common.md#clear-and-simple-design One text field represents the selected file; request identity and transport status remain in their existing shapes.
141
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Text comes from the snapshot instead of a separately read file that may have changed.
142
+ // @evidence contracts/common.md#meaningful-documentation The member comment names retained-program provenance under the documentation skill's context guidance.
143
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources GetSourceFileTextResult is a data type and acquires no handle, task or retained state.
144
+ // @evidenceExclude contracts/performance.md#efficient-algorithms GetSourceFileTextResult is a data type and chooses no algorithm or processing strategy.
145
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work GetSourceFileTextResult is a data type and coordinates no shared or repeated computation.
146
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation GetSourceFileTextResult is a data type and performs no native filesystem, path or process operation.
96
147
  type GetSourceFileTextResult struct {
148
+ // Text is the source currently held by the retained Program, including rewrites.
97
149
  Text string `json:"text"`
98
150
  }
99
151
 
100
152
  // GetDiagnosticsResult is the response shape for `getDiagnostics()`.
153
+ //
154
+ // @evidence contracts/common.md#principled-implementation The shared CompileDiagnostic slice preserves the compiler's public diagnostic projection.
155
+ // @evidence contracts/common.md#clear-and-simple-design The selected messages reuse the compile diagnostic DTO rather than introducing a snapshot-specific message model.
156
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Queries return compiler messages rather than source-pattern approximations of diagnostics.
157
+ // @evidence contracts/common.md#meaningful-documentation Native comments identify the payload and empty-array meaning under the documentation skill.
158
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources GetDiagnosticsResult is a data type and acquires no handle, task or retained state.
159
+ // @evidenceExclude contracts/performance.md#efficient-algorithms GetDiagnosticsResult is a data type and chooses no algorithm or processing strategy.
160
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work GetDiagnosticsResult is a data type and coordinates no shared or repeated computation.
161
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation GetDiagnosticsResult is a data type and performs no native filesystem, path or process operation.
101
162
  type GetDiagnosticsResult struct {
163
+ // Diagnostics is an initialized slice, so no selected messages encodes as [].
102
164
  Diagnostics []CompileDiagnostic `json:"diagnostics"`
103
165
  }
104
166
 
105
167
  // NodeInfo is the serialized AST node returned by `getNodeAtPosition`.
168
+ //
169
+ // @evidence contracts/common.md#principled-implementation Kind metadata and native byte ranges project a token without exposing Go AST objects.
170
+ // @evidence contracts/common.md#clear-and-simple-design A flat kind/range/spelling value carries syntax information without mutable AST links or another token hierarchy.
171
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Native ranges remain byte coordinates rather than guessed JavaScript character indices.
172
+ // @evidence contracts/common.md#meaningful-documentation Member comments explain native identity, units and optional text under the documentation skill.
173
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources NodeInfo is a data type and acquires no handle, task or retained state.
174
+ // @evidenceExclude contracts/performance.md#efficient-algorithms NodeInfo is a data type and chooses no algorithm or processing strategy.
175
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work NodeInfo is a data type and coordinates no shared or repeated computation.
176
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation NodeInfo is a data type and performs no native filesystem, path or process operation.
106
177
  type NodeInfo struct {
107
- Kind int `json:"kind"`
178
+ // Kind is the numeric TypeScript-Go AST kind.
179
+ Kind int `json:"kind"`
180
+
181
+ // KindName is its human-readable Stringer name.
108
182
  KindName string `json:"kindName"`
109
- Pos int `json:"pos"`
110
- End int `json:"end"`
111
- Text string `json:"text,omitempty"`
183
+
184
+ // Pos is the inclusive UTF-8 byte offset of the token's first byte, after any
185
+ // leading whitespace and comments.
186
+ Pos int `json:"pos"`
187
+
188
+ // End is the exclusive UTF-8 byte offset.
189
+ End int `json:"end"`
190
+
191
+ // Text is omitted when the scanner has no nonempty source spelling.
192
+ Text string `json:"text,omitempty"`
112
193
  }
113
194
 
114
195
  // GetNodeAtPositionResult is the response shape for `getNodeAtPosition()`.
196
+ //
197
+ // @evidence contracts/common.md#principled-implementation A nullable pointer distinguishes successful absence from the query's error envelope.
198
+ // @evidence contracts/common.md#clear-and-simple-design One nullable token projection conveys lookup outcome without another status flag or duplicated source identity.
199
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts No fabricated node fills whitespace or an absent token.
200
+ // @evidence contracts/common.md#meaningful-documentation The member comment explains null token absence under the documentation skill.
201
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources GetNodeAtPositionResult is a data type and acquires no handle, task or retained state.
202
+ // @evidenceExclude contracts/performance.md#efficient-algorithms GetNodeAtPositionResult is a data type and chooses no algorithm or processing strategy.
203
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work GetNodeAtPositionResult is a data type and coordinates no shared or repeated computation.
204
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation GetNodeAtPositionResult is a data type and performs no native filesystem, path or process operation.
115
205
  type GetNodeAtPositionResult struct {
206
+ // Node encodes as null when no syntax token touches the queried position.
116
207
  Node *NodeInfo `json:"node"`
117
208
  }
118
209
 
119
210
  // TypeInfo is the serialized type returned by `getTypeAtPosition`.
211
+ //
212
+ // @evidence contracts/common.md#principled-implementation Checker-printed text and native flags avoid a parallel incomplete public type model.
213
+ // @evidence contracts/common.md#clear-and-simple-design Presentation text and native classification bits are sufficient for this query; structural compiler types stay behind the checker boundary.
214
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The checker supplies type presentation rather than inferring it from token spelling.
215
+ // @evidence contracts/common.md#meaningful-documentation Native comments identify printing authority and flag provenance under the documentation skill.
216
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources TypeInfo is a data type and acquires no handle, task or retained state.
217
+ // @evidenceExclude contracts/performance.md#efficient-algorithms TypeInfo is a data type and chooses no algorithm or processing strategy.
218
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work TypeInfo is a data type and coordinates no shared or repeated computation.
219
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation TypeInfo is a data type and performs no native filesystem, path or process operation.
120
220
  type TypeInfo struct {
121
- Text string `json:"text"`
122
- Flags int `json:"flags"`
221
+ // Text is the checker's TypeToString presentation.
222
+ Text string `json:"text"`
223
+
224
+ // Flags is the numeric TypeScript-Go TypeFlags bitmask.
225
+ Flags int `json:"flags"`
123
226
  }
124
227
 
125
228
  // GetTypeAtPositionResult is the response shape for `getTypeAtPosition()`.
229
+ //
230
+ // @evidence contracts/common.md#principled-implementation Nullable semantic output mirrors the TypeScript request result without overloading an error type.
231
+ // @evidence contracts/common.md#clear-and-simple-design One nullable type field separates semantic absence from transport errors without duplicating the type metadata representation.
232
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Positions without type semantics remain absent rather than receiving a fabricated any type.
233
+ // @evidence contracts/common.md#meaningful-documentation The member comment explains null meaning under the documentation skill's absence guidance.
234
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources GetTypeAtPositionResult is a data type and acquires no handle, task or retained state.
235
+ // @evidenceExclude contracts/performance.md#efficient-algorithms GetTypeAtPositionResult is a data type and chooses no algorithm or processing strategy.
236
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work GetTypeAtPositionResult is a data type and coordinates no shared or repeated computation.
237
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation GetTypeAtPositionResult is a data type and performs no native filesystem, path or process operation.
126
238
  type GetTypeAtPositionResult struct {
239
+ // Type encodes as null when the touching token has no semantic type result.
127
240
  Type *TypeInfo `json:"type"`
128
241
  }
129
242
 
130
243
  // SymbolDeclaration is the serialized declaration site returned by
131
244
  // `getSymbolAtPosition`.
245
+ //
246
+ // @evidence contracts/common.md#principled-implementation Nullable source identity and native ranges preserve declaration provenance through JSON.
247
+ // @evidence contracts/common.md#clear-and-simple-design A declaration site contains only file identity and its byte interval; source text and compiler node ownership remain elsewhere.
248
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts A source-less declaration keeps nil identity rather than a placeholder filename.
249
+ // @evidence contracts/common.md#meaningful-documentation Member comments explain path identity and byte interval units under the documentation skill.
250
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources SymbolDeclaration is a data type and acquires no handle, task or retained state.
251
+ // @evidenceExclude contracts/performance.md#efficient-algorithms SymbolDeclaration is a data type and chooses no algorithm or processing strategy.
252
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work SymbolDeclaration is a data type and coordinates no shared or repeated computation.
253
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation SymbolDeclaration is a data type and performs no native filesystem, path or process operation.
132
254
  type SymbolDeclaration struct {
255
+ // File is a project-relative or outside absolute path, nil for source-less nodes.
133
256
  File *string `json:"file"`
134
- Pos int `json:"pos"`
135
- End int `json:"end"`
257
+
258
+ // Pos is the inclusive UTF-8 byte offset of the declaration's first token,
259
+ // after any leading whitespace, comments and JSDoc.
260
+ Pos int `json:"pos"`
261
+
262
+ // End is the declaration's exclusive UTF-8 byte offset.
263
+ End int `json:"end"`
136
264
  }
137
265
 
138
266
  // SymbolInfo is the serialized symbol returned by `getSymbolAtPosition`.
267
+ //
268
+ // @evidence contracts/common.md#principled-implementation Raw and printed names, flags and declaration projections preserve the native checker result.
269
+ // @evidence contracts/common.md#clear-and-simple-design Identity and display text are distinct, and one capped site list plus its original total explains truncation without separate declaration handles.
270
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The response cap preserves the original total rather than presenting a shortened declaration list as complete.
271
+ // @evidence contracts/common.md#meaningful-documentation Native comments distinguish internal names, display text and capped metadata under the documentation skill.
272
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources SymbolInfo is a data type and acquires no handle, task or retained state.
273
+ // @evidenceExclude contracts/performance.md#efficient-algorithms SymbolInfo is a data type and chooses no algorithm or processing strategy.
274
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work SymbolInfo is a data type and coordinates no shared or repeated computation.
275
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation SymbolInfo is a data type and performs no native filesystem, path or process operation.
139
276
  type SymbolInfo struct {
140
- Name string `json:"name"`
141
- Text string `json:"text,omitempty"`
142
- Flags int `json:"flags"`
143
- Declarations []SymbolDeclaration `json:"declarations,omitempty"`
144
- DeclarationCount int `json:"declarationCount,omitempty"`
277
+ // Name is the raw name, including TypeScript internal prefix markers.
278
+ Name string `json:"name"`
279
+
280
+ // Text is the optional checker-printed symbol representation.
281
+ Text string `json:"text,omitempty"`
282
+
283
+ // Flags is the numeric TypeScript-Go SymbolFlags bitmask.
284
+ Flags int `json:"flags"`
285
+
286
+ // Declarations contains at most 16 sites and is omitted when empty.
287
+ Declarations []SymbolDeclaration `json:"declarations,omitempty"`
288
+
289
+ // DeclarationCount retains the original total and is omitted when zero.
290
+ DeclarationCount int `json:"declarationCount,omitempty"`
145
291
  }
146
292
 
147
293
  // GetSymbolAtPositionResult is the response shape for `getSymbolAtPosition()`.
294
+ //
295
+ // @evidence contracts/common.md#principled-implementation A nullable symbol pointer distinguishes absent semantics from a failed request.
296
+ // @evidence contracts/common.md#clear-and-simple-design One nullable symbol field expresses the binding outcome while symbol metadata and request status remain separate.
297
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Missing binding does not produce a symbol guessed from the token's name.
298
+ // @evidence contracts/common.md#meaningful-documentation The member comment explains null result meaning under the documentation skill.
299
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources GetSymbolAtPositionResult is a data type and acquires no handle, task or retained state.
300
+ // @evidenceExclude contracts/performance.md#efficient-algorithms GetSymbolAtPositionResult is a data type and chooses no algorithm or processing strategy.
301
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work GetSymbolAtPositionResult is a data type and coordinates no shared or repeated computation.
302
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation GetSymbolAtPositionResult is a data type and performs no native filesystem, path or process operation.
148
303
  type GetSymbolAtPositionResult struct {
304
+ // Symbol encodes as null when the touching token has no associated symbol.
149
305
  Symbol *SymbolInfo `json:"symbol"`
150
306
  }
151
307
 
@@ -298,7 +454,7 @@ func jsGetDiagnostics(this js.Value, args []js.Value) any {
298
454
  func jsGetNodeAtPosition(this js.Value, args []js.Value) any {
299
455
  return withSnapshotPosition(args, func(_ *snapshotEntry, file *ast.SourceFile, pos int) any {
300
456
  node := tokenAtPosition(file, pos)
301
- return fountainOK(GetNodeAtPositionResult{Node: nodeInfoOf(node)})
457
+ return fountainOK(GetNodeAtPositionResult{Node: nodeInfoOf(file, node)})
302
458
  })
303
459
  }
304
460
 
@@ -391,17 +547,18 @@ func withSnapshotPosition(args []js.Value, fn func(*snapshotEntry, *ast.SourceFi
391
547
  if posVal.Type() != js.TypeNumber {
392
548
  return errorResponse(2, "host: \"position\" must be a number (byte offset)")
393
549
  }
394
- pos := posVal.Int()
395
- if pos < 0 {
396
- return errorResponse(2, "host: \"position\" must be non-negative")
550
+ offset := posVal.Float()
551
+ if math.IsNaN(offset) || math.IsInf(offset, 0) || math.Trunc(offset) != offset || offset < 0 {
552
+ return errorResponse(2, "host: \"position\" must be a finite non-negative integer byte offset")
397
553
  }
398
554
  file := resolveSnapshotFile(entry, path)
399
555
  if file == nil {
400
556
  return errorResponse(2, fmt.Sprintf("host: file %q not found in snapshot", path))
401
557
  }
402
- if pos >= len(file.Text()) {
403
- return errorResponse(2, fmt.Sprintf("host: \"position\" %d is outside file length %d", pos, len(file.Text())))
558
+ if offset >= float64(len(file.Text())) {
559
+ return errorResponse(2, fmt.Sprintf("host: \"position\" %v is outside file length %d", offset, len(file.Text())))
404
560
  }
561
+ pos := int(offset)
405
562
  return fn(entry, file, pos)
406
563
  })
407
564
  }
@@ -446,7 +603,7 @@ func resolveSnapshotFile(entry *snapshotEntry, path string) *ast.SourceFile {
446
603
  return nil
447
604
  }
448
605
 
449
- // snapshotFileKey mirrors apiOutputKey but is exported for use by all
606
+ // snapshotFileKey delegates to apiOutputKey for use by all
450
607
  // fountain endpoints that return file paths.
451
608
  func snapshotFileKey(cwd, fileName string) string {
452
609
  return apiOutputKey(cwd, fileName)
@@ -469,14 +626,18 @@ func fountainOK(payload any) any {
469
626
  }
470
627
 
471
628
  // nodeInfoOf converts a *ast.Node into the JSON-serializable NodeInfo.
472
- func nodeInfoOf(node *ast.Node) *NodeInfo {
629
+ //
630
+ // Pos is the token's first byte, after leading whitespace and comments.
631
+ // TypeScript-Go's Node.Pos is the full start, which includes that trivia, so
632
+ // reporting it would give an interval wider than the Text it describes.
633
+ func nodeInfoOf(file *ast.SourceFile, node *ast.Node) *NodeInfo {
473
634
  if node == nil {
474
635
  return nil
475
636
  }
476
637
  info := &NodeInfo{
477
638
  Kind: int(node.Kind),
478
639
  KindName: astKindName(node.Kind),
479
- Pos: node.Pos(),
640
+ Pos: tokenStart(file, node),
480
641
  End: node.End(),
481
642
  }
482
643
  if text := shimscanner.GetTextOfNode(node); text != "" {
@@ -516,6 +677,7 @@ func symbolInfoOf(entry *snapshotEntry, sym *ast.Symbol) *SymbolInfo {
516
677
  }
517
678
  item := SymbolDeclaration{Pos: d.Pos(), End: d.End()}
518
679
  if file := ast.GetSourceFileOfNode(d); file != nil {
680
+ item.Pos = tokenStart(file, d)
519
681
  key := snapshotFileKey(entry.cwd, file.FileName())
520
682
  item.File = &key
521
683
  }
@@ -527,6 +689,14 @@ func symbolInfoOf(entry *snapshotEntry, sym *ast.Symbol) *SymbolInfo {
527
689
  return info
528
690
  }
529
691
 
692
+ // tokenStart returns the byte offset of node's first token. It skips the leading
693
+ // whitespace and comments that TypeScript-Go's Node.Pos includes; JSDoc attached
694
+ // to a declaration is such a comment, so a declaration starts at its own first
695
+ // token rather than at its documentation.
696
+ func tokenStart(file *ast.SourceFile, node *ast.Node) int {
697
+ return shimscanner.GetTokenPosOfNode(node, file, false)
698
+ }
699
+
530
700
  // astKindName renders an ast.Kind to its string representation. ast.Kind has
531
701
  // a Stringer impl in tsgo (`%v` produces "FunctionDeclaration" etc.).
532
702
  func astKindName(k ast.Kind) string {
package/host/host.go CHANGED
@@ -3,8 +3,9 @@
3
3
  // JS-side bindings for the host package.
4
4
  //
5
5
  // `Expose` installs `globalThis[apiName]` with the base ttsc endpoints
6
- // (version, build, check, transform) plus `plugin({ name, command, ...opts })`,
7
- // routing into the consumer's registered plugins. Every async endpoint returns
6
+ // (version, build, check, transform), the retained-program verbs of
7
+ // fountain.go, and `plugin({ name, command, ...opts })`, routing into the
8
+ // consumer's registered plugins. Every async endpoint returns
8
9
  // the JS result envelope so callers can build a single boot helper that works
9
10
  // against any host-built binary.
10
11
  package host
@@ -14,6 +15,7 @@ import (
14
15
  "fmt"
15
16
  "os"
16
17
  "runtime"
18
+ "strconv"
17
19
  "sync/atomic"
18
20
  "syscall/js"
19
21
  "time"
@@ -27,31 +29,12 @@ var (
27
29
  date = "unknown"
28
30
  )
29
31
 
30
- // exposed gates Expose so a duplicate call panics instead of leaking the
32
+ // exposed gates Expose so a duplicate call reports failure instead of leaking the
31
33
  // previous batch of js.Funcs + spawning a second keepalive goroutine.
32
34
  var exposed atomic.Bool
33
35
 
34
- // API stability: experimental until v1.0; signatures may change between
35
- // minor releases. Pin exact versions in production playgrounds.
36
- //
37
- // Expose installs `globalThis[apiName]` with the base API endpoints and the
38
- // plugin dispatcher, then keeps the Go runtime alive forever.
39
- //
40
- // The contract:
41
- //
42
- // - globalThis[apiName].version() → version banner
43
- // - globalThis[apiName].build({ cwd, tsconfig }) → Promise<ITtscResult>
44
- // - globalThis[apiName].check({ cwd, tsconfig }) → Promise<ITtscResult>
45
- // - globalThis[apiName].transform({ cwd, tsconfig }) → Promise<ITtscResult>
46
- // - globalThis[apiName].plugin({ name, command, ...opts}) → Promise<ITtscResult>
47
- // - globalThis[apiName].plugins() → string[] of registered names
48
- //
49
- // build/check/transform encode their structured payloads as JSON in
50
- // ITtscResult.result. Plugin stdout/stderr are captured in the envelope streams.
51
- //
52
- // A matching readiness resolver is invoked: `globalThis[`${apiName}Ready`]`.
53
- // JS callers register this BEFORE go.run begins so they can await wasm boot.
54
- func Expose(apiName string, cfg Config) {
36
+ // expose is the js/wasm implementation behind Expose.
37
+ func expose(apiName string, cfg Config) {
55
38
  // Refuse double-Expose: the second call would leak every js.FuncOf from
56
39
  // the first batch (Go pins js.Funcs and they're not GC'd), spin a second
57
40
  // keepalive goroutine forever, and overwrite the ready resolver so JS
@@ -258,7 +241,9 @@ func buildPluginArgv(opts js.Value) []string {
258
241
  out = append(out, "--"+key)
259
242
  }
260
243
  case js.TypeNumber:
261
- out = append(out, fmt.Sprintf("--%s=%v", key, value.Float()))
244
+ // 'f' with the shortest precision that round-trips keeps an integer such
245
+ // as 1234567 as "1234567"; `%v` would spell it "1.234567e+06".
246
+ out = append(out, "--"+key+"="+strconv.FormatFloat(value.Float(), 'f', -1, 64))
262
247
  }
263
248
  }
264
249
  return out
@@ -5,11 +5,9 @@
5
5
  // without GOOS=js, which native CI jobs need.
6
6
  package host
7
7
 
8
- // Expose is a no-op on native targets. Consumers can `go build ./...` to
9
- // type-check their wasm entry point without GOOS=js; the function symbol
10
- // stays in scope but the body short-circuits so it's safe to call from a
11
- // non-wasm sanity-test entrypoint.
12
- func Expose(apiName string, cfg Config) {
8
+ // expose is the native alternative behind Expose: it installs no JavaScript API
9
+ // and returns immediately.
10
+ func expose(apiName string, cfg Config) {
13
11
  _ = apiName
14
12
  _ = cfg
15
13
  }