dsh-ptc-plus 0.3.1 → 0.4.1

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 (200) hide show
  1. package/README.en.md +66 -73
  2. package/README.md +66 -73
  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 +30610 -722
  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 +554 -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 +6 -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 +6 -4
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
  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 +9 -3
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +11 -5
  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 +8 -6
  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 +17 -3
  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 +82 -22
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +96 -20
  51. package/docs/durability-design.md +61 -24
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/program-data-plane.md +6 -0
  55. package/docs/publishing.md +5 -3
  56. package/docs/runtime-reference.md +79 -25
  57. package/docs/semantic-validation.md +29 -0
  58. package/docs/stateful-binding-updates-proposal.md +755 -0
  59. package/docs/user-bindings.en.md +64 -0
  60. package/docs/user-bindings.md +64 -0
  61. package/docs/value-wire.md +3 -3
  62. package/docs/verification.md +57 -0
  63. package/index.js +374 -92
  64. package/internal/ast-traversal.js +5 -2
  65. package/internal/binding-descriptors.js +1 -0
  66. package/internal/binding-pattern-traversal.js +25 -0
  67. package/internal/binding-pattern.js +26 -29
  68. package/internal/binding-source-analysis.js +391 -0
  69. package/internal/binding-update-policy.js +66 -0
  70. package/internal/bounded-worker-round.js +56 -0
  71. package/internal/callable-definition-environment.js +42 -0
  72. package/internal/callable-source-catalog.js +129 -0
  73. package/internal/callable-source-encoding.js +47 -0
  74. package/internal/callable-source-facts.js +459 -0
  75. package/internal/callable-source-format.js +2 -0
  76. package/internal/callable-source-lowering.js +123 -0
  77. package/internal/cell-analysis-contract.js +25 -0
  78. package/internal/cell-analysis.js +252 -242
  79. package/internal/cell-completion.js +15 -0
  80. package/internal/cell-error.js +7 -0
  81. package/internal/cell-parser.js +66 -0
  82. package/internal/cell-rewriter.js +3 -553
  83. package/internal/commonjs-export-evidence.js +63 -0
  84. package/internal/commonjs-source-evidence.js +72 -0
  85. package/internal/compiler-amaro.cjs +2 -0
  86. package/internal/compiler-compression.js +12 -0
  87. package/internal/compiler-data.js +58 -0
  88. package/internal/compiler-descriptors.js +54 -0
  89. package/internal/compiler-dynamic-cache.js +48 -0
  90. package/internal/compiler-entry.js +35 -0
  91. package/internal/compiler-generated-names.js +17 -0
  92. package/internal/compiler-intrinsics.js +84 -0
  93. package/internal/compiler-module-links.js +34 -0
  94. package/internal/compiler-operations.js +61 -0
  95. package/internal/compiler-platform-factory.js +84 -0
  96. package/internal/compiler-platform.js +136 -0
  97. package/internal/compiler-record-roles.js +53 -0
  98. package/internal/compiler-region-output.js +259 -0
  99. package/internal/compiler-resource-lowering.js +103 -0
  100. package/internal/compiler-scope-facts.js +115 -0
  101. package/internal/compiler-service.js +109 -0
  102. package/internal/compiler-source-cache.js +32 -0
  103. package/internal/compiler-source-pieces.js +93 -0
  104. package/internal/compiler-source-regions.js +132 -0
  105. package/internal/compiler-storage-source.js +54 -0
  106. package/internal/compiler-text.js +15 -0
  107. package/internal/compiler-typescript.cjs +2 -0
  108. package/internal/config-spec.js +91 -25
  109. package/internal/cordis-tools-owner.js +130 -21
  110. package/internal/diagnostic.js +27 -1
  111. package/internal/direct-surface-owner.js +76 -23
  112. package/internal/dynamic-binding-evidence.js +16 -0
  113. package/internal/dynamic-environment-compiler.js +779 -0
  114. package/internal/dynamic-environment-integration.js +87 -0
  115. package/internal/dynamic-environment-runtime.js +362 -0
  116. package/internal/dynamic-native-calls.js +150 -0
  117. package/internal/dynamic-native-runtime.js +248 -0
  118. package/internal/dynamic-scope-analysis.js +90 -0
  119. package/internal/edit-transport-owner.js +28 -3
  120. package/internal/execution-seam-compat.js +193 -0
  121. package/internal/failure-reporting.js +118 -18
  122. package/internal/host-rpc.js +66 -0
  123. package/internal/isolated-worker.js +548 -0
  124. package/internal/kernel-child.js +139 -0
  125. package/internal/kernel-worker.js +911 -116
  126. package/internal/language-semantics.js +14 -0
  127. package/internal/legacy-binding-catalog.js +48 -0
  128. package/internal/legacy-cell-analysis.js +600 -0
  129. package/internal/legacy-cell-parser.js +50 -0
  130. package/internal/legacy-cell-rewriter.js +631 -0
  131. package/internal/legacy-repl-convenience.js +291 -0
  132. package/internal/managed-module-operations.js +170 -0
  133. package/internal/module-compilation.js +321 -0
  134. package/internal/module-runtime-platform.js +19 -0
  135. package/internal/module-transform-contract.js +12 -0
  136. package/internal/native-await-declarations.js +68 -0
  137. package/internal/native-root-compilation.js +54 -0
  138. package/internal/native-root-dynamic.js +67 -0
  139. package/internal/recovery-tips.js +27 -15
  140. package/internal/rejected-cell-editor.js +13 -8
  141. package/internal/repl-convenience.js +2 -201
  142. package/internal/repl-memory-projection.js +81 -12
  143. package/internal/repl-observation-interest.js +70 -0
  144. package/internal/repl-rewrite-contract.js +21 -0
  145. package/internal/repl-scope-normalizer.js +2091 -0
  146. package/internal/repl-value-observer.js +114 -0
  147. package/internal/rpc-contract.js +52 -0
  148. package/internal/runtime-bridge-owner.js +172 -28
  149. package/internal/runtime-config.js +48 -51
  150. package/internal/runtime-contexts.js +82 -10
  151. package/internal/runtime-messages.js +111 -0
  152. package/internal/session-cell-executor.js +455 -91
  153. package/internal/session-journal-recovery.js +684 -0
  154. package/internal/session-journal-schema.js +106 -0
  155. package/internal/session-journal.js +237 -440
  156. package/internal/session-log-view.js +42 -9
  157. package/internal/session-runtime.js +325 -80
  158. package/internal/session-state.js +249 -40
  159. package/internal/source-position-map.js +211 -71
  160. package/internal/stateful-module-compiler.js +154 -0
  161. package/internal/stateful-module-runtime.js +303 -0
  162. package/internal/stateful-root-compiler.js +598 -0
  163. package/internal/stateful-root-runtime.js +340 -0
  164. package/internal/typert-codec-compat.js +28 -0
  165. package/internal/typescript-transform.js +29 -0
  166. package/internal/user-binding-authoring.js +70 -0
  167. package/internal/user-binding-console-worker.js +184 -0
  168. package/internal/user-binding-console.js +148 -0
  169. package/internal/user-binding-draft-projection.js +335 -0
  170. package/internal/user-binding-model-context.js +31 -0
  171. package/internal/user-binding-runner.js +56 -0
  172. package/internal/user-bindings-owner.js +1065 -0
  173. package/internal/user-bindings-store.js +293 -0
  174. package/internal/user-bindings.js +353 -0
  175. package/internal/value-wire-schema.js +28 -0
  176. package/internal/value-wire.js +29 -20
  177. package/internal/worker-client.js +57 -67
  178. package/internal/worker-environment.js +45 -0
  179. package/internal/worker-output-relay.js +29 -0
  180. package/internal/worker-shutdown.js +14 -0
  181. package/package.json +81 -11
  182. package/src/client-activity.js +109 -45
  183. package/src/client-authoring-view.js +580 -0
  184. package/src/client-binding-review.js +230 -0
  185. package/src/client-bindings-data.js +29 -0
  186. package/src/client-catalog.js +127 -0
  187. package/src/client-code-editor.js +84 -0
  188. package/src/client-console.js +112 -0
  189. package/src/client-copy.js +370 -0
  190. package/src/client-feature-gates.js +62 -0
  191. package/src/client-host-compat.js +150 -0
  192. package/src/client-indicator-view.js +267 -0
  193. package/src/client-repl-view.js +292 -0
  194. package/src/client-rpc.js +20 -0
  195. package/src/client-settings-view.js +221 -0
  196. package/src/client-styles.js +106 -0
  197. package/src/client-tool-view.js +108 -0
  198. package/src/client-transport.js +159 -0
  199. package/src/client-workbench.js +521 -0
  200. package/src/client.js +233 -615
