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.
- package/README.en.md +65 -78
- package/README.md +65 -78
- 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 +30596 -742
- 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 +5 -3
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
- 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 +3 -1
- package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
- 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 +5 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
- 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 +73 -21
- package/docs/binding-review-placement-design.md +200 -0
- package/docs/capability-projection.md +1 -1
- package/docs/client-ui.md +95 -19
- package/docs/durability-design.md +25 -10
- package/docs/evaluation.md +78 -6
- package/docs/installation.md +41 -6
- package/docs/publishing.md +5 -3
- package/docs/runtime-reference.md +72 -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 +369 -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 +240 -290
- 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 -677
- 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 +83 -27
- 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 +27 -2
- 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 +904 -120
- 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 -289
- package/internal/repl-memory-projection.js +81 -12
- package/internal/repl-observation-interest.js +70 -0
- package/internal/repl-rewrite-contract.js +6 -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 -52
- package/internal/runtime-contexts.js +82 -10
- package/internal/runtime-messages.js +111 -0
- package/internal/session-cell-executor.js +401 -87
- package/internal/session-journal-recovery.js +684 -0
- package/internal/session-journal-schema.js +106 -0
- package/internal/session-journal.js +184 -654
- package/internal/session-log-view.js +42 -9
- package/internal/session-runtime.js +256 -95
- package/internal/session-state.js +247 -71
- 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 +75 -7
- package/src/client-activity.js +88 -53
- 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,42 @@
|
|
|
1
|
+
import { types as t } from '@babel/core'
|
|
2
|
+
import traverseImport from '@babel/traverse'
|
|
3
|
+
|
|
4
|
+
const traverse = traverseImport.default ?? traverseImport
|
|
5
|
+
|
|
6
|
+
/** Parameters and computed keys evaluate before a callable body scope exists.
|
|
7
|
+
* Return that callable's path; an intervening callable body owns its own scope. */
|
|
8
|
+
export function callableDefinitionOwner(path) {
|
|
9
|
+
let child = path
|
|
10
|
+
while (child.parentPath && !child.parentPath.isFunction()) child = child.parentPath
|
|
11
|
+
return child.parentPath && child.key !== 'body' ? child.parentPath : undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Close definition helpers without losing surrounding suspension or lexical
|
|
15
|
+
* this/new.target. The outer owner only supports Babel's arrow conversion. */
|
|
16
|
+
export function closeDefinitionExpression(helpers, value, parameters = [], argumentsList = []) {
|
|
17
|
+
const body = t.blockStatement([...helpers, t.returnStatement(value)])
|
|
18
|
+
const factory = t.arrowFunctionExpression(parameters, body)
|
|
19
|
+
const owner = t.functionExpression(null, [], t.blockStatement([t.returnStatement(factory)]))
|
|
20
|
+
const tree = t.file(t.program([t.expressionStatement(owner)]))
|
|
21
|
+
let awaited = false, yielded = false
|
|
22
|
+
traverse(tree, { ArrowFunctionExpression(path) {
|
|
23
|
+
if (path.node !== factory) return
|
|
24
|
+
path.get('body').traverse(traverse.visitors.environmentVisitor({
|
|
25
|
+
ArrowFunctionExpression(path) { path.skip() },
|
|
26
|
+
AwaitExpression() { awaited = true },
|
|
27
|
+
YieldExpression() { yielded = true },
|
|
28
|
+
}))
|
|
29
|
+
factory.async = awaited
|
|
30
|
+
if (yielded) {
|
|
31
|
+
const converted = path.arrowFunctionToExpression()
|
|
32
|
+
converted.node.generator = true
|
|
33
|
+
}
|
|
34
|
+
path.stop()
|
|
35
|
+
} })
|
|
36
|
+
if (yielded) {
|
|
37
|
+
owner.type = 'ArrowFunctionExpression'
|
|
38
|
+
return t.yieldExpression(t.callExpression(t.callExpression(owner, []), argumentsList), true)
|
|
39
|
+
}
|
|
40
|
+
const expression = t.callExpression(factory, argumentsList)
|
|
41
|
+
return awaited ? t.awaitExpression(expression) : expression
|
|
42
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { hashText, inflateText } from './compiler-service.js'
|
|
2
|
+
import { moduleCompilerIntrinsics, moduleRuntimeIntrinsics as internal } from './compiler-intrinsics.js'
|
|
3
|
+
import { CALLABLE_SOURCE_FORMAT as FORMAT, CALLABLE_SOURCE_BLOCK_UNITS as BLOCK_UNITS } from './callable-source-format.js'
|
|
4
|
+
|
|
5
|
+
export { createCallableSourceCatalog } from './compiler-service.js'
|
|
6
|
+
|
|
7
|
+
const CACHED_BLOCKS = 8
|
|
8
|
+
const sourceKey = source => `${source.length}:${hashText(source, 'utf16le', 'base64')}`
|
|
9
|
+
|
|
10
|
+
function catalogKey(catalog) {
|
|
11
|
+
let text = ''
|
|
12
|
+
for (let index = 0; index < catalog.buffers.length; index++) {
|
|
13
|
+
const buffer = catalog.buffers[index]
|
|
14
|
+
text += `${buffer.length}:`
|
|
15
|
+
for (let block = 0; block < buffer.blocks.length; block++) {
|
|
16
|
+
text += `${buffer.blocks[block]};`
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (let index = 0; index < catalog.entries.length; index++) {
|
|
20
|
+
const entry = catalog.entries[index]
|
|
21
|
+
for (let item = 0; item < entry.length; item++) text += `${entry[item]}:`
|
|
22
|
+
text += ';'
|
|
23
|
+
}
|
|
24
|
+
return hashText(text)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sameCatalog(left, right) {
|
|
28
|
+
if (left.buffers.length !== right.buffers.length || left.entries.length !== right.entries.length) return false
|
|
29
|
+
for (let index = 0; index < left.buffers.length; index++) {
|
|
30
|
+
const a = left.buffers[index], b = right.buffers[index]
|
|
31
|
+
if (a.length !== b.length || a.blocks.length !== b.blocks.length) return false
|
|
32
|
+
for (let block = 0; block < a.blocks.length; block++) if (a.blocks[block] !== b.blocks[block]) return false
|
|
33
|
+
}
|
|
34
|
+
for (let index = 0; index < left.entries.length; index++) {
|
|
35
|
+
const a = left.entries[index], b = right.entries[index]
|
|
36
|
+
if (a.length !== b.length) return false
|
|
37
|
+
for (let item = 0; item < a.length; item++) if (a[item] !== b[item]) return false
|
|
38
|
+
}
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Digests select candidates; only an exact native-source comparison owns reflection. */
|
|
43
|
+
export function createCallableSourceRegistry() {
|
|
44
|
+
const literals = new internal.Map()
|
|
45
|
+
const catalogs = new internal.Map()
|
|
46
|
+
const candidates = new internal.Map()
|
|
47
|
+
const decoded = new internal.Map()
|
|
48
|
+
let ordinal = 0
|
|
49
|
+
const blockText = encoded => {
|
|
50
|
+
let text = internal.mapGet(decoded, encoded)
|
|
51
|
+
if (text === undefined) {
|
|
52
|
+
text = inflateText(encoded, BLOCK_UNITS * 2)
|
|
53
|
+
internal.mapSet(decoded, encoded, text)
|
|
54
|
+
if (internal.mapSize(decoded) > CACHED_BLOCKS) internal.mapDelete(decoded, internal.mapFirstKey(decoded))
|
|
55
|
+
}
|
|
56
|
+
return text
|
|
57
|
+
}
|
|
58
|
+
const visitParts = (catalog, bufferIndex, start, end, visit) => {
|
|
59
|
+
const buffer = catalog.buffers[bufferIndex]
|
|
60
|
+
while (start < end) {
|
|
61
|
+
const index = internal.floor(start / BLOCK_UNITS)
|
|
62
|
+
const offset = start % BLOCK_UNITS
|
|
63
|
+
const length = internal.min(end - start, BLOCK_UNITS - offset)
|
|
64
|
+
if (visit(internal.sliceString(blockText(buffer.blocks[index]), offset, offset + length)) === false) return false
|
|
65
|
+
start += length
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const matches = (catalog, entry, native) => {
|
|
69
|
+
let offset = 0
|
|
70
|
+
const complete = visitParts(catalog, entry[0], entry[1], entry[2], text => {
|
|
71
|
+
if (!internal.startsWith(native, text, offset)) return false
|
|
72
|
+
offset += text.length
|
|
73
|
+
})
|
|
74
|
+
return complete !== false && offset === native.length
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
register(sources) {
|
|
78
|
+
const current = ++ordinal
|
|
79
|
+
if (internal.isArray(sources)) {
|
|
80
|
+
for (let index = 0; index < sources.length; index++) {
|
|
81
|
+
const entry = sources[index]
|
|
82
|
+
internal.mapSet(literals, entry[0], { original: entry[1], ordinal: current })
|
|
83
|
+
}
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
if (sources.format !== FORMAT) throw new moduleCompilerIntrinsics.TypeError('unsupported callable source catalog')
|
|
87
|
+
const key = catalogKey(sources)
|
|
88
|
+
let bucket = internal.mapGet(catalogs, key)
|
|
89
|
+
if (bucket === undefined) internal.mapSet(catalogs, key, bucket = [])
|
|
90
|
+
for (let index = 0; index < bucket.length; index++) {
|
|
91
|
+
const registration = bucket[index]
|
|
92
|
+
if (!sameCatalog(registration.catalog, sources)) continue
|
|
93
|
+
registration.ordinal = current
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
// Compiler transport values may be reused by their caller. Snapshot the
|
|
97
|
+
// compressed facts without retaining caller-owned mutable containers.
|
|
98
|
+
const catalog = { buffers: [], entries: [] }
|
|
99
|
+
for (let index = 0; index < sources.buffers.length; index++) {
|
|
100
|
+
const buffer = sources.buffers[index]
|
|
101
|
+
internal.appendArray(catalog.buffers, { length: buffer.length, blocks: internal.copyArray(buffer.blocks) })
|
|
102
|
+
}
|
|
103
|
+
for (let index = 0; index < sources.entries.length; index++) internal.appendArray(catalog.entries, internal.copyArray(sources.entries[index]))
|
|
104
|
+
const registration = { catalog, ordinal: current }
|
|
105
|
+
internal.appendArray(bucket, registration)
|
|
106
|
+
for (let index = 0; index < catalog.entries.length; index++) {
|
|
107
|
+
const entry = catalog.entries[index]
|
|
108
|
+
let entries = internal.mapGet(candidates, entry[6])
|
|
109
|
+
if (entries === undefined) internal.mapSet(candidates, entry[6], entries = [])
|
|
110
|
+
internal.appendArray(entries, { registration, entry })
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
get(native) {
|
|
114
|
+
let found = internal.mapGet(literals, native)
|
|
115
|
+
const entries = internal.mapGet(candidates, sourceKey(native))
|
|
116
|
+
if (entries !== undefined) for (let index = 0; index < entries.length; index++) {
|
|
117
|
+
const { registration, entry } = entries[index]
|
|
118
|
+
if (found !== undefined && found.ordinal > registration.ordinal) continue
|
|
119
|
+
if (matches(registration.catalog, entry, native)) found = { ...registration, entry }
|
|
120
|
+
}
|
|
121
|
+
if (found === undefined) return undefined
|
|
122
|
+
if (found.original !== undefined) return found.original
|
|
123
|
+
const entry = found.entry
|
|
124
|
+
let original = ''
|
|
125
|
+
visitParts(found.catalog, entry[3], entry[4], entry[5], text => { original += text })
|
|
126
|
+
return original
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { hashText, deflateText } from './compiler-text.js'
|
|
2
|
+
import { CALLABLE_SOURCE_FORMAT as FORMAT, CALLABLE_SOURCE_BLOCK_UNITS as BLOCK_UNITS } from './callable-source-format.js'
|
|
3
|
+
|
|
4
|
+
/** Keep windows around referenced callables. Neighbors share compression
|
|
5
|
+
* blocks; unrelated large data regions stay outside the reflection catalog. */
|
|
6
|
+
export function createCallableSourceCatalog(buffers, ranges) {
|
|
7
|
+
const selected = buffers.map(() => [])
|
|
8
|
+
const entries = ranges.map(range => [...range])
|
|
9
|
+
for (let index = 0; index < ranges.length; index++) {
|
|
10
|
+
const range = ranges[index]
|
|
11
|
+
for (const offset of [0, 3]) {
|
|
12
|
+
const source = buffers[range[offset]]
|
|
13
|
+
const start = range[offset + 1], end = range[offset + 2]
|
|
14
|
+
if (typeof source !== 'string' || !Number.isInteger(start) || !Number.isInteger(end)
|
|
15
|
+
|| start < 0 || end < start || end > source.length) throw new TypeError('invalid callable source range')
|
|
16
|
+
selected[range[offset]].push({ start, end, index, offset })
|
|
17
|
+
}
|
|
18
|
+
const source = buffers[range[0]].slice(range[1], range[2])
|
|
19
|
+
entries[index].push(`${source.length}:${hashText(source, 'utf16le', 'base64')}`)
|
|
20
|
+
}
|
|
21
|
+
const encoded = []
|
|
22
|
+
for (let index = 0; index < selected.length; index++) {
|
|
23
|
+
const intervals = []
|
|
24
|
+
for (const item of selected[index].sort((left, right) => left.start - right.start || right.end - left.end)) {
|
|
25
|
+
let interval = intervals.at(-1)
|
|
26
|
+
if (interval === undefined || item.start - interval.end > BLOCK_UNITS) {
|
|
27
|
+
interval = { start: item.start, end: item.end, items: [] }
|
|
28
|
+
intervals.push(interval)
|
|
29
|
+
}
|
|
30
|
+
interval.end = Math.max(interval.end, item.end)
|
|
31
|
+
interval.items.push(item)
|
|
32
|
+
}
|
|
33
|
+
for (const interval of intervals) {
|
|
34
|
+
const source = buffers[index].slice(interval.start, interval.end)
|
|
35
|
+
const blocks = []
|
|
36
|
+
for (let start = 0; start < source.length; start += BLOCK_UNITS) blocks.push(deflateText(source.slice(start, start + BLOCK_UNITS)))
|
|
37
|
+
for (const item of interval.items) {
|
|
38
|
+
const entry = entries[item.index]
|
|
39
|
+
entry[item.offset] = encoded.length
|
|
40
|
+
entry[item.offset + 1] -= interval.start
|
|
41
|
+
entry[item.offset + 2] -= interval.start
|
|
42
|
+
}
|
|
43
|
+
encoded.push({ length: source.length, blocks })
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { format: FORMAT, length: entries.length, buffers: encoded, entries }
|
|
47
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { parse } from '@babel/parser'
|
|
2
|
+
import { tokenizer } from 'acorn'
|
|
3
|
+
import { types as t } from '@babel/core'
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
import { SourceMap } from 'node:module'
|
|
6
|
+
import { applySourceEdits, identitySourceMap, mappedSourceTransform, sourceLineStarts, sourceOffsetAt, visitSourceMappings } from './source-position-map.js'
|
|
7
|
+
import { createCallableSourceCatalog } from './callable-source-encoding.js'
|
|
8
|
+
import { regionInput, visitRegionSource } from './compiler-region-output.js'
|
|
9
|
+
import { lowerCallableSource, undecoratedMethodSource, decoratedClassSource } from './callable-source-lowering.js'
|
|
10
|
+
import { supportsNativeUsing, transformTypeScriptSource } from './typescript-transform.js'
|
|
11
|
+
|
|
12
|
+
const isCallable = node => t.isFunction(node) || t.isClass(node)
|
|
13
|
+
const isMethod = node => t.isObjectMethod(node) || t.isClassMethod(node) || t.isClassPrivateMethod(node)
|
|
14
|
+
const requiresTypeErasure = node => node.type.startsWith('TS') || node.type === 'Decorator' || node.type === 'ClassAccessorProperty'
|
|
15
|
+
|| node.accessibility !== undefined && node.accessibility !== null || node.abstract || node.declare
|
|
16
|
+
|| node.override || node.readonly || node.definite || node.optional
|
|
17
|
+
|
|
18
|
+
class CallableSourceFacts {
|
|
19
|
+
constructor(source) { this.buffers = [source]; this.entries = []; this.length = 0; this.aliases = [] }
|
|
20
|
+
add(marker, start, end, override) {
|
|
21
|
+
let buffer = 0
|
|
22
|
+
if (override !== undefined) { buffer = this.buffers.length; this.buffers.push(override); start = 0; end = override.length }
|
|
23
|
+
this.entries.push({ marker, buffer, start, end })
|
|
24
|
+
this.length++
|
|
25
|
+
}
|
|
26
|
+
*[Symbol.iterator]() { for (const entry of this.entries) yield [entry.marker, this.buffers[entry.buffer].slice(entry.start, entry.end)] }
|
|
27
|
+
map(callback) { return [...this].map(callback) }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const factMap = sources => sources instanceof CallableSourceFacts
|
|
31
|
+
? new Map(sources.entries.map(entry => [entry.marker, { entry, buffers: sources.buffers }]))
|
|
32
|
+
: new Map(sources.map(([marker, original]) => [marker, { original }]))
|
|
33
|
+
const originalText = fact => fact.entry === undefined ? fact.original
|
|
34
|
+
: fact.buffers[fact.entry.buffer].slice(fact.entry.start, fact.entry.end)
|
|
35
|
+
|
|
36
|
+
// Native method source starts after the static modifier and its trivia. Read
|
|
37
|
+
// tokens only at this parsed method boundary, without retaining file tokens.
|
|
38
|
+
function callableSourceStart(source, node) {
|
|
39
|
+
if (!(node.staticMethod ?? (node.static && node.type.endsWith('Method')))) return node.start
|
|
40
|
+
const start = node.decorators?.at(-1)?.end ?? node.start
|
|
41
|
+
const tokens = tokenizer(source.slice(start, node.end), { ecmaVersion: 'latest' })
|
|
42
|
+
while (tokens.getToken().value !== 'static') {}
|
|
43
|
+
return start + tokens.getToken().start
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function callableMarkerPosition(node, source, comments) {
|
|
47
|
+
if (node.type !== 'ArrowFunctionExpression') return node.body.start + 1
|
|
48
|
+
let start = source.lastIndexOf('=>', node.body.start - 1)
|
|
49
|
+
while (true) {
|
|
50
|
+
let low = 0, high = comments.length
|
|
51
|
+
while (low < high) {
|
|
52
|
+
const middle = (low + high) >>> 1
|
|
53
|
+
if (comments[middle].start <= start) low = middle + 1
|
|
54
|
+
else high = middle
|
|
55
|
+
}
|
|
56
|
+
const comment = comments[low - 1]
|
|
57
|
+
if (comment === undefined || comment.end <= start) return start
|
|
58
|
+
start = source.lastIndexOf('=>', comment.start - 1)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Carry source identity through transforms without wrapping callable values. */
|
|
63
|
+
export function markCallableSources(source, sourceMap = identitySourceMap(source.length), parserOptions = {}, {
|
|
64
|
+
lowerNativeSource, resolveOriginalSource, nativeUsing = supportsNativeUsing(),
|
|
65
|
+
} = {}) {
|
|
66
|
+
const options = { sourceType: 'unambiguous', allowAwaitOutsideFunction: true,
|
|
67
|
+
allowReturnOutsideFunction: true, errorRecovery: true, ...parserOptions }
|
|
68
|
+
let tree, nativeJavaScript = false
|
|
69
|
+
try {
|
|
70
|
+
tree = parse(source, { ...options, errorRecovery: false, plugins: ['importAttributes'] })
|
|
71
|
+
nativeJavaScript = true
|
|
72
|
+
} catch {
|
|
73
|
+
tree = parse(source, options)
|
|
74
|
+
}
|
|
75
|
+
const identity = createHash('sha256').update(source).digest('hex')
|
|
76
|
+
const sources = new CallableSourceFacts(source)
|
|
77
|
+
const typedCallables = new WeakSet()
|
|
78
|
+
const resourceCallables = new WeakSet()
|
|
79
|
+
const lowerResources = !nativeUsing && source.includes('using')
|
|
80
|
+
const typed = node => {
|
|
81
|
+
if (!node) return 0
|
|
82
|
+
let needed = requiresTypeErasure(node) ? 1 : 0
|
|
83
|
+
if (lowerResources && t.isVariableDeclaration(node) && (node.kind === 'using' || node.kind === 'await using')) needed |= 2
|
|
84
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
85
|
+
const child = node[key]
|
|
86
|
+
if (Array.isArray(child)) { for (const value of child) needed |= typed(value) }
|
|
87
|
+
else needed |= typed(child)
|
|
88
|
+
}
|
|
89
|
+
if (needed && isCallable(node)) typedCallables.add(node)
|
|
90
|
+
if ((needed & 2) && isCallable(node)) resourceCallables.add(node)
|
|
91
|
+
return needed
|
|
92
|
+
}
|
|
93
|
+
if (!nativeJavaScript || lowerResources) typed(tree.program)
|
|
94
|
+
const edits = [], typedRecords = []
|
|
95
|
+
const contexts = new WeakMap()
|
|
96
|
+
const context = (node, privateNames = []) => {
|
|
97
|
+
if (!node) return
|
|
98
|
+
if (isCallable(node)) contexts.set(node, privateNames)
|
|
99
|
+
const names = t.isClass(node) ? [...new Set([...privateNames, ...node.body.body
|
|
100
|
+
.filter(member => t.isPrivateName(member.key)).map(member => member.key.id.name)])] : privateNames
|
|
101
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
102
|
+
const child = node[key]
|
|
103
|
+
if (Array.isArray(child)) { for (const value of child) context(value, names) }
|
|
104
|
+
else context(child, names)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!nativeJavaScript || lowerResources) context(tree.program)
|
|
108
|
+
t.traverseFast(tree, node => {
|
|
109
|
+
if (t.isVariableDeclaration(node) && (node.kind === 'using' || node.kind === 'await using')) nativeJavaScript = false
|
|
110
|
+
if (!isCallable(node)) return
|
|
111
|
+
if (node.body === undefined || node.body === null) return
|
|
112
|
+
// A constructor method's callable value is the class itself. It cannot
|
|
113
|
+
// have a separate source identity from Class.prototype.constructor.
|
|
114
|
+
if (t.isClassMethod(node, { kind: 'constructor' })) return
|
|
115
|
+
const marker = `__dsh_ptc_callable_${identity}_${sources.length}__`
|
|
116
|
+
const startOffset = callableSourceStart(source, node)
|
|
117
|
+
sources.add(marker, startOffset, node.end, resolveOriginalSource?.(source.slice(startOffset, node.end)))
|
|
118
|
+
const start = callableMarkerPosition(node, source, tree.comments)
|
|
119
|
+
edits.push({ start, end: start, text: `/*${marker}*/` })
|
|
120
|
+
if (typedCallables.has(node)) typedRecords.push({ marker, node, entry: sources.entries.at(-1), privateNames: contexts.get(node),
|
|
121
|
+
resources: resourceCallables.has(node) })
|
|
122
|
+
})
|
|
123
|
+
const marked = applySourceEdits(source, sourceMap, edits)
|
|
124
|
+
if (typedRecords.length === 0) return { ...marked, callableSources: sources, nativeJavaScript }
|
|
125
|
+
// Every descendant has a source identity before its parent is lowered.
|
|
126
|
+
const markedTree = parse(marked.code, options)
|
|
127
|
+
const markedNodes = new Map([...markedCallableOwners(markedTree, sources)].map(([node, fact]) => [fact.marker, node]))
|
|
128
|
+
const originals = new Map(sources)
|
|
129
|
+
const nativeFunctions = typedRecords.map(record => markedNodes.get(record.marker))
|
|
130
|
+
.filter(node => t.isFunction(node) && !isMethod(node)).sort((left, right) => left.start - right.start)
|
|
131
|
+
const nativeRecipes = new Map()
|
|
132
|
+
const nativeFunctionIndex = start => {
|
|
133
|
+
let low = 0, high = nativeFunctions.length
|
|
134
|
+
while (low < high) {
|
|
135
|
+
const middle = (low + high) >>> 1
|
|
136
|
+
if (nativeFunctions[middle].start < start) low = middle + 1
|
|
137
|
+
else high = middle
|
|
138
|
+
}
|
|
139
|
+
return low
|
|
140
|
+
}
|
|
141
|
+
for (const record of typedRecords.sort((a, b) => a.node.end - a.node.start - (b.node.end - b.node.start))) {
|
|
142
|
+
const { marker, entry, privateNames } = record
|
|
143
|
+
const node = markedNodes.get(marker)
|
|
144
|
+
const start = callableSourceStart(marked.code, node)
|
|
145
|
+
let original = marked.code.slice(start, node.end)
|
|
146
|
+
const method = isMethod(node)
|
|
147
|
+
const classRecipe = t.isClass(node) && (node.decorators?.length || node.abstract)
|
|
148
|
+
const methodRecipe = method && (node.decorators?.length || node.params.some(parameter => parameter.decorators?.length))
|
|
149
|
+
const reused = []
|
|
150
|
+
if (classRecipe) original = decoratedClassSource(node)
|
|
151
|
+
else if (methodRecipe) original = undecoratedMethodSource(node)
|
|
152
|
+
else {
|
|
153
|
+
// A lowered function closes its generated helpers over its own source.
|
|
154
|
+
// Reuse that complete function in enclosing reflection recipes; class
|
|
155
|
+
// and method definition phases retain their separate grammar owners.
|
|
156
|
+
// Descendants are complete before their parent. Select the outermost
|
|
157
|
+
// reusable functions, jumping over the descendants each one already owns.
|
|
158
|
+
for (let index = nativeFunctionIndex(start); index < nativeFunctions.length;) {
|
|
159
|
+
const child = nativeFunctions[index]
|
|
160
|
+
if (child.start >= node.end) break
|
|
161
|
+
const replacement = nativeRecipes.get(child)
|
|
162
|
+
if (replacement === undefined) { index++; continue }
|
|
163
|
+
reused.push(replacement)
|
|
164
|
+
index = nativeFunctionIndex(child.end)
|
|
165
|
+
}
|
|
166
|
+
if (reused.length > 0) original = applySourceEdits(original, identitySourceMap(original.length), reused
|
|
167
|
+
.map(value => ({ start: value.start - start, end: value.end - start, text: value.text }))).code
|
|
168
|
+
}
|
|
169
|
+
const prefix = method ? 'class Source {' : '('
|
|
170
|
+
const suffix = method ? '}' : ')'
|
|
171
|
+
const lowerCallable = () => {
|
|
172
|
+
const lowered = lowerCallableSource(original, { method,
|
|
173
|
+
lowerNativeSource: (source, options) => lowerNativeSource(source, { ...options, nativeUsing }),
|
|
174
|
+
privateNames, sourceType: tree.program.sourceType })
|
|
175
|
+
original = lowered.source
|
|
176
|
+
const entries = [...originals].map(([key, text]) => [key, key === marker ? original : text])
|
|
177
|
+
for (const [node, fact] of markedCallableOwners(lowered.emitted, entries)) {
|
|
178
|
+
sources.aliases.push([lowered.emittedSource.slice(node.start, node.end), fact.original])
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Prefer source-preserving erasure. Runtime TypeScript constructs such as
|
|
182
|
+
// parameter properties require equivalent JavaScript initialization too.
|
|
183
|
+
let resources = record.resources
|
|
184
|
+
let erase = true
|
|
185
|
+
if (reused.length > 0) {
|
|
186
|
+
resources = false
|
|
187
|
+
erase = false
|
|
188
|
+
const reusedNodes = new Set(reused.map(value => value.node))
|
|
189
|
+
t.traverseFast(node, child => {
|
|
190
|
+
if (reusedNodes.has(child)) return t.traverseFast.skip
|
|
191
|
+
if (requiresTypeErasure(child)) erase = true
|
|
192
|
+
if (record.resources && t.isVariableDeclaration(child) && (child.kind === 'using' || child.kind === 'await using')) resources = true
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
// Replacing complete functions with proven native functions leaves native
|
|
196
|
+
// surrounding syntax intact. Their generated bodies need no erasure pass.
|
|
197
|
+
if (resources) lowerCallable()
|
|
198
|
+
else if (erase) try {
|
|
199
|
+
const erased = transformTypeScriptSource(`${prefix}${original}${suffix}`, { mode: 'strip-only' }).code
|
|
200
|
+
parse(erased, { errorRecovery: true })
|
|
201
|
+
original = erased.slice(prefix.length, erased.length - suffix.length)
|
|
202
|
+
} catch {
|
|
203
|
+
lowerCallable()
|
|
204
|
+
}
|
|
205
|
+
entry.buffer = sources.buffers.length
|
|
206
|
+
sources.buffers.push(original)
|
|
207
|
+
entry.start = 0
|
|
208
|
+
entry.end = original.length
|
|
209
|
+
originals.set(marker, original)
|
|
210
|
+
if (!method && t.isFunction(node)) nativeRecipes.set(node, { node, start, end: node.end, text: original })
|
|
211
|
+
}
|
|
212
|
+
return { ...marked, callableSources: sources, nativeJavaScript }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function markedCallableOwners(tree, sources) {
|
|
216
|
+
const declarations = []
|
|
217
|
+
t.traverseFast(tree, node => { if (isCallable(node)) declarations.push(node) })
|
|
218
|
+
declarations.sort((left, right) => left.start - right.start || right.end - left.end)
|
|
219
|
+
const facts = factMap(sources)
|
|
220
|
+
const originals = new Map()
|
|
221
|
+
const stack = []
|
|
222
|
+
let next = 0
|
|
223
|
+
for (const comment of tree.comments) {
|
|
224
|
+
const fact = facts.get(comment.value)
|
|
225
|
+
if (fact === undefined) continue
|
|
226
|
+
while (next < declarations.length && declarations[next].start <= comment.start) {
|
|
227
|
+
const node = declarations[next++]
|
|
228
|
+
while (stack.length > 0 && stack.at(-1).end < node.end) stack.pop()
|
|
229
|
+
stack.push(node)
|
|
230
|
+
}
|
|
231
|
+
while (stack.length > 0 && stack.at(-1).end < comment.end) stack.pop()
|
|
232
|
+
if (stack.length > 0) originals.set(stack.at(-1), { marker: comment.value, ...fact })
|
|
233
|
+
}
|
|
234
|
+
return originals
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function sourceOwnerPositions(code, sources, parserOptions) {
|
|
238
|
+
return new Map([...markedCallableOwners(parse(code, parserOptions), sources)]
|
|
239
|
+
.map(([node, fact]) => [`${node.type}:${node.start}`, fact]))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function callableSourceOwners(code, sources, parserOptions, transformation) {
|
|
243
|
+
const options = { sourceType: 'unambiguous', allowAwaitOutsideFunction: true,
|
|
244
|
+
allowReturnOutsideFunction: true, ...parserOptions }
|
|
245
|
+
if (transformation === undefined) {
|
|
246
|
+
const tree = parse(code, options)
|
|
247
|
+
return { tree, owners: markedCallableOwners(tree, sources) }
|
|
248
|
+
}
|
|
249
|
+
// Retain numeric owner facts, not a second complete AST while reading output.
|
|
250
|
+
const owners = sourceOwnerPositions(transformation.code, sources, options)
|
|
251
|
+
const sourceMap = new SourceMap(JSON.parse(transformation.map))
|
|
252
|
+
const starts = sourceLineStarts(transformation.code)
|
|
253
|
+
const emitted = parse(code, options)
|
|
254
|
+
const matched = new Map()
|
|
255
|
+
t.traverseFast(emitted, node => {
|
|
256
|
+
if (!isCallable(node)) return
|
|
257
|
+
const position = sourceMap.findEntry(node.loc.start.line - 1, node.loc.start.column)
|
|
258
|
+
// A preceding segment does not prove a generated callable's source owner.
|
|
259
|
+
if (position.generatedLine !== node.loc.start.line - 1 || position.generatedColumn !== node.loc.start.column
|
|
260
|
+
|| starts[position.originalLine] === undefined) return
|
|
261
|
+
const fact = owners.get(`${node.type}:${starts[position.originalLine] + position.originalColumn}`)
|
|
262
|
+
if (fact !== undefined) matched.set(node, fact)
|
|
263
|
+
})
|
|
264
|
+
return { tree: emitted, owners: matched }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Source marks own callables before transforms; exact mappings retain that ownership afterward. */
|
|
268
|
+
export function collectCallableSources(code, sources, parserOptions = {}, transformation) {
|
|
269
|
+
if (sources.length === 0) return []
|
|
270
|
+
const { owners } = callableSourceOwners(code, sources, parserOptions, transformation)
|
|
271
|
+
return [...owners].map(([node, fact]) => {
|
|
272
|
+
return [code.slice(callableSourceStart(code, node), node.end), originalText(fact)]
|
|
273
|
+
}).concat(sources.aliases ?? [])
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Emission ranges come from typed nodes and exact generator mappings. */
|
|
277
|
+
export function regionCallableRanges(input, generated, sourceRanges, parserOptions) {
|
|
278
|
+
if (!sourceRanges?.length) return []
|
|
279
|
+
const sourceByStart = new Map(sourceRanges.map(fact => [fact.start, fact]))
|
|
280
|
+
const local = new Map()
|
|
281
|
+
t.traverseFast(input.selected, node => {
|
|
282
|
+
if (!isCallable(node)) return
|
|
283
|
+
const fact = sourceByStart.get(sourceOffsetAt(input.sourceMap, node.start))
|
|
284
|
+
if (fact && sourceOffsetAt(input.sourceMap, node.end - 1) + 1 === fact.end) local.set(node.start, fact)
|
|
285
|
+
})
|
|
286
|
+
if (!local.size) return []
|
|
287
|
+
const inputStarts = sourceLineStarts(input.code), generatedStarts = sourceLineStarts(generated.code)
|
|
288
|
+
const mapped = new Map()
|
|
289
|
+
for (const item of generated.rawMappings) if (item.original) mapped.set(
|
|
290
|
+
generatedStarts[item.generated.line - 1] + item.generated.column,
|
|
291
|
+
inputStarts[item.original.line - 1] + item.original.column)
|
|
292
|
+
const output = regionInput(generated.code, { ...input.region, start: 0, end: generated.code.length, children: [] },
|
|
293
|
+
{ ...parserOptions, attachComment: false })
|
|
294
|
+
const ranges = []
|
|
295
|
+
t.traverseFast(output.selected, node => {
|
|
296
|
+
if (!isCallable(node)) return
|
|
297
|
+
const start = node.start - output.prefix.length
|
|
298
|
+
const fact = local.get(mapped.get(start))
|
|
299
|
+
if (fact) ranges.push({ marker: fact.marker, start, end: node.end - output.prefix.length,
|
|
300
|
+
staticMethod: node.static && node.type.endsWith('Method') })
|
|
301
|
+
})
|
|
302
|
+
return ranges
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Parent shells and child bodies jointly prove ownership, without retaining
|
|
306
|
+
* their ASTs or parsing the expanded module as a whole. */
|
|
307
|
+
export function createRegionCallableCollector(sources) {
|
|
308
|
+
const facts = factMap(sources)
|
|
309
|
+
const collect = input => {
|
|
310
|
+
const ranges = []
|
|
311
|
+
t.traverseFast(input.selected, node => {
|
|
312
|
+
if (isCallable(node)) ranges.push({
|
|
313
|
+
start: sourceOffsetAt(input.sourceMap, node.start),
|
|
314
|
+
end: sourceOffsetAt(input.sourceMap, node.end - 1) + 1,
|
|
315
|
+
staticMethod: node.static && node.type.endsWith('Method'),
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
for (const comment of input.tree.comments) if (facts.has(comment.value)) ranges.push({
|
|
319
|
+
marker: comment.value, start: sourceOffsetAt(input.sourceMap, comment.start),
|
|
320
|
+
end: sourceOffsetAt(input.sourceMap, comment.end - 1) + 1,
|
|
321
|
+
})
|
|
322
|
+
return ranges
|
|
323
|
+
}
|
|
324
|
+
return { collect, resolve: resolveRegionCallableRanges }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolveRegionCallableRanges(ranges) {
|
|
328
|
+
const declarations = [], comments = []
|
|
329
|
+
for (const range of ranges) (range.marker === undefined ? declarations : comments).push(range)
|
|
330
|
+
declarations.sort((a, b) => a.start - b.start || b.end - a.end)
|
|
331
|
+
comments.sort((a, b) => a.start - b.start)
|
|
332
|
+
const owners = new Map(), stack = []
|
|
333
|
+
let next = 0
|
|
334
|
+
for (const comment of comments) {
|
|
335
|
+
while (next < declarations.length && declarations[next].start <= comment.start) {
|
|
336
|
+
const node = declarations[next++]
|
|
337
|
+
while (stack.length && stack.at(-1).end < node.end) stack.pop()
|
|
338
|
+
stack.push(node)
|
|
339
|
+
}
|
|
340
|
+
while (stack.length && stack.at(-1).end < comment.end) stack.pop()
|
|
341
|
+
const owner = stack.at(-1)
|
|
342
|
+
if (owner) owners.set(owner.start, { ...owner, marker: comment.marker })
|
|
343
|
+
}
|
|
344
|
+
return owners
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function collectRegionCallableRanges(mapped, sources, options) {
|
|
348
|
+
const collector = createRegionCallableCollector(sources)
|
|
349
|
+
const ranges = []
|
|
350
|
+
visitRegionSource(mapped, options, input => {
|
|
351
|
+
for (const range of collector.collect(input)) ranges.push(range)
|
|
352
|
+
})
|
|
353
|
+
return collector.resolve(ranges)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Restore identities while the maintained transform's bounded tree and exact
|
|
357
|
+
* source-map points are available; child slots remain structural output facts. */
|
|
358
|
+
export function emitRegionCallableSources(input, generated, sourceRanges, options) {
|
|
359
|
+
const originals = new Map()
|
|
360
|
+
t.traverseFast(input.selected, node => {
|
|
361
|
+
if (!isCallable(node)) return
|
|
362
|
+
const fact = sourceRanges.get(sourceOffsetAt(input.sourceMap, node.start))
|
|
363
|
+
if (fact && sourceOffsetAt(input.sourceMap, node.end - 1) + 1 === fact.end) originals.set(node.start, fact)
|
|
364
|
+
})
|
|
365
|
+
const inputStarts = sourceLineStarts(input.code), generatedStarts = sourceLineStarts(generated.code)
|
|
366
|
+
const points = [], origins = new Map()
|
|
367
|
+
visitSourceMappings(generated, (line, column, originalLine, originalColumn) => {
|
|
368
|
+
if (originalLine === undefined) return
|
|
369
|
+
const point = [generatedStarts[line] + column, inputStarts[originalLine] + originalColumn]
|
|
370
|
+
points.push(point)
|
|
371
|
+
origins.set(...point)
|
|
372
|
+
})
|
|
373
|
+
const tree = parse(generated.code, { ...options, plugins: ['importAttributes'],
|
|
374
|
+
allowSuperOutsideMethod: true, allowUndeclaredExports: options.sourceType === 'module', errorRecovery: true })
|
|
375
|
+
for (const error of tree.errors) {
|
|
376
|
+
if (error.reasonCode === 'InvalidPrivateFieldResolution' && input.region.privateNames?.includes(error.details.identifierName)) continue
|
|
377
|
+
throw error
|
|
378
|
+
}
|
|
379
|
+
let from = 0, to = generated.code.length
|
|
380
|
+
if (input.prefix) {
|
|
381
|
+
let statement = tree.program.body[0].body.body[0]
|
|
382
|
+
while (statement.type === 'LabeledStatement') statement = statement.body
|
|
383
|
+
const body = statement.body.body
|
|
384
|
+
from = body[0]?.start ?? statement.body.start + 1
|
|
385
|
+
to = body.at(-1)?.end ?? from
|
|
386
|
+
}
|
|
387
|
+
const owners = []
|
|
388
|
+
const slotStarts = new Map(input.slots.map(slot => [slot.start, slot]))
|
|
389
|
+
const slotEnds = new Map(input.slots.map(slot => [slot.end - 1, slot]))
|
|
390
|
+
const slots = []
|
|
391
|
+
t.traverseFast(tree, node => {
|
|
392
|
+
if (node.type === 'BlockStatement' && node.body.length === 0 && node.directives.length === 0) {
|
|
393
|
+
const slot = slotStarts.get(origins.get(node.start)) ?? slotEnds.get(origins.get(node.end - 1))
|
|
394
|
+
if (slot) slots.push({ sourceStart: slot.start, start: node.start, end: node.end, prefix: '' })
|
|
395
|
+
}
|
|
396
|
+
if (!isCallable(node) || node.start < from || node.end > to) return
|
|
397
|
+
const fact = originals.get(origins.get(node.start))
|
|
398
|
+
if (fact) owners.push({ node, fact })
|
|
399
|
+
})
|
|
400
|
+
const markers = new Set([...originals.values()].map(fact => fact.marker))
|
|
401
|
+
const edits = tree.comments.filter(comment => markers.has(comment.value))
|
|
402
|
+
.map(comment => ({ start: comment.start, end: comment.end, text: '' }))
|
|
403
|
+
for (const { node, fact } of owners) {
|
|
404
|
+
const start = callableMarkerPosition(node, generated.code, tree.comments)
|
|
405
|
+
const text = `/*${fact.marker}*/`
|
|
406
|
+
const slot = slots.find(slot => start > slot.start && start < slot.end)
|
|
407
|
+
if (slot) slot.prefix += text
|
|
408
|
+
else edits.push({ start, end: start, text })
|
|
409
|
+
}
|
|
410
|
+
if (from) edits.push({ start: 0, end: from, text: '' })
|
|
411
|
+
if (to < generated.code.length) edits.push({ start: to, end: generated.code.length, text: '' })
|
|
412
|
+
edits.sort((a, b) => a.start - b.start || a.end - b.end)
|
|
413
|
+
// Non-overlapping edits have ordered ends. Include every edit ending at a
|
|
414
|
+
// queried boundary, including all insertions at that same position.
|
|
415
|
+
const deltas = [0]
|
|
416
|
+
for (const edit of edits) deltas.push(deltas.at(-1) + edit.text.length - (edit.end - edit.start))
|
|
417
|
+
const position = offset => {
|
|
418
|
+
let low = 0, high = edits.length
|
|
419
|
+
while (low < high) {
|
|
420
|
+
const middle = (low + high) >>> 1
|
|
421
|
+
if (edits[middle].end > offset) high = middle
|
|
422
|
+
else low = middle + 1
|
|
423
|
+
}
|
|
424
|
+
return offset + deltas[low]
|
|
425
|
+
}
|
|
426
|
+
const emission = mappedSourceTransform(input.code, identitySourceMap(input.code.length), generated)
|
|
427
|
+
const restored = applySourceEdits(emission.code, emission.sourceMap, edits)
|
|
428
|
+
return { code: restored.code, sourceOffsets: restored.sourceMap,
|
|
429
|
+
mappingPoints: points.filter(([output]) => output >= from && output < to)
|
|
430
|
+
.map(([output, original]) => [position(output), original]),
|
|
431
|
+
slotRanges: slots.map(slot => ({ ...slot, start: position(slot.start), end: position(slot.end) })),
|
|
432
|
+
callableRanges: owners.map(({ node, fact }) => ({ marker: fact.marker,
|
|
433
|
+
start: position(node.start), end: position(node.end), staticMethod: node.static && node.type.endsWith('Method') })),
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function callableSourceCatalog(code, sources, ranges) {
|
|
438
|
+
const facts = factMap(sources)
|
|
439
|
+
const buffers = [code]
|
|
440
|
+
const indexes = new Map()
|
|
441
|
+
const bufferIndex = text => {
|
|
442
|
+
let index = indexes.get(text)
|
|
443
|
+
if (index === undefined) { index = buffers.length; indexes.set(text, index); buffers.push(text) }
|
|
444
|
+
return index
|
|
445
|
+
}
|
|
446
|
+
const entries = []
|
|
447
|
+
for (const range of ranges) {
|
|
448
|
+
const fact = facts.get(range.marker)
|
|
449
|
+
if (!fact) continue
|
|
450
|
+
const start = callableSourceStart(code, range)
|
|
451
|
+
const entry = fact.entry
|
|
452
|
+
entries.push([0, start, range.end, bufferIndex(entry ? fact.buffers[entry.buffer] : fact.original),
|
|
453
|
+
entry?.start ?? 0, entry?.end ?? fact.original.length])
|
|
454
|
+
}
|
|
455
|
+
for (const [generated, original] of sources.aliases ?? []) entries.push([
|
|
456
|
+
bufferIndex(generated), 0, generated.length, bufferIndex(original), 0, original.length,
|
|
457
|
+
])
|
|
458
|
+
return createCallableSourceCatalog(buffers, entries)
|
|
459
|
+
}
|