dsh-ptc-plus 0.3.2 → 0.4.2

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 (206) hide show
  1. package/README.en.md +63 -80
  2. package/README.md +63 -80
  3. package/assets/ptc-plus-bindings-en.png +0 -0
  4. package/assets/ptc-plus-bindings-zh.png +0 -0
  5. package/assets/ptc-plus-repair-en.png +0 -0
  6. package/assets/ptc-plus-repair-zh.png +0 -0
  7. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  8. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  9. package/assets/ptc-plus-settings-en.png +0 -0
  10. package/assets/ptc-plus-settings-zh.png +0 -0
  11. package/client.js +30827 -742
  12. package/compiler-amaro.cjs +5 -0
  13. package/compiler-amaro.cjs.map +6 -0
  14. package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
  15. package/compiler-assets.json +3 -0
  16. package/compiler-core.cjs +556 -0
  17. package/compiler-core.cjs.map +6 -0
  18. package/compiler-platform.cjs +2 -0
  19. package/compiler-platform.cjs.map +6 -0
  20. package/compiler-typescript.cjs +388 -0
  21. package/compiler-typescript.cjs.map +6 -0
  22. package/docs/README.md +7 -0
  23. package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
  24. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +5 -3
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
  26. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
  27. package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
  28. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
  29. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
  30. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +3 -1
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
  32. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
  33. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  34. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
  35. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
  36. package/docs/adr/0019-plugin-settings-and-kill-switch.md +5 -3
  37. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  38. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
  39. package/docs/adr/0023-global-user-bindings.md +104 -0
  40. package/docs/adr/0024-repl-console-observation.md +33 -0
  41. package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
  42. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
  43. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
  44. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
  45. package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
  46. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
  47. package/docs/architecture.md +77 -21
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +95 -19
  51. package/docs/durability-design.md +28 -10
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/publishing.md +5 -3
  55. package/docs/runtime-reference.md +76 -25
  56. package/docs/semantic-validation.md +29 -0
  57. package/docs/stateful-binding-updates-proposal.md +759 -0
  58. package/docs/user-bindings.en.md +66 -0
  59. package/docs/user-bindings.md +66 -0
  60. package/docs/value-wire.md +3 -3
  61. package/docs/verification-optimization-work.md +41 -0
  62. package/docs/verification.md +71 -0
  63. package/index.js +369 -92
  64. package/internal/ast-traversal.js +5 -2
  65. package/internal/async-local-storage-intrinsics.js +38 -0
  66. package/internal/binding-declaration-contract.js +8 -0
  67. package/internal/binding-descriptors.js +1 -0
  68. package/internal/binding-pattern-traversal.js +25 -0
  69. package/internal/binding-pattern.js +26 -29
  70. package/internal/binding-source-analysis.js +708 -0
  71. package/internal/binding-update-policy.js +72 -0
  72. package/internal/bounded-worker-round.js +56 -0
  73. package/internal/callable-definition-environment.js +42 -0
  74. package/internal/callable-source-catalog.js +129 -0
  75. package/internal/callable-source-encoding.js +47 -0
  76. package/internal/callable-source-facts.js +459 -0
  77. package/internal/callable-source-format.js +2 -0
  78. package/internal/callable-source-lowering.js +123 -0
  79. package/internal/cell-analysis-contract.js +25 -0
  80. package/internal/cell-analysis.js +253 -290
  81. package/internal/cell-completion.js +15 -0
  82. package/internal/cell-error.js +7 -0
  83. package/internal/cell-parser.js +66 -0
  84. package/internal/cell-rewriter.js +3 -677
  85. package/internal/commonjs-export-evidence.js +63 -0
  86. package/internal/commonjs-source-evidence.js +72 -0
  87. package/internal/compiler-amaro.cjs +2 -0
  88. package/internal/compiler-compression.js +12 -0
  89. package/internal/compiler-data.js +58 -0
  90. package/internal/compiler-descriptors.js +54 -0
  91. package/internal/compiler-dynamic-cache.js +48 -0
  92. package/internal/compiler-entry.js +35 -0
  93. package/internal/compiler-generated-names.js +17 -0
  94. package/internal/compiler-intrinsics.js +50 -0
  95. package/internal/compiler-module-links.js +37 -0
  96. package/internal/compiler-operations.js +61 -0
  97. package/internal/compiler-platform-factory.js +84 -0
  98. package/internal/compiler-platform.js +136 -0
  99. package/internal/compiler-record-roles.js +53 -0
  100. package/internal/compiler-region-output.js +259 -0
  101. package/internal/compiler-resource-lowering.js +103 -0
  102. package/internal/compiler-scope-facts.js +115 -0
  103. package/internal/compiler-service.js +109 -0
  104. package/internal/compiler-source-cache.js +32 -0
  105. package/internal/compiler-source-pieces.js +93 -0
  106. package/internal/compiler-source-regions.js +132 -0
  107. package/internal/compiler-storage-source.js +54 -0
  108. package/internal/compiler-text.js +15 -0
  109. package/internal/compiler-typescript.cjs +2 -0
  110. package/internal/config-spec.js +83 -27
  111. package/internal/cordis-tools-owner.js +130 -21
  112. package/internal/diagnostic.js +27 -1
  113. package/internal/direct-surface-owner.js +76 -23
  114. package/internal/dynamic-binding-evidence.js +16 -0
  115. package/internal/dynamic-environment-compiler.js +779 -0
  116. package/internal/dynamic-environment-integration.js +91 -0
  117. package/internal/dynamic-environment-runtime.js +383 -0
  118. package/internal/dynamic-native-calls.js +153 -0
  119. package/internal/dynamic-native-runtime.js +262 -0
  120. package/internal/dynamic-scope-analysis.js +90 -0
  121. package/internal/edit-transport-owner.js +27 -2
  122. package/internal/execution-seam-compat.js +193 -0
  123. package/internal/failure-reporting.js +136 -28
  124. package/internal/host-rpc.js +66 -0
  125. package/internal/isolated-worker.js +555 -0
  126. package/internal/kernel-child.js +139 -0
  127. package/internal/kernel-worker.js +1179 -233
  128. package/internal/language-semantics.js +14 -0
  129. package/internal/legacy-binding-catalog.js +48 -0
  130. package/internal/legacy-cell-analysis.js +600 -0
  131. package/internal/legacy-cell-parser.js +50 -0
  132. package/internal/legacy-cell-rewriter.js +631 -0
  133. package/internal/legacy-repl-convenience.js +291 -0
  134. package/internal/managed-module-operations.js +170 -0
  135. package/internal/module-compilation.js +322 -0
  136. package/internal/module-runtime-platform.js +19 -0
  137. package/internal/module-transform-contract.js +42 -0
  138. package/internal/native-await-declarations.js +68 -0
  139. package/internal/native-root-compilation.js +54 -0
  140. package/internal/native-root-dynamic.js +67 -0
  141. package/internal/record-utils.js +24 -15
  142. package/internal/recovery-tips.js +27 -15
  143. package/internal/rejected-cell-editor.js +13 -8
  144. package/internal/repl-convenience.js +2 -289
  145. package/internal/repl-memory-projection.js +81 -12
  146. package/internal/repl-observation-interest.js +70 -0
  147. package/internal/repl-rewrite-contract.js +6 -0
  148. package/internal/repl-scope-normalizer.js +2091 -0
  149. package/internal/repl-value-observer.js +117 -0
  150. package/internal/rpc-contract.js +52 -0
  151. package/internal/runtime-bridge-owner.js +172 -28
  152. package/internal/runtime-config.js +48 -52
  153. package/internal/runtime-contexts.js +82 -10
  154. package/internal/runtime-intrinsics.js +101 -0
  155. package/internal/runtime-messages.js +111 -0
  156. package/internal/session-cell-executor.js +402 -87
  157. package/internal/session-journal-recovery.js +684 -0
  158. package/internal/session-journal-schema.js +123 -0
  159. package/internal/session-journal.js +199 -654
  160. package/internal/session-log-view.js +42 -9
  161. package/internal/session-runtime.js +257 -95
  162. package/internal/session-state.js +247 -71
  163. package/internal/source-position-map.js +230 -71
  164. package/internal/stateful-module-compiler.js +154 -0
  165. package/internal/stateful-module-runtime.js +303 -0
  166. package/internal/stateful-root-compiler.js +600 -0
  167. package/internal/stateful-root-runtime.js +340 -0
  168. package/internal/typert-codec-compat.js +28 -0
  169. package/internal/typescript-transform.js +31 -0
  170. package/internal/user-binding-authoring.js +70 -0
  171. package/internal/user-binding-console-worker.js +244 -0
  172. package/internal/user-binding-console.js +148 -0
  173. package/internal/user-binding-draft-projection.js +335 -0
  174. package/internal/user-binding-model-context.js +31 -0
  175. package/internal/user-binding-runner.js +56 -0
  176. package/internal/user-bindings-owner.js +1065 -0
  177. package/internal/user-bindings-store.js +293 -0
  178. package/internal/user-bindings.js +361 -0
  179. package/internal/value-wire-schema.js +28 -0
  180. package/internal/value-wire.js +130 -91
  181. package/internal/worker-client.js +57 -67
  182. package/internal/worker-cwd-virtualization.js +80 -53
  183. package/internal/worker-environment.js +65 -0
  184. package/internal/worker-output-relay.js +29 -0
  185. package/internal/worker-repl-realm.js +134 -0
  186. package/internal/worker-shutdown.js +14 -0
  187. package/package.json +76 -7
  188. package/src/client-activity.js +95 -53
  189. package/src/client-authoring-view.js +580 -0
  190. package/src/client-binding-review.js +230 -0
  191. package/src/client-bindings-data.js +29 -0
  192. package/src/client-catalog.js +127 -0
  193. package/src/client-code-editor.js +84 -0
  194. package/src/client-console.js +112 -0
  195. package/src/client-copy.js +370 -0
  196. package/src/client-feature-gates.js +62 -0
  197. package/src/client-host-compat.js +150 -0
  198. package/src/client-indicator-view.js +267 -0
  199. package/src/client-repl-view.js +292 -0
  200. package/src/client-rpc.js +20 -0
  201. package/src/client-settings-view.js +221 -0
  202. package/src/client-styles.js +106 -0
  203. package/src/client-tool-view.js +108 -0
  204. package/src/client-transport.js +159 -0
  205. package/src/client-workbench.js +521 -0
  206. package/src/client.js +233 -615