@@ -0,0 +1,42 @@
1
+ import { types as t } from '@babel/core'
2
+ import traverseImport from '@babel/traverse'
3
+
4
+ const traverse = traverseImport.default ?? traverseImport
5
+
6
+ /** Parameters and computed keys evaluate before a callable body scope exists.
7
+ * Return that callable's path; an intervening callable body owns its own scope. */
8
+ export function callableDefinitionOwner(path) {
9
+ let child = path
10
+ while (child.parentPath && !child.parentPath.isFunction()) child = child.parentPath
11
+ return child.parentPath && child.key !== 'body' ? child.parentPath : undefined
12
+ }
13
+
14
+ /** Close definition helpers without losing surrounding suspension or lexical
15
+ * this/new.target. The outer owner only supports Babel's arrow conversion. */
16
+ export function closeDefinitionExpression(helpers, value, parameters = [], argumentsList = []) {
17
+ const body = t.blockStatement([...helpers, t.returnStatement(value)])
18
+ const factory = t.arrowFunctionExpression(parameters, body)
19
+ const owner = t.functionExpression(null, [], t.blockStatement([t.returnStatement(factory)]))
20
+ const tree = t.file(t.program([t.expressionStatement(owner)]))
21
+ let awaited = false, yielded = false
22
+ traverse(tree, { ArrowFunctionExpression(path) {
23
+ if (path.node !== factory) return
24
+ path.get('body').traverse(traverse.visitors.environmentVisitor({
25
+ ArrowFunctionExpression(path) { path.skip() },
26
+ AwaitExpression() { awaited = true },
27
+ YieldExpression() { yielded = true },
28
+ }))
29
+ factory.async = awaited
30
+ if (yielded) {
31
+ const converted = path.arrowFunctionToExpression()
32
+ converted.node.generator = true
33
+ }
34
+ path.stop()
35
+ } })
36
+ if (yielded) {
37
+ owner.type = 'ArrowFunctionExpression'
38
+ return t.yieldExpression(t.callExpression(t.callExpression(owner, []), argumentsList), true)
39
+ }
40
+ const expression = t.callExpression(factory, argumentsList)
41
+ return awaited ? t.awaitExpression(expression) : expression
42
+ }
@@ -0,0 +1,129 @@
1
+ import { hashText, inflateText } from './compiler-service.js'
2
+ import { moduleCompilerIntrinsics, moduleRuntimeIntrinsics as internal } from './compiler-intrinsics.js'
3
+ import { CALLABLE_SOURCE_FORMAT as FORMAT, CALLABLE_SOURCE_BLOCK_UNITS as BLOCK_UNITS } from './callable-source-format.js'
4
+
5
+ export { createCallableSourceCatalog } from './compiler-service.js'
6
+
7
+ const CACHED_BLOCKS = 8
8
+ const sourceKey = source => `${source.length}:${hashText(source, 'utf16le', 'base64')}`
9
+
10
+ function catalogKey(catalog) {
11
+ let text = ''
12
+ for (let index = 0; index < catalog.buffers.length; index++) {
13
+ const buffer = catalog.buffers[index]
14
+ text += `${buffer.length}:`
15
+ for (let block = 0; block < buffer.blocks.length; block++) {
16
+ text += `${buffer.blocks[block]};`
17
+ }
18
+ }
19
+ for (let index = 0; index < catalog.entries.length; index++) {
20
+ const entry = catalog.entries[index]
21
+ for (let item = 0; item < entry.length; item++) text += `${entry[item]}:`
22
+ text += ';'
23
+ }
24
+ return hashText(text)
25
+ }
26
+
27
+ function sameCatalog(left, right) {
28
+ if (left.buffers.length !== right.buffers.length || left.entries.length !== right.entries.length) return false
29
+ for (let index = 0; index < left.buffers.length; index++) {
30
+ const a = left.buffers[index], b = right.buffers[index]
31
+ if (a.length !== b.length || a.blocks.length !== b.blocks.length) return false
32
+ for (let block = 0; block < a.blocks.length; block++) if (a.blocks[block] !== b.blocks[block]) return false
33
+ }
34
+ for (let index = 0; index < left.entries.length; index++) {
35
+ const a = left.entries[index], b = right.entries[index]
36
+ if (a.length !== b.length) return false
37
+ for (let item = 0; item < a.length; item++) if (a[item] !== b[item]) return false
38
+ }
39
+ return true
40
+ }
41
+
42
+ /** Digests select candidates; only an exact native-source comparison owns reflection. */
43
+ export function createCallableSourceRegistry() {
44
+ const literals = new internal.Map()
45
+ const catalogs = new internal.Map()
46
+ const candidates = new internal.Map()
47
+ const decoded = new internal.Map()
48
+ let ordinal = 0
49
+ const blockText = encoded => {
50
+ let text = internal.mapGet(decoded, encoded)
51
+ if (text === undefined) {
52
+ text = inflateText(encoded, BLOCK_UNITS * 2)
53
+ internal.mapSet(decoded, encoded, text)
54
+ if (internal.mapSize(decoded) > CACHED_BLOCKS) internal.mapDelete(decoded, internal.mapFirstKey(decoded))
55
+ }
56
+ return text
57
+ }
58
+ const visitParts = (catalog, bufferIndex, start, end, visit) => {
59
+ const buffer = catalog.buffers[bufferIndex]
60
+ while (start < end) {
61
+ const index = internal.floor(start / BLOCK_UNITS)
62
+ const offset = start % BLOCK_UNITS
63
+ const length = internal.min(end - start, BLOCK_UNITS - offset)
64
+ if (visit(internal.sliceString(blockText(buffer.blocks[index]), offset, offset + length)) === false) return false
65
+ start += length
66
+ }
67
+ }
68
+ const matches = (catalog, entry, native) => {
69
+ let offset = 0
70
+ const complete = visitParts(catalog, entry[0], entry[1], entry[2], text => {
71
+ if (!internal.startsWith(native, text, offset)) return false
72
+ offset += text.length
73
+ })
74
+ return complete !== false && offset === native.length
75
+ }
76
+ return {
77
+ register(sources) {
78
+ const current = ++ordinal
79
+ if (internal.isArray(sources)) {
80
+ for (let index = 0; index < sources.length; index++) {
81
+ const entry = sources[index]
82
+ internal.mapSet(literals, entry[0], { original: entry[1], ordinal: current })
83
+ }
84
+ return
85
+ }
86
+ if (sources.format !== FORMAT) throw new moduleCompilerIntrinsics.TypeError('unsupported callable source catalog')
87
+ const key = catalogKey(sources)
88
+ let bucket = internal.mapGet(catalogs, key)
89
+ if (bucket === undefined) internal.mapSet(catalogs, key, bucket = [])
90
+ for (let index = 0; index < bucket.length; index++) {
91
+ const registration = bucket[index]
92
+ if (!sameCatalog(registration.catalog, sources)) continue
93
+ registration.ordinal = current
94
+ return
95
+ }
96
+ // Compiler transport values may be reused by their caller. Snapshot the
97
+ // compressed facts without retaining caller-owned mutable containers.
98
+ const catalog = { buffers: [], entries: [] }
99
+ for (let index = 0; index < sources.buffers.length; index++) {
100
+ const buffer = sources.buffers[index]
101
+ internal.appendArray(catalog.buffers, { length: buffer.length, blocks: internal.copyArray(buffer.blocks) })
102
+ }
103
+ for (let index = 0; index < sources.entries.length; index++) internal.appendArray(catalog.entries, internal.copyArray(sources.entries[index]))
104
+ const registration = { catalog, ordinal: current }
105
+ internal.appendArray(bucket, registration)
106
+ for (let index = 0; index < catalog.entries.length; index++) {
107
+ const entry = catalog.entries[index]
108
+ let entries = internal.mapGet(candidates, entry[6])
109
+ if (entries === undefined) internal.mapSet(candidates, entry[6], entries = [])
110
+ internal.appendArray(entries, { registration, entry })
111
+ }
112
+ },
113
+ get(native) {
114
+ let found = internal.mapGet(literals, native)
115
+ const entries = internal.mapGet(candidates, sourceKey(native))
116
+ if (entries !== undefined) for (let index = 0; index < entries.length; index++) {
117
+ const { registration, entry } = entries[index]
118
+ if (found !== undefined && found.ordinal > registration.ordinal) continue
119
+ if (matches(registration.catalog, entry, native)) found = { ...registration, entry }
120
+ }
121
+ if (found === undefined) return undefined
122
+ if (found.original !== undefined) return found.original
123
+ const entry = found.entry
124
+ let original = ''
125
+ visitParts(found.catalog, entry[3], entry[4], entry[5], text => { original += text })
126
+ return original
127
+ },
128
+ }
129
+ }
@@ -0,0 +1,47 @@
1
+ import { hashText, deflateText } from './compiler-text.js'
2
+ import { CALLABLE_SOURCE_FORMAT as FORMAT, CALLABLE_SOURCE_BLOCK_UNITS as BLOCK_UNITS } from './callable-source-format.js'
3
+
4
+ /** Keep windows around referenced callables. Neighbors share compression
5
+ * blocks; unrelated large data regions stay outside the reflection catalog. */
6
+ export function createCallableSourceCatalog(buffers, ranges) {
7
+ const selected = buffers.map(() => [])
8
+ const entries = ranges.map(range => [...range])
9
+ for (let index = 0; index < ranges.length; index++) {
10
+ const range = ranges[index]
11
+ for (const offset of [0, 3]) {
12
+ const source = buffers[range[offset]]
13
+ const start = range[offset + 1], end = range[offset + 2]
14
+ if (typeof source !== 'string' || !Number.isInteger(start) || !Number.isInteger(end)
15
+ || start < 0 || end < start || end > source.length) throw new TypeError('invalid callable source range')
16
+ selected[range[offset]].push({ start, end, index, offset })
17
+ }
18
+ const source = buffers[range[0]].slice(range[1], range[2])
19
+ entries[index].push(`${source.length}:${hashText(source, 'utf16le', 'base64')}`)
20
+ }
21
+ const encoded = []
22
+ for (let index = 0; index < selected.length; index++) {
23
+ const intervals = []
24
+ for (const item of selected[index].sort((left, right) => left.start - right.start || right.end - left.end)) {
25
+ let interval = intervals.at(-1)
26
+ if (interval === undefined || item.start - interval.end > BLOCK_UNITS) {
27
+ interval = { start: item.start, end: item.end, items: [] }
28
+ intervals.push(interval)
29
+ }
30
+ interval.end = Math.max(interval.end, item.end)
31
+ interval.items.push(item)
32
+ }
33
+ for (const interval of intervals) {
34
+ const source = buffers[index].slice(interval.start, interval.end)
35
+ const blocks = []
36
+ for (let start = 0; start < source.length; start += BLOCK_UNITS) blocks.push(deflateText(source.slice(start, start + BLOCK_UNITS)))
37
+ for (const item of interval.items) {
38
+ const entry = entries[item.index]
39
+ entry[item.offset] = encoded.length
40
+ entry[item.offset + 1] -= interval.start
41
+ entry[item.offset + 2] -= interval.start
42
+ }
43
+ encoded.push({ length: source.length, blocks })
44
+ }
45
+ }
46
+ return { format: FORMAT, length: entries.length, buffers: encoded, entries }
47
+ }
@@ -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
+ }