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.
- package/README.en.md +66 -73
- package/README.md +66 -73
- package/assets/ptc-plus-bindings-en.png +0 -0
- package/assets/ptc-plus-bindings-zh.png +0 -0
- package/assets/ptc-plus-repair-en.png +0 -0
- package/assets/ptc-plus-repair-zh.png +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.js +30610 -722
- package/compiler-amaro.cjs +5 -0
- package/compiler-amaro.cjs.map +6 -0
- package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
- package/compiler-assets.json +3 -0
- package/compiler-core.cjs +554 -0
- package/compiler-core.cjs.map +6 -0
- package/compiler-platform.cjs +2 -0
- package/compiler-platform.cjs.map +6 -0
- package/compiler-typescript.cjs +388 -0
- package/compiler-typescript.cjs.map +6 -0
- package/docs/README.md +6 -0
- package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
- package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +6 -4
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
- package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
- package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
- package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
- package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +9 -3
- package/docs/adr/0014-persist-cell-rewrite-policy.md +11 -5
- package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
- package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +8 -6
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +17 -3
- package/docs/adr/0023-global-user-bindings.md +104 -0
- package/docs/adr/0024-repl-console-observation.md +33 -0
- package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
- package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
- package/docs/architecture.md +82 -22
- package/docs/binding-review-placement-design.md +200 -0
- package/docs/capability-projection.md +1 -1
- package/docs/client-ui.md +96 -20
- package/docs/durability-design.md +61 -24
- package/docs/evaluation.md +78 -6
- package/docs/installation.md +41 -6
- package/docs/program-data-plane.md +6 -0
- package/docs/publishing.md +5 -3
- package/docs/runtime-reference.md +79 -25
- package/docs/semantic-validation.md +29 -0
- package/docs/stateful-binding-updates-proposal.md +755 -0
- package/docs/user-bindings.en.md +64 -0
- package/docs/user-bindings.md +64 -0
- package/docs/value-wire.md +3 -3
- package/docs/verification.md +57 -0
- package/index.js +374 -92
- package/internal/ast-traversal.js +5 -2
- package/internal/binding-descriptors.js +1 -0
- package/internal/binding-pattern-traversal.js +25 -0
- package/internal/binding-pattern.js +26 -29
- package/internal/binding-source-analysis.js +391 -0
- package/internal/binding-update-policy.js +66 -0
- package/internal/bounded-worker-round.js +56 -0
- package/internal/callable-definition-environment.js +42 -0
- package/internal/callable-source-catalog.js +129 -0
- package/internal/callable-source-encoding.js +47 -0
- package/internal/callable-source-facts.js +459 -0
- package/internal/callable-source-format.js +2 -0
- package/internal/callable-source-lowering.js +123 -0
- package/internal/cell-analysis-contract.js +25 -0
- package/internal/cell-analysis.js +252 -242
- package/internal/cell-completion.js +15 -0
- package/internal/cell-error.js +7 -0
- package/internal/cell-parser.js +66 -0
- package/internal/cell-rewriter.js +3 -553
- package/internal/commonjs-export-evidence.js +63 -0
- package/internal/commonjs-source-evidence.js +72 -0
- package/internal/compiler-amaro.cjs +2 -0
- package/internal/compiler-compression.js +12 -0
- package/internal/compiler-data.js +58 -0
- package/internal/compiler-descriptors.js +54 -0
- package/internal/compiler-dynamic-cache.js +48 -0
- package/internal/compiler-entry.js +35 -0
- package/internal/compiler-generated-names.js +17 -0
- package/internal/compiler-intrinsics.js +84 -0
- package/internal/compiler-module-links.js +34 -0
- package/internal/compiler-operations.js +61 -0
- package/internal/compiler-platform-factory.js +84 -0
- package/internal/compiler-platform.js +136 -0
- package/internal/compiler-record-roles.js +53 -0
- package/internal/compiler-region-output.js +259 -0
- package/internal/compiler-resource-lowering.js +103 -0
- package/internal/compiler-scope-facts.js +115 -0
- package/internal/compiler-service.js +109 -0
- package/internal/compiler-source-cache.js +32 -0
- package/internal/compiler-source-pieces.js +93 -0
- package/internal/compiler-source-regions.js +132 -0
- package/internal/compiler-storage-source.js +54 -0
- package/internal/compiler-text.js +15 -0
- package/internal/compiler-typescript.cjs +2 -0
- package/internal/config-spec.js +91 -25
- package/internal/cordis-tools-owner.js +130 -21
- package/internal/diagnostic.js +27 -1
- package/internal/direct-surface-owner.js +76 -23
- package/internal/dynamic-binding-evidence.js +16 -0
- package/internal/dynamic-environment-compiler.js +779 -0
- package/internal/dynamic-environment-integration.js +87 -0
- package/internal/dynamic-environment-runtime.js +362 -0
- package/internal/dynamic-native-calls.js +150 -0
- package/internal/dynamic-native-runtime.js +248 -0
- package/internal/dynamic-scope-analysis.js +90 -0
- package/internal/edit-transport-owner.js +28 -3
- package/internal/execution-seam-compat.js +193 -0
- package/internal/failure-reporting.js +118 -18
- package/internal/host-rpc.js +66 -0
- package/internal/isolated-worker.js +548 -0
- package/internal/kernel-child.js +139 -0
- package/internal/kernel-worker.js +911 -116
- package/internal/language-semantics.js +14 -0
- package/internal/legacy-binding-catalog.js +48 -0
- package/internal/legacy-cell-analysis.js +600 -0
- package/internal/legacy-cell-parser.js +50 -0
- package/internal/legacy-cell-rewriter.js +631 -0
- package/internal/legacy-repl-convenience.js +291 -0
- package/internal/managed-module-operations.js +170 -0
- package/internal/module-compilation.js +321 -0
- package/internal/module-runtime-platform.js +19 -0
- package/internal/module-transform-contract.js +12 -0
- package/internal/native-await-declarations.js +68 -0
- package/internal/native-root-compilation.js +54 -0
- package/internal/native-root-dynamic.js +67 -0
- package/internal/recovery-tips.js +27 -15
- package/internal/rejected-cell-editor.js +13 -8
- package/internal/repl-convenience.js +2 -201
- package/internal/repl-memory-projection.js +81 -12
- package/internal/repl-observation-interest.js +70 -0
- package/internal/repl-rewrite-contract.js +21 -0
- package/internal/repl-scope-normalizer.js +2091 -0
- package/internal/repl-value-observer.js +114 -0
- package/internal/rpc-contract.js +52 -0
- package/internal/runtime-bridge-owner.js +172 -28
- package/internal/runtime-config.js +48 -51
- package/internal/runtime-contexts.js +82 -10
- package/internal/runtime-messages.js +111 -0
- package/internal/session-cell-executor.js +455 -91
- package/internal/session-journal-recovery.js +684 -0
- package/internal/session-journal-schema.js +106 -0
- package/internal/session-journal.js +237 -440
- package/internal/session-log-view.js +42 -9
- package/internal/session-runtime.js +325 -80
- package/internal/session-state.js +249 -40
- package/internal/source-position-map.js +211 -71
- package/internal/stateful-module-compiler.js +154 -0
- package/internal/stateful-module-runtime.js +303 -0
- package/internal/stateful-root-compiler.js +598 -0
- package/internal/stateful-root-runtime.js +340 -0
- package/internal/typert-codec-compat.js +28 -0
- package/internal/typescript-transform.js +29 -0
- package/internal/user-binding-authoring.js +70 -0
- package/internal/user-binding-console-worker.js +184 -0
- package/internal/user-binding-console.js +148 -0
- package/internal/user-binding-draft-projection.js +335 -0
- package/internal/user-binding-model-context.js +31 -0
- package/internal/user-binding-runner.js +56 -0
- package/internal/user-bindings-owner.js +1065 -0
- package/internal/user-bindings-store.js +293 -0
- package/internal/user-bindings.js +353 -0
- package/internal/value-wire-schema.js +28 -0
- package/internal/value-wire.js +29 -20
- package/internal/worker-client.js +57 -67
- package/internal/worker-environment.js +45 -0
- package/internal/worker-output-relay.js +29 -0
- package/internal/worker-shutdown.js +14 -0
- package/package.json +81 -11
- package/src/client-activity.js +109 -45
- package/src/client-authoring-view.js +580 -0
- package/src/client-binding-review.js +230 -0
- package/src/client-bindings-data.js +29 -0
- package/src/client-catalog.js +127 -0
- package/src/client-code-editor.js +84 -0
- package/src/client-console.js +112 -0
- package/src/client-copy.js +370 -0
- package/src/client-feature-gates.js +62 -0
- package/src/client-host-compat.js +150 -0
- package/src/client-indicator-view.js +267 -0
- package/src/client-repl-view.js +292 -0
- package/src/client-rpc.js +20 -0
- package/src/client-settings-view.js +221 -0
- package/src/client-styles.js +106 -0
- package/src/client-tool-view.js +108 -0
- package/src/client-transport.js +159 -0
- package/src/client-workbench.js +521 -0
- 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
|
+
}
|