@@ -0,0 +1,459 @@
1
+ import { parse } from '@babel/parser'
2
+ import { tokenizer } from 'acorn'
3
+ import { types as t } from '@babel/core'
4
+ import { createHash } from 'node:crypto'
5
+ import { SourceMap } from 'node:module'
6
+ import { applySourceEdits, identitySourceMap, mappedSourceTransform, sourceLineStarts, sourceOffsetAt, visitSourceMappings } from './source-position-map.js'
7
+ import { createCallableSourceCatalog } from './callable-source-encoding.js'
8
+ import { regionInput, visitRegionSource } from './compiler-region-output.js'
9
+ import { lowerCallableSource, undecoratedMethodSource, decoratedClassSource } from './callable-source-lowering.js'
10
+ import { supportsNativeUsing, transformTypeScriptSource } from './typescript-transform.js'
11
+
12
+ const isCallable = node => t.isFunction(node) || t.isClass(node)
13
+ const isMethod = node => t.isObjectMethod(node) || t.isClassMethod(node) || t.isClassPrivateMethod(node)
14
+ const requiresTypeErasure = node => node.type.startsWith('TS') || node.type === 'Decorator' || node.type === 'ClassAccessorProperty'
15
+ || node.accessibility !== undefined && node.accessibility !== null || node.abstract || node.declare
16
+ || node.override || node.readonly || node.definite || node.optional
17
+
18
+ class CallableSourceFacts {
19
+ constructor(source) { this.buffers = [source]; this.entries = []; this.length = 0; this.aliases = [] }
20
+ add(marker, start, end, override) {
21
+ let buffer = 0
22
+ if (override !== undefined) { buffer = this.buffers.length; this.buffers.push(override); start = 0; end = override.length }
23
+ this.entries.push({ marker, buffer, start, end })
24
+ this.length++
25
+ }
26
+ *[Symbol.iterator]() { for (const entry of this.entries) yield [entry.marker, this.buffers[entry.buffer].slice(entry.start, entry.end)] }
27
+ map(callback) { return [...this].map(callback) }
28
+ }
29
+
30
+ const factMap = sources => sources instanceof CallableSourceFacts
31
+ ? new Map(sources.entries.map(entry => [entry.marker, { entry, buffers: sources.buffers }]))
32
+ : new Map(sources.map(([marker, original]) => [marker, { original }]))
33
+ const originalText = fact => fact.entry === undefined ? fact.original
34
+ : fact.buffers[fact.entry.buffer].slice(fact.entry.start, fact.entry.end)
35
+
36
+ // Native method source starts after the static modifier and its trivia. Read
37
+ // tokens only at this parsed method boundary, without retaining file tokens.
38
+ function callableSourceStart(source, node) {
39
+ if (!(node.staticMethod ?? (node.static && node.type.endsWith('Method')))) return node.start
40
+ const start = node.decorators?.at(-1)?.end ?? node.start
41
+ const tokens = tokenizer(source.slice(start, node.end), { ecmaVersion: 'latest' })
42
+ while (tokens.getToken().value !== 'static') {}
43
+ return start + tokens.getToken().start
44
+ }
45
+
46
+ export function callableMarkerPosition(node, source, comments) {
47
+ if (node.type !== 'ArrowFunctionExpression') return node.body.start + 1
48
+ let start = source.lastIndexOf('=>', node.body.start - 1)
49
+ while (true) {
50
+ let low = 0, high = comments.length
51
+ while (low < high) {
52
+ const middle = (low + high) >>> 1
53
+ if (comments[middle].start <= start) low = middle + 1
54
+ else high = middle
55
+ }
56
+ const comment = comments[low - 1]
57
+ if (comment === undefined || comment.end <= start) return start
58
+ start = source.lastIndexOf('=>', comment.start - 1)
59
+ }
60
+ }
61
+
62
+ /** Carry source identity through transforms without wrapping callable values. */
63
+ export function markCallableSources(source, sourceMap = identitySourceMap(source.length), parserOptions = {}, {
64
+ lowerNativeSource, resolveOriginalSource, nativeUsing = supportsNativeUsing(),
65
+ } = {}) {
66
+ const options = { sourceType: 'unambiguous', allowAwaitOutsideFunction: true,
67
+ allowReturnOutsideFunction: true, errorRecovery: true, ...parserOptions }
68
+ let tree, nativeJavaScript = false
69
+ try {
70
+ tree = parse(source, { ...options, errorRecovery: false, plugins: ['importAttributes'] })
71
+ nativeJavaScript = true
72
+ } catch {
73
+ tree = parse(source, options)
74
+ }
75
+ const identity = createHash('sha256').update(source).digest('hex')
76
+ const sources = new CallableSourceFacts(source)
77
+ const typedCallables = new WeakSet()
78
+ const resourceCallables = new WeakSet()
79
+ const lowerResources = !nativeUsing && source.includes('using')
80
+ const typed = node => {
81
+ if (!node) return 0
82
+ let needed = requiresTypeErasure(node) ? 1 : 0
83
+ if (lowerResources && t.isVariableDeclaration(node) && (node.kind === 'using' || node.kind === 'await using')) needed |= 2
84
+ for (const key of t.VISITOR_KEYS[node.type] ?? []) {
85
+ const child = node[key]
86
+ if (Array.isArray(child)) { for (const value of child) needed |= typed(value) }
87
+ else needed |= typed(child)
88
+ }
89
+ if (needed && isCallable(node)) typedCallables.add(node)
90
+ if ((needed & 2) && isCallable(node)) resourceCallables.add(node)
91
+ return needed
92
+ }
93
+ if (!nativeJavaScript || lowerResources) typed(tree.program)
94
+ const edits = [], typedRecords = []
95
+ const contexts = new WeakMap()
96
+ const context = (node, privateNames = []) => {
97
+ if (!node) return
98
+ if (isCallable(node)) contexts.set(node, privateNames)
99
+ const names = t.isClass(node) ? [...new Set([...privateNames, ...node.body.body
100
+ .filter(member => t.isPrivateName(member.key)).map(member => member.key.id.name)])] : privateNames
101
+ for (const key of t.VISITOR_KEYS[node.type] ?? []) {
102
+ const child = node[key]
103
+ if (Array.isArray(child)) { for (const value of child) context(value, names) }
104
+ else context(child, names)
105
+ }
106
+ }
107
+ if (!nativeJavaScript || lowerResources) context(tree.program)
108
+ t.traverseFast(tree, node => {
109
+ if (t.isVariableDeclaration(node) && (node.kind === 'using' || node.kind === 'await using')) nativeJavaScript = false
110
+ if (!isCallable(node)) return
111
+ if (node.body === undefined || node.body === null) return
112
+ // A constructor method's callable value is the class itself. It cannot
113
+ // have a separate source identity from Class.prototype.constructor.
114
+ if (t.isClassMethod(node, { kind: 'constructor' })) return
115
+ const marker = `__dsh_ptc_callable_${identity}_${sources.length}__`
116
+ const startOffset = callableSourceStart(source, node)
117
+ sources.add(marker, startOffset, node.end, resolveOriginalSource?.(source.slice(startOffset, node.end)))
118
+ const start = callableMarkerPosition(node, source, tree.comments)
119
+ edits.push({ start, end: start, text: `/*${marker}*/` })
120
+ if (typedCallables.has(node)) typedRecords.push({ marker, node, entry: sources.entries.at(-1), privateNames: contexts.get(node),
121
+ resources: resourceCallables.has(node) })
122
+ })
123
+ const marked = applySourceEdits(source, sourceMap, edits)
124
+ if (typedRecords.length === 0) return { ...marked, callableSources: sources, nativeJavaScript }
125
+ // Every descendant has a source identity before its parent is lowered.
126
+ const markedTree = parse(marked.code, options)
127
+ const markedNodes = new Map([...markedCallableOwners(markedTree, sources)].map(([node, fact]) => [fact.marker, node]))
128
+ const originals = new Map(sources)
129
+ const nativeFunctions = typedRecords.map(record => markedNodes.get(record.marker))
130
+ .filter(node => t.isFunction(node) && !isMethod(node)).sort((left, right) => left.start - right.start)
131
+ const nativeRecipes = new Map()
132
+ const nativeFunctionIndex = start => {
133
+ let low = 0, high = nativeFunctions.length
134
+ while (low < high) {
135
+ const middle = (low + high) >>> 1
136
+ if (nativeFunctions[middle].start < start) low = middle + 1
137
+ else high = middle
138
+ }
139
+ return low
140
+ }
141
+ for (const record of typedRecords.sort((a, b) => a.node.end - a.node.start - (b.node.end - b.node.start))) {
142
+ const { marker, entry, privateNames } = record
143
+ const node = markedNodes.get(marker)
144
+ const start = callableSourceStart(marked.code, node)
145
+ let original = marked.code.slice(start, node.end)
146
+ const method = isMethod(node)
147
+ const classRecipe = t.isClass(node) && (node.decorators?.length || node.abstract)
148
+ const methodRecipe = method && (node.decorators?.length || node.params.some(parameter => parameter.decorators?.length))
149
+ const reused = []
150
+ if (classRecipe) original = decoratedClassSource(node)
151
+ else if (methodRecipe) original = undecoratedMethodSource(node)
152
+ else {
153
+ // A lowered function closes its generated helpers over its own source.
154
+ // Reuse that complete function in enclosing reflection recipes; class
155
+ // and method definition phases retain their separate grammar owners.
156
+ // Descendants are complete before their parent. Select the outermost
157
+ // reusable functions, jumping over the descendants each one already owns.
158
+ for (let index = nativeFunctionIndex(start); index < nativeFunctions.length;) {
159
+ const child = nativeFunctions[index]
160
+ if (child.start >= node.end) break
161
+ const replacement = nativeRecipes.get(child)
162
+ if (replacement === undefined) { index++; continue }
163
+ reused.push(replacement)
164
+ index = nativeFunctionIndex(child.end)
165
+ }
166
+ if (reused.length > 0) original = applySourceEdits(original, identitySourceMap(original.length), reused
167
+ .map(value => ({ start: value.start - start, end: value.end - start, text: value.text }))).code
168
+ }
169
+ const prefix = method ? 'class Source {' : '('
170
+ const suffix = method ? '}' : ')'
171
+ const lowerCallable = () => {
172
+ const lowered = lowerCallableSource(original, { method,
173
+ lowerNativeSource: (source, options) => lowerNativeSource(source, { ...options, nativeUsing }),
174
+ privateNames, sourceType: tree.program.sourceType })
175
+ original = lowered.source
176
+ const entries = [...originals].map(([key, text]) => [key, key === marker ? original : text])
177
+ for (const [node, fact] of markedCallableOwners(lowered.emitted, entries)) {
178
+ sources.aliases.push([lowered.emittedSource.slice(node.start, node.end), fact.original])
179
+ }
180
+ }
181
+ // Prefer source-preserving erasure. Runtime TypeScript constructs such as
182
+ // parameter properties require equivalent JavaScript initialization too.
183
+ let resources = record.resources
184
+ let erase = true
185
+ if (reused.length > 0) {
186
+ resources = false
187
+ erase = false
188
+ const reusedNodes = new Set(reused.map(value => value.node))
189
+ t.traverseFast(node, child => {
190
+ if (reusedNodes.has(child)) return t.traverseFast.skip
191
+ if (requiresTypeErasure(child)) erase = true
192
+ if (record.resources && t.isVariableDeclaration(child) && (child.kind === 'using' || child.kind === 'await using')) resources = true
193
+ })
194
+ }
195
+ // Replacing complete functions with proven native functions leaves native
196
+ // surrounding syntax intact. Their generated bodies need no erasure pass.
197
+ if (resources) lowerCallable()
198
+ else if (erase) try {
199
+ const erased = transformTypeScriptSource(`${prefix}${original}${suffix}`, { mode: 'strip-only' }).code
200
+ parse(erased, { errorRecovery: true })
201
+ original = erased.slice(prefix.length, erased.length - suffix.length)
202
+ } catch {
203
+ lowerCallable()
204
+ }
205
+ entry.buffer = sources.buffers.length
206
+ sources.buffers.push(original)
207
+ entry.start = 0
208
+ entry.end = original.length
209
+ originals.set(marker, original)
210
+ if (!method && t.isFunction(node)) nativeRecipes.set(node, { node, start, end: node.end, text: original })
211
+ }
212
+ return { ...marked, callableSources: sources, nativeJavaScript }
213
+ }
214
+
215
+ export function markedCallableOwners(tree, sources) {
216
+ const declarations = []
217
+ t.traverseFast(tree, node => { if (isCallable(node)) declarations.push(node) })
218
+ declarations.sort((left, right) => left.start - right.start || right.end - left.end)
219
+ const facts = factMap(sources)
220
+ const originals = new Map()
221
+ const stack = []
222
+ let next = 0
223
+ for (const comment of tree.comments) {
224
+ const fact = facts.get(comment.value)
225
+ if (fact === undefined) continue
226
+ while (next < declarations.length && declarations[next].start <= comment.start) {
227
+ const node = declarations[next++]
228
+ while (stack.length > 0 && stack.at(-1).end < node.end) stack.pop()
229
+ stack.push(node)
230
+ }
231
+ while (stack.length > 0 && stack.at(-1).end < comment.end) stack.pop()
232
+ if (stack.length > 0) originals.set(stack.at(-1), { marker: comment.value, ...fact })
233
+ }
234
+ return originals
235
+ }
236
+
237
+ function sourceOwnerPositions(code, sources, parserOptions) {
238
+ return new Map([...markedCallableOwners(parse(code, parserOptions), sources)]
239
+ .map(([node, fact]) => [`${node.type}:${node.start}`, fact]))
240
+ }
241
+
242
+ function callableSourceOwners(code, sources, parserOptions, transformation) {
243
+ const options = { sourceType: 'unambiguous', allowAwaitOutsideFunction: true,
244
+ allowReturnOutsideFunction: true, ...parserOptions }
245
+ if (transformation === undefined) {
246
+ const tree = parse(code, options)
247
+ return { tree, owners: markedCallableOwners(tree, sources) }
248
+ }
249
+ // Retain numeric owner facts, not a second complete AST while reading output.
250
+ const owners = sourceOwnerPositions(transformation.code, sources, options)
251
+ const sourceMap = new SourceMap(JSON.parse(transformation.map))
252
+ const starts = sourceLineStarts(transformation.code)
253
+ const emitted = parse(code, options)
254
+ const matched = new Map()
255
+ t.traverseFast(emitted, node => {
256
+ if (!isCallable(node)) return
257
+ const position = sourceMap.findEntry(node.loc.start.line - 1, node.loc.start.column)
258
+ // A preceding segment does not prove a generated callable's source owner.
259
+ if (position.generatedLine !== node.loc.start.line - 1 || position.generatedColumn !== node.loc.start.column
260
+ || starts[position.originalLine] === undefined) return
261
+ const fact = owners.get(`${node.type}:${starts[position.originalLine] + position.originalColumn}`)
262
+ if (fact !== undefined) matched.set(node, fact)
263
+ })
264
+ return { tree: emitted, owners: matched }
265
+ }
266
+
267
+ /** Source marks own callables before transforms; exact mappings retain that ownership afterward. */
268
+ export function collectCallableSources(code, sources, parserOptions = {}, transformation) {
269
+ if (sources.length === 0) return []
270
+ const { owners } = callableSourceOwners(code, sources, parserOptions, transformation)
271
+ return [...owners].map(([node, fact]) => {
272
+ return [code.slice(callableSourceStart(code, node), node.end), originalText(fact)]
273
+ }).concat(sources.aliases ?? [])
274
+ }
275
+
276
+ /** Emission ranges come from typed nodes and exact generator mappings. */
277
+ export function regionCallableRanges(input, generated, sourceRanges, parserOptions) {
278
+ if (!sourceRanges?.length) return []
279
+ const sourceByStart = new Map(sourceRanges.map(fact => [fact.start, fact]))
280
+ const local = new Map()
281
+ t.traverseFast(input.selected, node => {
282
+ if (!isCallable(node)) return
283
+ const fact = sourceByStart.get(sourceOffsetAt(input.sourceMap, node.start))
284
+ if (fact && sourceOffsetAt(input.sourceMap, node.end - 1) + 1 === fact.end) local.set(node.start, fact)
285
+ })
286
+ if (!local.size) return []
287
+ const inputStarts = sourceLineStarts(input.code), generatedStarts = sourceLineStarts(generated.code)
288
+ const mapped = new Map()
289
+ for (const item of generated.rawMappings) if (item.original) mapped.set(
290
+ generatedStarts[item.generated.line - 1] + item.generated.column,
291
+ inputStarts[item.original.line - 1] + item.original.column)
292
+ const output = regionInput(generated.code, { ...input.region, start: 0, end: generated.code.length, children: [] },
293
+ { ...parserOptions, attachComment: false })
294
+ const ranges = []
295
+ t.traverseFast(output.selected, node => {
296
+ if (!isCallable(node)) return
297
+ const start = node.start - output.prefix.length
298
+ const fact = local.get(mapped.get(start))
299
+ if (fact) ranges.push({ marker: fact.marker, start, end: node.end - output.prefix.length,
300
+ staticMethod: node.static && node.type.endsWith('Method') })
301
+ })
302
+ return ranges
303
+ }
304
+
305
+ /** Parent shells and child bodies jointly prove ownership, without retaining
306
+ * their ASTs or parsing the expanded module as a whole. */
307
+ export function createRegionCallableCollector(sources) {
308
+ const facts = factMap(sources)
309
+ const collect = input => {
310
+ const ranges = []
311
+ t.traverseFast(input.selected, node => {
312
+ if (isCallable(node)) ranges.push({
313
+ start: sourceOffsetAt(input.sourceMap, node.start),
314
+ end: sourceOffsetAt(input.sourceMap, node.end - 1) + 1,
315
+ staticMethod: node.static && node.type.endsWith('Method'),
316
+ })
317
+ })
318
+ for (const comment of input.tree.comments) if (facts.has(comment.value)) ranges.push({
319
+ marker: comment.value, start: sourceOffsetAt(input.sourceMap, comment.start),
320
+ end: sourceOffsetAt(input.sourceMap, comment.end - 1) + 1,
321
+ })
322
+ return ranges
323
+ }
324
+ return { collect, resolve: resolveRegionCallableRanges }
325
+ }
326
+
327
+ function resolveRegionCallableRanges(ranges) {
328
+ const declarations = [], comments = []
329
+ for (const range of ranges) (range.marker === undefined ? declarations : comments).push(range)
330
+ declarations.sort((a, b) => a.start - b.start || b.end - a.end)
331
+ comments.sort((a, b) => a.start - b.start)
332
+ const owners = new Map(), stack = []
333
+ let next = 0
334
+ for (const comment of comments) {
335
+ while (next < declarations.length && declarations[next].start <= comment.start) {
336
+ const node = declarations[next++]
337
+ while (stack.length && stack.at(-1).end < node.end) stack.pop()
338
+ stack.push(node)
339
+ }
340
+ while (stack.length && stack.at(-1).end < comment.end) stack.pop()
341
+ const owner = stack.at(-1)
342
+ if (owner) owners.set(owner.start, { ...owner, marker: comment.marker })
343
+ }
344
+ return owners
345
+ }
346
+
347
+ export function collectRegionCallableRanges(mapped, sources, options) {
348
+ const collector = createRegionCallableCollector(sources)
349
+ const ranges = []
350
+ visitRegionSource(mapped, options, input => {
351
+ for (const range of collector.collect(input)) ranges.push(range)
352
+ })
353
+ return collector.resolve(ranges)
354
+ }
355
+
356
+ /** Restore identities while the maintained transform's bounded tree and exact
357
+ * source-map points are available; child slots remain structural output facts. */
358
+ export function emitRegionCallableSources(input, generated, sourceRanges, options) {
359
+ const originals = new Map()
360
+ t.traverseFast(input.selected, node => {
361
+ if (!isCallable(node)) return
362
+ const fact = sourceRanges.get(sourceOffsetAt(input.sourceMap, node.start))
363
+ if (fact && sourceOffsetAt(input.sourceMap, node.end - 1) + 1 === fact.end) originals.set(node.start, fact)
364
+ })
365
+ const inputStarts = sourceLineStarts(input.code), generatedStarts = sourceLineStarts(generated.code)
366
+ const points = [], origins = new Map()
367
+ visitSourceMappings(generated, (line, column, originalLine, originalColumn) => {
368
+ if (originalLine === undefined) return
369
+ const point = [generatedStarts[line] + column, inputStarts[originalLine] + originalColumn]
370
+ points.push(point)
371
+ origins.set(...point)
372
+ })
373
+ const tree = parse(generated.code, { ...options, plugins: ['importAttributes'],
374
+ allowSuperOutsideMethod: true, allowUndeclaredExports: options.sourceType === 'module', errorRecovery: true })
375
+ for (const error of tree.errors) {
376
+ if (error.reasonCode === 'InvalidPrivateFieldResolution' && input.region.privateNames?.includes(error.details.identifierName)) continue
377
+ throw error
378
+ }
379
+ let from = 0, to = generated.code.length
380
+ if (input.prefix) {
381
+ let statement = tree.program.body[0].body.body[0]
382
+ while (statement.type === 'LabeledStatement') statement = statement.body
383
+ const body = statement.body.body
384
+ from = body[0]?.start ?? statement.body.start + 1
385
+ to = body.at(-1)?.end ?? from
386
+ }
387
+ const owners = []
388
+ const slotStarts = new Map(input.slots.map(slot => [slot.start, slot]))
389
+ const slotEnds = new Map(input.slots.map(slot => [slot.end - 1, slot]))
390
+ const slots = []
391
+ t.traverseFast(tree, node => {
392
+ if (node.type === 'BlockStatement' && node.body.length === 0 && node.directives.length === 0) {
393
+ const slot = slotStarts.get(origins.get(node.start)) ?? slotEnds.get(origins.get(node.end - 1))
394
+ if (slot) slots.push({ sourceStart: slot.start, start: node.start, end: node.end, prefix: '' })
395
+ }
396
+ if (!isCallable(node) || node.start < from || node.end > to) return
397
+ const fact = originals.get(origins.get(node.start))
398
+ if (fact) owners.push({ node, fact })
399
+ })
400
+ const markers = new Set([...originals.values()].map(fact => fact.marker))
401
+ const edits = tree.comments.filter(comment => markers.has(comment.value))
402
+ .map(comment => ({ start: comment.start, end: comment.end, text: '' }))
403
+ for (const { node, fact } of owners) {
404
+ const start = callableMarkerPosition(node, generated.code, tree.comments)
405
+ const text = `/*${fact.marker}*/`
406
+ const slot = slots.find(slot => start > slot.start && start < slot.end)
407
+ if (slot) slot.prefix += text
408
+ else edits.push({ start, end: start, text })
409
+ }
410
+ if (from) edits.push({ start: 0, end: from, text: '' })
411
+ if (to < generated.code.length) edits.push({ start: to, end: generated.code.length, text: '' })
412
+ edits.sort((a, b) => a.start - b.start || a.end - b.end)
413
+ // Non-overlapping edits have ordered ends. Include every edit ending at a
414
+ // queried boundary, including all insertions at that same position.
415
+ const deltas = [0]
416
+ for (const edit of edits) deltas.push(deltas.at(-1) + edit.text.length - (edit.end - edit.start))
417
+ const position = offset => {
418
+ let low = 0, high = edits.length
419
+ while (low < high) {
420
+ const middle = (low + high) >>> 1
421
+ if (edits[middle].end > offset) high = middle
422
+ else low = middle + 1
423
+ }
424
+ return offset + deltas[low]
425
+ }
426
+ const emission = mappedSourceTransform(input.code, identitySourceMap(input.code.length), generated)
427
+ const restored = applySourceEdits(emission.code, emission.sourceMap, edits)
428
+ return { code: restored.code, sourceOffsets: restored.sourceMap,
429
+ mappingPoints: points.filter(([output]) => output >= from && output < to)
430
+ .map(([output, original]) => [position(output), original]),
431
+ slotRanges: slots.map(slot => ({ ...slot, start: position(slot.start), end: position(slot.end) })),
432
+ callableRanges: owners.map(({ node, fact }) => ({ marker: fact.marker,
433
+ start: position(node.start), end: position(node.end), staticMethod: node.static && node.type.endsWith('Method') })),
434
+ }
435
+ }
436
+
437
+ export function callableSourceCatalog(code, sources, ranges) {
438
+ const facts = factMap(sources)
439
+ const buffers = [code]
440
+ const indexes = new Map()
441
+ const bufferIndex = text => {
442
+ let index = indexes.get(text)
443
+ if (index === undefined) { index = buffers.length; indexes.set(text, index); buffers.push(text) }
444
+ return index
445
+ }
446
+ const entries = []
447
+ for (const range of ranges) {
448
+ const fact = facts.get(range.marker)
449
+ if (!fact) continue
450
+ const start = callableSourceStart(code, range)
451
+ const entry = fact.entry
452
+ entries.push([0, start, range.end, bufferIndex(entry ? fact.buffers[entry.buffer] : fact.original),
453
+ entry?.start ?? 0, entry?.end ?? fact.original.length])
454
+ }
455
+ for (const [generated, original] of sources.aliases ?? []) entries.push([
456
+ bufferIndex(generated), 0, generated.length, bufferIndex(original), 0, original.length,
457
+ ])
458
+ return createCallableSourceCatalog(buffers, entries)
459
+ }
@@ -0,0 +1,2 @@
1
+ export const CALLABLE_SOURCE_FORMAT = 'ptc-callable-sources-v1'
2
+ export const CALLABLE_SOURCE_BLOCK_UNITS = 16_384
@@ -0,0 +1,123 @@
1
+ import { parse } from '@babel/parser'
2
+ import { types as t } from '@babel/core'
3
+ import generatorImport from '@babel/generator'
4
+ import traverseImport from '@babel/traverse'
5
+ import { callableDefinitionOwner, closeDefinitionExpression } from './callable-definition-environment.js'
6
+
7
+ const generate = generatorImport.default ?? generatorImport
8
+ const traverse = traverseImport.default ?? traverseImport
9
+ const nativeParserOptions = { sourceType: 'script', allowAwaitOutsideFunction: true, allowYieldOutsideFunction: true,
10
+ allowSuperOutsideMethod: true, allowNewTargetOutsideFunction: true }
11
+ const parserOptions = { sourceType: 'script', plugins: ['typescript', 'decorators', 'decoratorAutoAccessors'],
12
+ ...nativeParserOptions, allowReturnOutsideFunction: true, errorRecovery: true }
13
+ const methodNode = node => t.isObjectMethod(node) || t.isClassMethod(node) || t.isClassPrivateMethod(node)
14
+ const text = node => generate(node).code
15
+
16
+ /** Definition-time decorators belong to the enclosing class, not the method value. */
17
+ export function undecoratedMethodSource(node) {
18
+ const method = t.cloneNode(node, true)
19
+ method.decorators = null
20
+ method.static = false
21
+ method.accessibility = null
22
+ for (const parameter of method.params) parameter.decorators = null
23
+ return text(method)
24
+ }
25
+
26
+ /** A decorated declaration's range may include its parent's export modifiers. */
27
+ export function decoratedClassSource(node) {
28
+ const value = t.cloneNode(node, true)
29
+ value.type = 'ClassExpression'
30
+ value.abstract = false
31
+ return text(value)
32
+ }
33
+
34
+ /** A body owns only the helper bindings it can use, including dependencies of
35
+ * those helpers. Parameter and key activations obtain their own lexical set. */
36
+ function closeCallableHelpers(value, helpers) {
37
+ const callable = t.isMethod(value) ? { ...value, type: 'ObjectMethod' } : value
38
+ const tree = t.file(t.program([...helpers, t.expressionStatement(t.isMethod(callable)
39
+ ? t.objectExpression([callable]) : callable)]))
40
+ const bodies = new Map([[callable, new Set()]])
41
+ const dependencies = new Map(helpers.map(node => [node, new Set()]))
42
+ const statement = path => {
43
+ while (!path.parentPath.isProgram()) path = path.parentPath
44
+ return path.node
45
+ }
46
+ traverse(tree, { Function(path) {
47
+ if (callableDefinitionOwner(path) && !dependencies.has(statement(path))) bodies.set(path.node, new Set())
48
+ } })
49
+ traverse(tree, { ReferencedIdentifier(path) {
50
+ const binding = path.scope.getBinding(path.node.name)
51
+ if (binding === undefined) return
52
+ const helper = statement(binding.path)
53
+ if (!dependencies.has(helper)) return
54
+ const consumer = dependencies.get(statement(path))
55
+ if (consumer) consumer.add(helper)
56
+ else {
57
+ let child = path
58
+ while (child.parentPath) {
59
+ const parent = child.parentPath
60
+ if (bodies.has(parent.node) && child.key === 'body') {
61
+ bodies.get(parent.node).add(helper)
62
+ break
63
+ }
64
+ child = parent
65
+ }
66
+ }
67
+ } })
68
+ for (const [body, required] of bodies) {
69
+ for (const helper of required) for (const dependency of dependencies.get(helper)) required.add(dependency)
70
+ if (!t.isBlockStatement(body.body)) body.body = t.blockStatement([t.returnStatement(body.body)])
71
+ body.body.body.unshift(...helpers.filter(node => required.has(node)).map(node => t.cloneNode(node, true)))
72
+ }
73
+ value.body = callable.body
74
+ }
75
+
76
+ /** Close generated language helpers over the source that actually needs them. */
77
+ export function lowerCallableSource(original, { method = false, lowerNativeSource, privateNames = [], sourceType = 'script' }) {
78
+ // Grammar-only containers must not acquire logical declaration identities.
79
+ // Every fragment crosses lowering as an expression, including method hosts.
80
+ const prefix = method ? '(class {' : '('
81
+ const suffix = method ? '})' : ')'
82
+ let input = prefix + original + suffix
83
+ const nativeOptions = { ...nativeParserOptions, sourceType, errorRecovery: true }
84
+ const tree = parse(input, { ...parserOptions, sourceType })
85
+ let contextual = privateNames.length > 0
86
+ t.traverseFast(tree, node => {
87
+ if (t.isSuper(node) || t.isMetaProperty(node) && node.meta.name === 'new') contextual = true
88
+ })
89
+ // Declaration ownership is assigned after dialect normalization. This
90
+ // inspection only selects the detached fragment's surrounding grammar.
91
+ traverse(tree, { noScope: true,
92
+ ...traverse.visitors.environmentVisitor({ YieldExpression() { contextual = true } }),
93
+ })
94
+ if (!method && t.isClass(tree.program.body[0].expression) && tree.program.body[0].expression.id === null) {
95
+ input = `({[""]:${input}})[""]`
96
+ }
97
+ if (contextual) {
98
+ const body = `async *source(){return ${input}}`
99
+ input = privateNames.length > 0 ? `(class{${privateNames.map(name => `#${name};`).join('')}${body}})`
100
+ : `({${body}})`
101
+ }
102
+ const lowered = lowerNativeSource(input, { sourceType })
103
+ const native = parse(lowered, nativeOptions)
104
+ const helpers = native.program.body.slice(0, -1)
105
+ let expression = native.program.body.at(-1).expression
106
+ if (contextual) {
107
+ const body = (privateNames.length > 0 ? expression.body.body : expression.properties)
108
+ .find(methodNode).body.body
109
+ helpers.push(...body.slice(0, -1))
110
+ expression = body.at(-1).argument
111
+ }
112
+ const value = method ? expression.body.body.find(methodNode) : expression
113
+ let source
114
+ if (t.isFunction(value)) {
115
+ closeCallableHelpers(value, helpers)
116
+ source = text(value)
117
+ } else {
118
+ source = text(helpers.length === 0 ? value : closeDefinitionExpression(helpers, value))
119
+ }
120
+ const emittedSource = method ? `class Source{${source}}` : `(${source})`
121
+ const emitted = parse(emittedSource, nativeOptions)
122
+ return { source, emitted, emittedSource }
123
+ }
@@ -0,0 +1,25 @@
1
+ export function declarationSpan(node) {
2
+ const start = node.loc?.start
3
+ /* c8 ignore next */
4
+ const end = node.loc?.end ?? start
5
+ /* c8 ignore next */
6
+ if (start === undefined) return undefined
7
+ return {
8
+ line: start.line,
9
+ column: start.column + 1,
10
+ /* c8 ignore next */
11
+ ...(end === undefined ? {} : {
12
+ end: {
13
+ line: end.line,
14
+ column: end.column + 1,
15
+ },
16
+ }),
17
+ }
18
+ }
19
+
20
+ export class PreflightError extends Error {
21
+ constructor(message, node, span = undefined) {
22
+ super(message)
23
+ this.span = span ?? declarationSpan(node)
24
+ }
25
+ }