dsh-ptc-plus 0.3.2 → 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 (199) hide show
  1. package/README.en.md +65 -78
  2. package/README.md +65 -78
  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 +30596 -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 +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 +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 +73 -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 +25 -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 +72 -25
  56. package/docs/semantic-validation.md +29 -0
  57. package/docs/stateful-binding-updates-proposal.md +755 -0
  58. package/docs/user-bindings.en.md +64 -0
  59. package/docs/user-bindings.md +64 -0
  60. package/docs/value-wire.md +3 -3
  61. package/docs/verification.md +57 -0
  62. package/index.js +369 -92
  63. package/internal/ast-traversal.js +5 -2
  64. package/internal/binding-descriptors.js +1 -0
  65. package/internal/binding-pattern-traversal.js +25 -0
  66. package/internal/binding-pattern.js +26 -29
  67. package/internal/binding-source-analysis.js +391 -0
  68. package/internal/binding-update-policy.js +66 -0
  69. package/internal/bounded-worker-round.js +56 -0
  70. package/internal/callable-definition-environment.js +42 -0
  71. package/internal/callable-source-catalog.js +129 -0
  72. package/internal/callable-source-encoding.js +47 -0
  73. package/internal/callable-source-facts.js +459 -0
  74. package/internal/callable-source-format.js +2 -0
  75. package/internal/callable-source-lowering.js +123 -0
  76. package/internal/cell-analysis-contract.js +25 -0
  77. package/internal/cell-analysis.js +240 -290
  78. package/internal/cell-completion.js +15 -0
  79. package/internal/cell-error.js +7 -0
  80. package/internal/cell-parser.js +66 -0
  81. package/internal/cell-rewriter.js +3 -677
  82. package/internal/commonjs-export-evidence.js +63 -0
  83. package/internal/commonjs-source-evidence.js +72 -0
  84. package/internal/compiler-amaro.cjs +2 -0
  85. package/internal/compiler-compression.js +12 -0
  86. package/internal/compiler-data.js +58 -0
  87. package/internal/compiler-descriptors.js +54 -0
  88. package/internal/compiler-dynamic-cache.js +48 -0
  89. package/internal/compiler-entry.js +35 -0
  90. package/internal/compiler-generated-names.js +17 -0
  91. package/internal/compiler-intrinsics.js +84 -0
  92. package/internal/compiler-module-links.js +34 -0
  93. package/internal/compiler-operations.js +61 -0
  94. package/internal/compiler-platform-factory.js +84 -0
  95. package/internal/compiler-platform.js +136 -0
  96. package/internal/compiler-record-roles.js +53 -0
  97. package/internal/compiler-region-output.js +259 -0
  98. package/internal/compiler-resource-lowering.js +103 -0
  99. package/internal/compiler-scope-facts.js +115 -0
  100. package/internal/compiler-service.js +109 -0
  101. package/internal/compiler-source-cache.js +32 -0
  102. package/internal/compiler-source-pieces.js +93 -0
  103. package/internal/compiler-source-regions.js +132 -0
  104. package/internal/compiler-storage-source.js +54 -0
  105. package/internal/compiler-text.js +15 -0
  106. package/internal/compiler-typescript.cjs +2 -0
  107. package/internal/config-spec.js +83 -27
  108. package/internal/cordis-tools-owner.js +130 -21
  109. package/internal/diagnostic.js +27 -1
  110. package/internal/direct-surface-owner.js +76 -23
  111. package/internal/dynamic-binding-evidence.js +16 -0
  112. package/internal/dynamic-environment-compiler.js +779 -0
  113. package/internal/dynamic-environment-integration.js +87 -0
  114. package/internal/dynamic-environment-runtime.js +362 -0
  115. package/internal/dynamic-native-calls.js +150 -0
  116. package/internal/dynamic-native-runtime.js +248 -0
  117. package/internal/dynamic-scope-analysis.js +90 -0
  118. package/internal/edit-transport-owner.js +27 -2
  119. package/internal/execution-seam-compat.js +193 -0
  120. package/internal/failure-reporting.js +118 -18
  121. package/internal/host-rpc.js +66 -0
  122. package/internal/isolated-worker.js +548 -0
  123. package/internal/kernel-child.js +139 -0
  124. package/internal/kernel-worker.js +904 -120
  125. package/internal/language-semantics.js +14 -0
  126. package/internal/legacy-binding-catalog.js +48 -0
  127. package/internal/legacy-cell-analysis.js +600 -0
  128. package/internal/legacy-cell-parser.js +50 -0
  129. package/internal/legacy-cell-rewriter.js +631 -0
  130. package/internal/legacy-repl-convenience.js +291 -0
  131. package/internal/managed-module-operations.js +170 -0
  132. package/internal/module-compilation.js +321 -0
  133. package/internal/module-runtime-platform.js +19 -0
  134. package/internal/module-transform-contract.js +12 -0
  135. package/internal/native-await-declarations.js +68 -0
  136. package/internal/native-root-compilation.js +54 -0
  137. package/internal/native-root-dynamic.js +67 -0
  138. package/internal/recovery-tips.js +27 -15
  139. package/internal/rejected-cell-editor.js +13 -8
  140. package/internal/repl-convenience.js +2 -289
  141. package/internal/repl-memory-projection.js +81 -12
  142. package/internal/repl-observation-interest.js +70 -0
  143. package/internal/repl-rewrite-contract.js +6 -0
  144. package/internal/repl-scope-normalizer.js +2091 -0
  145. package/internal/repl-value-observer.js +114 -0
  146. package/internal/rpc-contract.js +52 -0
  147. package/internal/runtime-bridge-owner.js +172 -28
  148. package/internal/runtime-config.js +48 -52
  149. package/internal/runtime-contexts.js +82 -10
  150. package/internal/runtime-messages.js +111 -0
  151. package/internal/session-cell-executor.js +401 -87
  152. package/internal/session-journal-recovery.js +684 -0
  153. package/internal/session-journal-schema.js +106 -0
  154. package/internal/session-journal.js +184 -654
  155. package/internal/session-log-view.js +42 -9
  156. package/internal/session-runtime.js +256 -95
  157. package/internal/session-state.js +247 -71
  158. package/internal/source-position-map.js +211 -71
  159. package/internal/stateful-module-compiler.js +154 -0
  160. package/internal/stateful-module-runtime.js +303 -0
  161. package/internal/stateful-root-compiler.js +598 -0
  162. package/internal/stateful-root-runtime.js +340 -0
  163. package/internal/typert-codec-compat.js +28 -0
  164. package/internal/typescript-transform.js +29 -0
  165. package/internal/user-binding-authoring.js +70 -0
  166. package/internal/user-binding-console-worker.js +184 -0
  167. package/internal/user-binding-console.js +148 -0
  168. package/internal/user-binding-draft-projection.js +335 -0
  169. package/internal/user-binding-model-context.js +31 -0
  170. package/internal/user-binding-runner.js +56 -0
  171. package/internal/user-bindings-owner.js +1065 -0
  172. package/internal/user-bindings-store.js +293 -0
  173. package/internal/user-bindings.js +353 -0
  174. package/internal/value-wire-schema.js +28 -0
  175. package/internal/value-wire.js +29 -20
  176. package/internal/worker-client.js +57 -67
  177. package/internal/worker-environment.js +45 -0
  178. package/internal/worker-output-relay.js +29 -0
  179. package/internal/worker-shutdown.js +14 -0
  180. package/package.json +75 -7
  181. package/src/client-activity.js +88 -53
  182. package/src/client-authoring-view.js +580 -0
  183. package/src/client-binding-review.js +230 -0
  184. package/src/client-bindings-data.js +29 -0
  185. package/src/client-catalog.js +127 -0
  186. package/src/client-code-editor.js +84 -0
  187. package/src/client-console.js +112 -0
  188. package/src/client-copy.js +370 -0
  189. package/src/client-feature-gates.js +62 -0
  190. package/src/client-host-compat.js +150 -0
  191. package/src/client-indicator-view.js +267 -0
  192. package/src/client-repl-view.js +292 -0
  193. package/src/client-rpc.js +20 -0
  194. package/src/client-settings-view.js +221 -0
  195. package/src/client-styles.js +106 -0
  196. package/src/client-tool-view.js +108 -0
  197. package/src/client-transport.js +159 -0
  198. package/src/client-workbench.js +521 -0
  199. package/src/client.js +233 -615
@@ -0,0 +1,136 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { createHash, Hash } from 'node:crypto'
3
+ import { SourceMap, stripTypeScriptTypes } from 'node:module'
4
+ import { URL, fileURLToPath, pathToFileURL } from 'node:url'
5
+ import { TextDecoder, TextEncoder, types } from 'node:util'
6
+ import { Script } from 'node:vm'
7
+ import { readFileSync } from 'node:fs'
8
+ import { compilerDescriptors } from './compiler-descriptors.js'
9
+
10
+ const apply = Reflect.apply
11
+ const define = compilerDescriptors.defineProperty
12
+ const descriptor = Object.getOwnPropertyDescriptor
13
+ // Node's named ESM exports can be updated by syncBuiltinESMExports. The bridge
14
+ // retains operations captured before user execution, not their live bindings.
15
+ const NativeURL = URL, NativeSourceMap = SourceMap
16
+ const NativeScript = Script, nativeReadFileSync = readFileSync
17
+ const NativeTextEncoder = TextEncoder, NativeTextDecoder = TextDecoder
18
+ const nativeCreateHash = createHash, nativeFileURLToPath = fileURLToPath
19
+ const nativePathToFileURL = pathToFileURL
20
+ const nativeStripTypeScriptTypes = stripTypeScriptTypes
21
+ const runInContext = Script.prototype.runInContext
22
+ const NativeUint8Array = Uint8Array
23
+ const NativeWeakRef = WeakRef, deref = WeakRef.prototype.deref
24
+ const NativeError = Error
25
+ const parseJson = JSON.parse
26
+ const hasOwn = Object.hasOwn
27
+ const byteLength = Buffer.byteLength
28
+ const codecs = {
29
+ utf8: { write: Buffer.prototype.utf8Write, slice: Buffer.prototype.utf8Slice },
30
+ utf16le: { write: Buffer.prototype.ucs2Write, slice: Buffer.prototype.ucs2Slice },
31
+ base64: { write: Buffer.prototype.base64Write, slice: Buffer.prototype.base64Slice },
32
+ }
33
+ const encoderEncode = NativeTextEncoder.prototype.encode
34
+ const encoderEncodeInto = NativeTextEncoder.prototype.encodeInto
35
+ const decoderDecode = NativeTextDecoder.prototype.decode
36
+ const hashUpdate = Hash.prototype.update, hashDigest = Hash.prototype.digest
37
+ const sourceMapEntry = NativeSourceMap.prototype.findEntry
38
+ const sourceMapOrigin = NativeSourceMap.prototype.findOrigin
39
+ const urlFields = ['href', 'protocol', 'hostname', 'pathname']
40
+ const urlGetters = urlFields.map(name => descriptor(NativeURL.prototype, name).get)
41
+ const cwd = process.cwd()
42
+ const nodeVersion = process.versions.node
43
+ const platform = process.platform
44
+ const typescript = process.features.typescript
45
+ const typeChecks = { isMap: types.isMap, isSet: types.isSet, isUint32Array: types.isUint32Array }
46
+
47
+ function nativeUrl(input, base) {
48
+ const url = new NativeURL(input, base)
49
+ return ownUrl(url)
50
+ }
51
+
52
+ function ownUrl(url) {
53
+ // fileURLToPath reads these public accessors. Snapshot their captured native
54
+ // values on the private instance before calling the platform operation.
55
+ for (let index = 0; index < urlFields.length; index++) {
56
+ define(url, urlFields[index], { value: apply(urlGetters[index], url, []) })
57
+ }
58
+ return url
59
+ }
60
+
61
+ export const compilerPlatformBridge = {
62
+ cwd, nodeVersion, platform, typescript,
63
+ stripTypeScriptTypes: (source, options) => nativeStripTypeScriptTypes(source, options),
64
+ url: (input, base) => nativeUrl(input, base).href,
65
+ fileURLToPath: input => nativeFileURLToPath(nativeUrl(input)),
66
+ pathToFileURL: input => ownUrl(nativePathToFileURL(input)),
67
+ encode(source, encoding) {
68
+ const buffer = new NativeUint8Array(byteLength(source, encoding))
69
+ const length = apply(codecs[encoding].write, buffer, [source, 0, buffer.length])
70
+ return { buffer, length }
71
+ },
72
+ decode: (buffer, encoding) => apply(codecs[encoding].slice, buffer, [0, buffer.length]),
73
+ textEncoder() {
74
+ const encoder = new NativeTextEncoder()
75
+ return { encode: source => apply(encoderEncode, encoder, [source]),
76
+ encodeInto: (source, output) => apply(encoderEncodeInto, encoder, [source, output]) }
77
+ },
78
+ textDecoder: (label, options) => {
79
+ const decoder = new NativeTextDecoder(label, options)
80
+ return (input, options) => apply(decoderDecode, decoder, [input, options])
81
+ },
82
+ hash(algorithm) {
83
+ const hash = nativeCreateHash(algorithm)
84
+ return { update: (source, encoding) => { apply(hashUpdate, hash, [source, encoding]) },
85
+ digest: encoding => apply(hashDigest, hash, [encoding]) }
86
+ },
87
+ sourceMap(payload) {
88
+ const map = new NativeSourceMap(payload)
89
+ define(map, 'findEntry', { value: sourceMapEntry })
90
+ return { findEntry: (line, column) => apply(sourceMapEntry, map, [line, column]),
91
+ findOrigin: (line, column) => apply(sourceMapOrigin, map, [line, column]) }
92
+ },
93
+ typeChecks,
94
+ }
95
+
96
+ const platformUrl = new NativeURL('../compiler-platform.cjs', import.meta.url)
97
+ const platformFilename = nativeFileURLToPath(platformUrl)
98
+ let platformScript
99
+ const dependencyFiles = { __proto__: null,
100
+ typescript: nativeFileURLToPath(new NativeURL('../compiler-typescript.cjs', import.meta.url)),
101
+ amaro: nativeFileURLToPath(new NativeURL('../compiler-amaro.cjs', import.meta.url)),
102
+ }
103
+ const assetManifestUrl = new NativeURL('../compiler-assets.json', import.meta.url)
104
+ const assetManifestFilename = nativeFileURLToPath(assetManifestUrl)
105
+ const assetManifestHref = assetManifestUrl.href
106
+ let assetFiles
107
+
108
+ export function installCompilerPlatform(context) {
109
+ platformScript ??= new NativeScript(nativeReadFileSync(platformFilename, 'utf8'),
110
+ { __proto__: null, filename: platformFilename, displayErrors: false })
111
+ define(context, 'module', { value: { exports: {} }, configurable: true, writable: true })
112
+ apply(runInContext, platformScript, [context, { __proto__: null, displayErrors: false }])
113
+ const dependencies = { __proto__: null }
114
+ const platform = context.module.exports.createPlatform({ ...compilerPlatformBridge,
115
+ loadAsset(name) {
116
+ assetFiles ??= parseJson(nativeReadFileSync(assetManifestFilename, 'utf8'))
117
+ if (!hasOwn(assetFiles, name)) throw new NativeError('unknown compiler asset')
118
+ const filename = nativeFileURLToPath(nativeUrl(assetFiles[name], assetManifestHref))
119
+ return parseJson(nativeReadFileSync(filename, 'utf8'))
120
+ },
121
+ loadDependency(name) {
122
+ // Fixed optional modules own no program state. A caller holds exports
123
+ // during use; idle compiler realms need not pin their code or WASM data.
124
+ let dependency = dependencies[name] === undefined ? undefined : apply(deref, dependencies[name], [])
125
+ if (dependency === undefined) {
126
+ const filename = dependencyFiles[name]
127
+ const script = new NativeScript(nativeReadFileSync(filename, 'utf8'), { __proto__: null, filename, displayErrors: false })
128
+ dependency = apply(runInContext, script, [context, { __proto__: null, displayErrors: false }])
129
+ dependencies[name] = new NativeWeakRef(dependency)
130
+ }
131
+ return dependency
132
+ },
133
+ })
134
+ const names = ['require', 'process', 'Buffer', 'URL', 'TextEncoder', 'TextDecoder']
135
+ for (let index = 0; index < names.length; index++) define(context, names[index], { value: platform[names[index]] })
136
+ }
@@ -0,0 +1,53 @@
1
+ import { types as t } from '@babel/core'
2
+
3
+ /** Prove ownership from consumers, before helper operations lose binding facts.
4
+ * Reading a field exposes its value, not its record. Passing or publishing the
5
+ * record itself requires a known private consumer; uncertainty retains the
6
+ * ordinary source-facing object contract. */
7
+ export function privateGeneratedRecords(file) {
8
+ const records = new WeakSet()
9
+ const resolve = path => {
10
+ if (!path.isIdentifier()) return path
11
+ const binding = path.scope.getBinding(path.node.name)
12
+ return binding?.constant && binding.path.isVariableDeclarator() ? binding.path.get('init') : path
13
+ }
14
+ const privateBinding = (binding, seen) => {
15
+ if (!binding || binding.identifier.loc) return false
16
+ if (seen.has(binding)) return true
17
+ seen.add(binding)
18
+ return binding.referencePaths.every(path => privateUse(path, seen))
19
+ }
20
+ const privateUse = (path, seen) => {
21
+ const parent = path.parentPath
22
+ if (parent.isMemberExpression() && path.key === 'object') {
23
+ return !parent.parentPath.isCallExpression({ callee: parent.node })
24
+ && !parent.parentPath.isOptionalCallExpression({ callee: parent.node })
25
+ && !parent.parentPath.isTaggedTemplateExpression({ tag: parent.node })
26
+ }
27
+ if (parent.isVariableDeclarator() && path.key === 'init' && t.isIdentifier(parent.node.id)) {
28
+ return privateBinding(parent.scope.getBinding(parent.node.id.name), seen)
29
+ }
30
+ if (parent.isAssignmentExpression({ operator: '=' }) && path.key === 'right' && t.isIdentifier(parent.node.left)) {
31
+ return privateBinding(parent.scope.getBinding(parent.node.left.name), seen)
32
+ && (parent.parentPath.isExpressionStatement() || privateUse(parent, seen))
33
+ }
34
+ if (!parent.isCallExpression() || path.listKey !== 'arguments') return false
35
+ const callee = resolve(parent.get('callee'))
36
+ if (!callee.isMemberExpression() || callee.node.computed) return false
37
+ const receiver = resolve(callee.get('object'))
38
+ if (callee.node.property.name === 'bind' && receiver.isFunctionExpression() && !receiver.node.loc && path.key > 0) {
39
+ const parameter = receiver.node.params[path.key - 1]
40
+ return t.isIdentifier(parameter) && privateBinding(receiver.scope.getBinding(parameter.name), seen)
41
+ }
42
+ // Captured descriptor operations consume own fields, never publish the
43
+ // transport object. Source Object calls have locations and are excluded.
44
+ return path.key === 2 && callee.node.property.name === 'defineProperty'
45
+ && receiver.isIdentifier({ name: 'Object' }) && !receiver.node.loc
46
+ }
47
+ file.path.traverse({ ObjectExpression(path) {
48
+ if (!path.node.loc && path.node.properties.every(property => t.isObjectProperty(property)
49
+ && !property.computed && !t.isIdentifier(property.key, { name: '__proto__' })
50
+ && !t.isStringLiteral(property.key, { value: '__proto__' })) && privateUse(path, new Set())) records.add(path.node)
51
+ } })
52
+ return records
53
+ }
@@ -0,0 +1,259 @@
1
+ import { parse } from '@babel/parser'
2
+ import { types as t } from '@babel/core'
3
+ import { createGeneratedNameAllocator } from './binding-pattern.js'
4
+ import { applySourceEdits, identitySourceMap, createSourceMapBuilder, mappedSourceTransform, sourceLineStarts } from './source-position-map.js'
5
+
6
+ const REGION_LENGTH = 32768
7
+
8
+ /** Scheduling closures end with compilation; only source spans reach runtime. */
9
+ export const sourceRegionData = facts => facts === undefined ? undefined : { regions: facts.regions }
10
+
11
+ function collectSourceFeatures(node, features) {
12
+ if (node.decorators?.length || node.type === 'ClassAccessorProperty') features.decorated = true
13
+ if (node.type === 'VariableDeclaration' && (node.kind === 'using' || node.kind === 'await using')) features.resources = true
14
+ if (node.type === 'WithStatement' || node.type === 'CallExpression'
15
+ && node.callee.type === 'Identifier' && node.callee.name === 'eval') features.lexicalDynamic = true
16
+ }
17
+
18
+ /** Lowering decisions use parsed syntax, including features in nested bodies. */
19
+ export function sourceFeatures(tree) {
20
+ const features = { decorated: false, resources: false, lexicalDynamic: false }
21
+ t.traverseFast(tree, node => collectSourceFeatures(node, features))
22
+ return features
23
+ }
24
+
25
+ /** Native lowering has already established syntax and declaration ownership.
26
+ * Index its source without creating a whole-module Babel path/scope graph. */
27
+ export function indexSourceRegions(source, options, tree = parse(source, options)) {
28
+ const regions = []
29
+ const features = { decorated: false, resources: false, lexicalDynamic: false }
30
+ const walk = (node, context) => {
31
+ if (!node) return
32
+ collectSourceFeatures(node, features)
33
+ if (t.isFunction(node)) context = { ...context, async: node.async, generator: node.generator, labels: [] }
34
+ if (node.type === 'LabeledStatement') context = { ...context, labels: [...context.labels ?? [], node.label.name] }
35
+ if (t.isClass(node)) context = { ...context, strict: true, privateNames: [...context.privateNames ?? [],
36
+ ...node.body.body.filter(member => member.key?.type === 'PrivateName').map(member => member.key.id.name)] }
37
+ if (node.directives?.some(item => item.value.value === 'use strict')) context = { ...context, strict: true }
38
+ if (node.type === 'BlockStatement' && node.end - node.start > REGION_LENGTH / 2) {
39
+ regions.push({ start: node.start, end: node.end, kind: 'block', ...context })
40
+ }
41
+ if (node.type === 'Program' || node.type === 'BlockStatement') {
42
+ let first, last
43
+ for (const statement of node.body) {
44
+ first ??= statement.start
45
+ last = statement.end
46
+ if (last - first >= REGION_LENGTH / 2) {
47
+ regions.push({ start: first, end: last, kind: 'statements', top: node.type === 'Program', ...context })
48
+ first = undefined
49
+ }
50
+ }
51
+ if (first !== undefined && first !== node.body[0]?.start) {
52
+ regions.push({ start: first, end: last, kind: 'statements', top: node.type === 'Program', ...context })
53
+ }
54
+ }
55
+ for (const key of t.VISITOR_KEYS[node.type] ?? []) {
56
+ const child = node[key]
57
+ if (Array.isArray(child)) { for (const item of child) walk(item, context) }
58
+ else walk(child, context)
59
+ }
60
+ }
61
+ walk(tree.program, { strict: options.sourceType === 'module' })
62
+ return { regions, allocate: createGeneratedNameAllocator(tree), ...features }
63
+ }
64
+
65
+ /** Scheduling uses typed output slots, never scans source for brace-like text. */
66
+ export function sourceRegionPlan(source, facts) {
67
+ const root = { start: 0, end: source.length, kind: 'program', children: [] }
68
+ const stack = [root]
69
+ for (const fact of [...facts?.regions ?? []].sort((a, b) => a.start - b.start || b.end - a.end)) {
70
+ while (stack.length > 1 && fact.start >= stack.at(-1).end) stack.pop()
71
+ while (stack.length > 1 && fact.end > stack.at(-1).end) stack.pop()
72
+ const parent = stack.at(-1)
73
+ if (parent.start === fact.start && parent.end === fact.end) continue
74
+ const region = { ...fact, children: [] }
75
+ parent.children.push(region)
76
+ stack.push(region)
77
+ }
78
+ const trim = region => {
79
+ if (region.end - region.start <= REGION_LENGTH) region.children = []
80
+ else for (const child of region.children) trim(child)
81
+ }
82
+ trim(root)
83
+ return root
84
+ }
85
+
86
+ export function regionInput(source, region, options) {
87
+ const changes = region.children.map((child, index) => ({ start: child.start - region.start,
88
+ end: child.end - region.start, text: '{}', child, index }))
89
+ const body = source.slice(region.start, region.end)
90
+ const outlined = applySourceEdits(body, identitySourceMap(body.length), changes)
91
+ const top = (region.kind === 'program' || region.top && region.kind === 'statements')
92
+ && !(options.wrapCommonJs && options.sourceType === 'commonjs')
93
+ let prefix = '', suffix = ''
94
+ if (!top) {
95
+ prefix = `${region.async ? 'async ' : ''}function${region.generator ? '*' : ''} __ptc_region__(){`
96
+ if (region.strict) prefix += '"use strict";'
97
+ for (const label of region.labels ?? []) prefix += `${label}:`
98
+ prefix += 'while(0){'
99
+ suffix = '\n}}'
100
+ }
101
+ const code = prefix + outlined.code + suffix
102
+ const parserOptions = { ...options, strictMode: region.strict ?? options.strictMode,
103
+ allowSuperOutsideMethod: true, allowUndeclaredExports: options.sourceType === 'module', errorRecovery: true }
104
+ const tree = parse(code, parserOptions)
105
+ for (const error of tree.errors) {
106
+ if (error.reasonCode === 'InvalidPrivateFieldResolution' && region.privateNames?.includes(error.details.identifierName)) continue
107
+ throw error
108
+ }
109
+ let selected = tree.program
110
+ if (!top) {
111
+ let statement = tree.program.body[0].body.body[0]
112
+ while (statement.type === 'LabeledStatement') statement = statement.body
113
+ selected = statement.body
114
+ }
115
+ const slots = []
116
+ let delta = prefix.length
117
+ for (const change of changes) {
118
+ slots.push({ start: change.start + delta, end: change.start + delta + change.text.length, child: change.child })
119
+ delta += change.text.length - (change.end - change.start)
120
+ }
121
+ const mappings = createSourceMapBuilder()
122
+ for (const item of outlined.sourceMap) mappings.push({ ...item,
123
+ originalStart: item.originalStart + region.start, originalEnd: item.originalEnd + region.start })
124
+ let mapped = { code: outlined.code, sourceMap: mappings.finish() }
125
+ if (prefix) mapped = applySourceEdits(mapped.code, mapped.sourceMap, [
126
+ { start: 0, end: 0, text: prefix }, { start: mapped.code.length, end: mapped.code.length, text: suffix },
127
+ ])
128
+ return { tree, selected, code, prefix, suffix, slots, outlined, region, sourceMap: mapped.sourceMap }
129
+ }
130
+
131
+ /** Edits retain region ownership when a phase changes tokens or inserts helpers. */
132
+ export function applyRegionEdits(mapped, edits) {
133
+ const ordered = [...edits].sort((a, b) => a.start - b.start || a.end - b.end)
134
+ const position = (offset, after) => {
135
+ let delta = 0
136
+ for (const edit of ordered) {
137
+ if (edit.end > offset || edit.end === offset && edit.start === edit.end && !after) break
138
+ delta += (edit.text?.length ?? 0) - (edit.end - edit.start)
139
+ }
140
+ return offset + delta
141
+ }
142
+ return { ...mapped, ...applySourceEdits(mapped.code, mapped.sourceMap, ordered),
143
+ ...(mapped.sourceRegions === undefined ? {} : { sourceRegions: { ...mapped.sourceRegions,
144
+ regions: mapped.sourceRegions.regions.map(region => ({ ...region,
145
+ start: position(region.start, true), end: position(region.end, false) })) } }) }
146
+ }
147
+
148
+ export function visitRegionSource(mapped, options, visitor, select) {
149
+ const root = sourceRegionPlan(mapped.code, mapped.sourceRegions)
150
+ const visit = region => {
151
+ if (select === undefined || select(region)) visitor(regionInput(mapped.code, region, { ...options, attachComment: false }))
152
+ for (const child of region.children) visit(child)
153
+ }
154
+ visit(root)
155
+ }
156
+
157
+ /** Compose exact child slots after the maintained generator emits their parent. */
158
+ export function transformRegionSource(mapped, options, transform, inspectOutput) {
159
+ const root = sourceRegionPlan(mapped.code, mapped.sourceRegions)
160
+ const visit = region => {
161
+ let input = regionInput(mapped.code, region, options)
162
+ let generated = transform(input)
163
+ const emission = generated.sourceOffsets === undefined
164
+ ? mappedSourceTransform(input.code, input.sourceMap, generated)
165
+ : applySourceEdits(input.code, input.sourceMap,
166
+ [{ start: 0, end: input.code.length, text: generated.code, mappings: generated.sourceOffsets }])
167
+ const starts = new Map(input.slots.map(slot => [slot.start, slot]))
168
+ const ends = new Map(input.slots.map(slot => [slot.end - 1, slot]))
169
+ let outputSlots = generated.slotRanges
170
+ let output = inspectOutput !== undefined || outputSlots === undefined && input.slots.length
171
+ ? regionInput(generated.code, { ...region, start: 0, end: generated.code.length, children: [] },
172
+ { ...options, attachComment: false }) : undefined
173
+ if (outputSlots === undefined && input.slots.length) {
174
+ const inputStarts = sourceLineStarts(input.code), outputStarts = sourceLineStarts(generated.code)
175
+ const points = generated.mappingPoints ?? generated.rawMappings.filter(item => item.original).map(item => [
176
+ outputStarts[item.generated.line - 1] + item.generated.column,
177
+ inputStarts[item.original.line - 1] + item.original.column,
178
+ ])
179
+ const origins = new Map(points)
180
+ outputSlots = []
181
+ t.traverseFast(output.selected, node => {
182
+ if (node.type !== 'BlockStatement' || node.body.length || node.directives.length) return
183
+ const start = node.start - output.prefix.length, end = node.end - output.prefix.length
184
+ const first = starts.get(origins.get(start)), last = ends.get(origins.get(end - 1))
185
+ if (first && last && first !== last) throw new Error('compiler changed a typed source-region boundary')
186
+ const slot = first ?? last
187
+ if (slot) outputSlots.push({ sourceStart: slot.start, start, end })
188
+ })
189
+ }
190
+ for (const fact of outputSlots ?? []) {
191
+ const slot = starts.get(fact.sourceStart)
192
+ if (slot) {
193
+ if (slot.generatedStart !== undefined) throw new Error('compiler duplicated a typed source-region slot')
194
+ Object.assign(slot, { generatedStart: fact.start, generatedEnd: fact.end, prefix: fact.prefix })
195
+ }
196
+ }
197
+ const slots = input.slots, emittedCallables = inspectOutput === undefined
198
+ ? generated.callableRanges ?? [] : inspectOutput(output)
199
+ input = undefined
200
+ generated = undefined
201
+ output = undefined
202
+ const replacements = slots.map(slot => {
203
+ if (slot.generatedStart === undefined || slot.generatedEnd === undefined) throw new Error('compiler lost a typed source-region slot')
204
+ let output = visit(slot.child)
205
+ if (slot.prefix) {
206
+ output = { ...output, ...applySourceEdits(output.code, output.sourceMap, [{ start: 0, end: 0, text: slot.prefix }]),
207
+ regions: output.regions.map(fact => ({ ...fact, start: fact.start + slot.prefix.length, end: fact.end + slot.prefix.length })),
208
+ callableRanges: output.callableRanges.map(fact => ({ ...fact, start: fact.start + slot.prefix.length, end: fact.end + slot.prefix.length })),
209
+ }
210
+ }
211
+ return { start: slot.generatedStart, end: slot.generatedEnd, output, fact: slot.child }
212
+ }).sort((a, b) => a.start - b.start)
213
+ const code = [], sourceMap = createSourceMapBuilder(), regions = [], callableRanges = []
214
+ let cursor = 0, offset = 0
215
+ const append = (text, mappings, from = 0, to = text.length) => {
216
+ code.push(text.slice(from, to))
217
+ for (const item of mappings) {
218
+ if (item.generatedEnd <= from) continue
219
+ if (item.generatedStart >= to) break
220
+ const left = Math.max(from, item.generatedStart), right = Math.min(to, item.generatedEnd)
221
+ const linear = item.generatedEnd - item.generatedStart === item.originalEnd - item.originalStart
222
+ sourceMap.push({ generatedStart: offset + left - from, generatedEnd: offset + right - from,
223
+ originalStart: item.originalStart + (linear ? left - item.generatedStart : 0),
224
+ originalEnd: linear ? item.originalStart + right - item.generatedStart : item.originalEnd })
225
+ }
226
+ offset += to - from
227
+ }
228
+ for (const replacement of replacements) {
229
+ append(emission.code, emission.sourceMap, cursor, replacement.start)
230
+ const start = offset
231
+ for (const fact of replacement.output.regions) regions.push({ ...fact, start: fact.start + start, end: fact.end + start })
232
+ for (const fact of replacement.output.callableRanges) callableRanges.push({ ...fact, start: fact.start + start, end: fact.end + start })
233
+ append(replacement.output.code, replacement.output.sourceMap)
234
+ const { children, ...fact } = replacement.fact
235
+ regions.push({ ...fact, start, end: offset })
236
+ cursor = replacement.end
237
+ }
238
+ append(emission.code, emission.sourceMap, cursor)
239
+ const position = value => {
240
+ let delta = 0
241
+ for (const item of replacements) {
242
+ if (value < item.end) break
243
+ delta += item.output.code.length - (item.end - item.start)
244
+ }
245
+ return value + delta
246
+ }
247
+ for (const fact of emittedCallables) callableRanges.push({ ...fact,
248
+ start: position(fact.start), end: position(fact.end) })
249
+ return { code: code.join(''), sourceMap: sourceMap.finish(true), regions, callableRanges }
250
+ }
251
+ const output = visit(root)
252
+ return { code: output.code, sourceMap: output.sourceMap, callableRanges: output.callableRanges,
253
+ sourceRegions: { ...mapped.sourceRegions, regions: output.regions } }
254
+ }
255
+
256
+ /** Validate each emitted grammar region without reparsing expanded whole output. */
257
+ export function validateRegionSource(mapped, options) {
258
+ visitRegionSource(mapped, options, () => {})
259
+ }
@@ -0,0 +1,103 @@
1
+ import { template, types as t } from '@babel/core'
2
+
3
+ // Babel owns lexical lifetimes and abrupt-completion routing. This context
4
+ // stores only private state: GetMethod, Call and Await belong to the emitter.
5
+ const contextFactory = template.statement(`function HELPER() {
6
+ const empty = {__proto__: null}, stack = [];
7
+ return {
8
+ __proto__: null, e: empty, stack,
9
+ validate(value) {
10
+ if (value != null && INTRINSICS.Object(value) !== value) {
11
+ throw new INTRINSICS.TypeError('using declarations require an object, function, null, or undefined.');
12
+ }
13
+ return value;
14
+ },
15
+ add(value, dispose, asynchronous, fallback) {
16
+ if (value != null && typeof dispose !== 'function') throw new INTRINSICS.TypeError('Object is not disposable.');
17
+ if (value != null || asynchronous) stack.push({__proto__: null, v: value, d: dispose, a: asynchronous, f: fallback});
18
+ return value;
19
+ },
20
+ suppress(error) {
21
+ if (this.e !== empty) {
22
+ if (typeof INTRINSICS.SuppressedError === 'function') error = new INTRINSICS.SuppressedError(error, this.e);
23
+ else {
24
+ const suppressed = new INTRINSICS.Error();
25
+ INTRINSICS.Object.defineProperties(suppressed, {
26
+ name: {value: 'SuppressedError', writable: true, configurable: true},
27
+ error: {value: error, writable: true, configurable: true},
28
+ suppressed: {value: this.e, writable: true, configurable: true}
29
+ });
30
+ error = suppressed;
31
+ }
32
+ }
33
+ this.e = error;
34
+ },
35
+ finish() { if (this.e !== empty) throw this.e; }
36
+ };
37
+ }`)
38
+
39
+ const acquisition = template.expression(`(
40
+ VALUE = CONTEXT.validate(INITIALIZER),
41
+ METHOD = VALUE == null ? void 0 : ASYNC ? VALUE[INTRINSICS.Symbol.asyncDispose] : void 0,
42
+ FALLBACK = ASYNC && METHOD == null,
43
+ METHOD = VALUE != null && METHOD == null ? VALUE[INTRINSICS.Symbol.dispose] : METHOD,
44
+ CONTEXT.add(VALUE, METHOD, ASYNC, FALLBACK)
45
+ )`)
46
+
47
+ const disposal = asynchronous => template.statement(`{
48
+ let RESOURCE${asynchronous ? ', STATE = 0' : ''};
49
+ while (RESOURCE = CONTEXT.stack.pop()) {
50
+ try {
51
+ ${asynchronous ? 'if (!RESOURCE.a && STATE === 1) { STATE = 0; await void 0; }' : ''}
52
+ if (RESOURCE.d) {
53
+ let VALUE, FAILED = false;
54
+ try { VALUE = (0, INTRINSICS.Reflect.apply)(RESOURCE.d, RESOURCE.v, []); }
55
+ catch (ERROR) { VALUE = ERROR; FAILED = true; }
56
+ if (FAILED) {
57
+ ${asynchronous ? 'if (RESOURCE.f) { STATE |= 2; await void 0; }' : ''}
58
+ throw VALUE;
59
+ }
60
+ ${asynchronous ? 'if (RESOURCE.a) { STATE |= 2; await (RESOURCE.f ? void 0 : VALUE); }' : ''}
61
+ } ${asynchronous ? 'else { STATE |= 1; }' : ''}
62
+ } catch (ERROR) { CONTEXT.suppress(ERROR); }
63
+ }
64
+ ${asynchronous ? 'if (STATE === 1) await void 0;' : ''}
65
+ CONTEXT.finish();
66
+ }`, { allowAwaitOutsideFunction: true })
67
+
68
+ const disposalLoops = [disposal(false), disposal(true)]
69
+
70
+ /** Replace the maintained lowerer's private protocol at its binding identity.
71
+ * Inline operations introduce blocks and temporaries, never a source callable
72
+ * activation. Acquisition completes before the original declaration initializes;
73
+ * the existing finally still controls return, break, continue and throw. */
74
+ export function lowerResourceOperations(file, intrinsics, allocate) {
75
+ const helper = file.declarations.usingCtx
76
+ const intrinsic = t.identifier(intrinsics)
77
+ const contexts = new Map()
78
+ file.path.traverse({ VariableDeclarator(path) {
79
+ if (!t.isCallExpression(path.node.init) || !t.isIdentifier(path.node.init.callee, helper)) return
80
+ contexts.set(path.scope.getBinding(path.node.id.name), path.parentPath)
81
+ } })
82
+ const names = labels => Object.fromEntries(labels.map(label => [label, t.identifier(allocate(`resource_${label.toLowerCase()}`))]))
83
+ file.path.traverse({ CallExpression: { exit(path) {
84
+ const callee = path.node.callee
85
+ if (path.node.loc || !t.isMemberExpression(callee) || callee.computed || !t.isIdentifier(callee.object)) return
86
+ const declaration = contexts.get(path.scope.getBinding(callee.object.name))
87
+ if (!declaration) return
88
+ const common = { CONTEXT: callee.object, INTRINSICS: intrinsic }
89
+ if (callee.property.name === 'u' || callee.property.name === 'a') {
90
+ const temporaries = names(['VALUE', 'METHOD', 'FALLBACK'])
91
+ declaration.insertBefore(t.variableDeclaration('let', Object.values(temporaries).map(name => t.variableDeclarator(t.cloneNode(name)))))
92
+ path.replaceWith(acquisition({ ...common, ...temporaries,
93
+ INITIALIZER: path.node.arguments[0], ASYNC: t.booleanLiteral(callee.property.name === 'a') }))
94
+ } else if (callee.property.name === 'd') {
95
+ const asynchronous = path.parentPath.isAwaitExpression()
96
+ const statement = asynchronous ? path.parentPath.parentPath : path.parentPath
97
+ statement.replaceWith(disposalLoops[Number(asynchronous)]({ ...common,
98
+ ...names(asynchronous ? ['RESOURCE', 'STATE', 'VALUE', 'FAILED', 'ERROR'] : ['RESOURCE', 'VALUE', 'FAILED', 'ERROR']) }))
99
+ }
100
+ } } })
101
+ file.scope.getBinding(helper.name).path.replaceWith(contextFactory({ HELPER: helper, INTRINSICS: intrinsic }))
102
+ file.scope.crawl()
103
+ }
@@ -0,0 +1,115 @@
1
+ import traverseModule from '@babel/traverse'
2
+ import { types as t } from '@babel/core'
3
+ import { indexSource } from './compiler-source-regions.js'
4
+ import { sourceScopePath, sourceInParameters } from './dynamic-scope-analysis.js'
5
+
6
+ const traverse = traverseModule.default ?? traverseModule
7
+
8
+ /** Node supplies these parameters to the CommonJS source activation. */
9
+ export const COMMONJS_PARAMETERS = Object.freeze(['exports', 'require', 'module', '__filename', '__dirname'])
10
+
11
+ /** Resolve original names against complete declaration ownership, including
12
+ * parameter/body separation and immutable callable/class self environments. */
13
+ export function resolveSourceBinding(facts, path, name) {
14
+ for (const scope of sourceScopePath(path)) {
15
+ const parameterEnvironment = sourceInParameters(path, scope)
16
+ const bodyScope = facts.functionBodyScopes.get(scope)
17
+ const body = facts.scopes.get(bodyScope)?.get(name)
18
+ if (body !== undefined && !parameterEnvironment) return body
19
+ const own = facts.scopes.get(scope)?.get(name)
20
+ if (own !== undefined && !(parameterEnvironment && !own.occurrences.some(item => item.role === 'parameter' || item.role === 'self'))) return own
21
+ const self = facts.scopes.get(facts.functionSelfScopes.get(scope))?.get(name)
22
+ if (self !== undefined) return self
23
+ if (t.isClass(scope.block) && facts.names.get(scope.block.id) === name) return undefined
24
+ }
25
+ return undefined
26
+ }
27
+
28
+ /** Retain the grammar's scope edges while omitting unrelated expression work. */
29
+ function shell(node, originals) {
30
+ const result = { ...node }
31
+ originals.set(result, node)
32
+ for (const key of t.VISITOR_KEYS[node.type] ?? []) result[key] = Array.isArray(node[key]) ? [] : null
33
+ if (t.isFunction(node)) { result.params = []; result.body = t.blockStatement([]) }
34
+ if (t.isClass(node)) result.body = t.classBody([])
35
+ if (t.isLabeledStatement(node)) result.label = { ...node.label }
36
+ if (t.isMethod(node) || t.isObjectProperty(node) || t.isClassProperty(node) || t.isClassPrivateProperty(node)) {
37
+ result.key = t.isPrivateName(node.key) ? t.privateName(t.identifier(node.key.id.name)) : t.numericLiteral(0)
38
+ }
39
+ return result
40
+ }
41
+
42
+ function pattern(node, copy) {
43
+ if (node === null) return null
44
+ const result = copy(node)
45
+ if (t.isIdentifier(node)) return result
46
+ if (t.isAssignmentPattern(node)) { result.left = pattern(node.left, copy); result.right ??= t.numericLiteral(0) }
47
+ else if (t.isRestElement(node)) result.argument = pattern(node.argument, copy)
48
+ else if (t.isArrayPattern(node)) result.elements = node.elements.map(item => pattern(item, copy))
49
+ else if (t.isObjectPattern(node)) result.properties = node.properties.map(item => {
50
+ if (t.isRestElement(item)) return pattern(item, copy)
51
+ const property = copy(item)
52
+ property.value = pattern(item.value, copy)
53
+ return property
54
+ })
55
+ return result
56
+ }
57
+
58
+ /** Unique occurrence names let bounded groups share their ancestry witness.
59
+ * Babel still owns declaration placement; unrelated expressions stay absent. */
60
+ export function analyzeSourceDeclarations(tree, declarations) {
61
+ const paths = indexSource(tree)
62
+ const owners = new Map()
63
+ const entries = [...declarations]
64
+ for (let offset = 0; offset < entries.length; offset += 64) {
65
+ const batch = entries.slice(offset, offset + 64)
66
+ const originals = new Map(), copies = new Map(), linked = new Set()
67
+ const copy = node => {
68
+ let result = copies.get(node)
69
+ if (result === undefined) { result = shell(node, originals); copies.set(node, result) }
70
+ return result
71
+ }
72
+ for (const [declaration] of batch) {
73
+ let selected = copy(declaration)
74
+ if (t.isVariableDeclaration(declaration)) {
75
+ selected.declarations = declaration.declarations.map(item => {
76
+ const declarator = copy(item)
77
+ declarator.id = pattern(item.id, copy)
78
+ return declarator
79
+ })
80
+ } else if (t.isFunction(declaration)) {
81
+ selected.id = declaration.id && pattern(declaration.id, copy)
82
+ selected.params = declaration.params.map(item => pattern(item, copy))
83
+ } else if (t.isClass(declaration)) selected.id = declaration.id && pattern(declaration.id, copy)
84
+ else if (t.isCatchClause(declaration)) {
85
+ selected.param = declaration.param && pattern(declaration.param, copy)
86
+ selected.body ??= t.blockStatement([])
87
+ } else if (t.isImportDeclaration(declaration)) {
88
+ selected.source = declaration.source
89
+ selected.specifiers = declaration.specifiers.map(item => ({ ...item, local: pattern(item.local, copy) }))
90
+ }
91
+ let path = paths.get(declaration)
92
+ while (path.parentPath && !linked.has(path.node)) {
93
+ linked.add(path.node)
94
+ const parent = copy(path.parent)
95
+ if (path.listKey !== null) {
96
+ if (!parent[path.listKey].includes(selected)) parent[path.listKey].push(selected)
97
+ }
98
+ else parent[path.key] = selected
99
+ selected = parent
100
+ path = path.parentPath
101
+ }
102
+ }
103
+ const witness = t.file(copies.get(tree.program))
104
+ const byName = new Map(batch.flatMap(([declaration, occurrences]) => occurrences
105
+ .map(occurrence => [occurrence.node.name, { occurrence, declaration }])))
106
+ traverse(witness, { Identifier(path) {
107
+ const { occurrence, declaration } = byName.get(path.node.name) ?? {}
108
+ if (occurrence === undefined || originals.get(path.node) !== occurrence.node) return
109
+ const binding = path.scope.getBinding(path.node.name)
110
+ const owner = occurrence.role === 'catch' ? declaration : originals.get(binding.scope.block)
111
+ owners.set(occurrence.node, paths.scopeFor(owner))
112
+ } })
113
+ }
114
+ return { paths, owners }
115
+ }