@rasputin-ai/node 0.4.1 → 0.5.0-alpha.10
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.md +188 -162
- package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts +2 -37
- package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts.map +1 -1
- package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +19 -0
- package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -0
- package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +41 -0
- package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -0
- package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +5 -2
- package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
- package/dist/auto-instrumentation/transform-source.d.ts +7 -1
- package/dist/auto-instrumentation/transform-source.d.ts.map +1 -1
- package/dist/auto-instrumentation/warn-production-runtime-transform.d.ts +8 -0
- package/dist/auto-instrumentation/warn-production-runtime-transform.d.ts.map +1 -0
- package/dist/derivation/analyze-function.d.ts +42 -0
- package/dist/derivation/analyze-function.d.ts.map +1 -0
- package/dist/derivation/build-dependency-graph.d.ts +21 -0
- package/dist/derivation/build-dependency-graph.d.ts.map +1 -0
- package/dist/derivation/derivation-ir.d.ts +33 -0
- package/dist/derivation/derivation-ir.d.ts.map +1 -0
- package/dist/derivation/derivation-proof.d.ts +14 -0
- package/dist/derivation/derivation-proof.d.ts.map +1 -0
- package/dist/derivation/emit-capture-plan.d.ts +8 -0
- package/dist/derivation/emit-capture-plan.d.ts.map +1 -0
- package/dist/execution-recorder/automatic-runtime.d.ts +21 -10
- package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
- package/dist/execution-recorder/execution-journal.d.ts +84 -0
- package/dist/execution-recorder/execution-journal.d.ts.map +1 -0
- package/dist/execution-recorder/execution-recorder-types.d.ts +36 -0
- package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
- package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
- package/dist/execution-recorder/function-plan.d.ts +26 -0
- package/dist/execution-recorder/function-plan.d.ts.map +1 -0
- package/dist/execution-recorder/recorder-memory-budget.d.ts +8 -1
- package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
- package/dist/execution-recorder/safe-serialize.d.ts +8 -0
- package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
- package/dist/index.js +1432 -418
- package/dist/instrument/build.d.ts +2 -0
- package/dist/instrument/build.d.ts.map +1 -0
- package/dist/instrument/build.js +1430 -0
- package/dist/instrument/bun.js +825 -82
- package/dist/instrument/node.js +825 -82
- package/dist/rasputin-init.d.ts +5 -5
- package/dist/rasputin-init.d.ts.map +1 -1
- package/dist/sdk-meta.d.ts +1 -1
- package/dist/sdk-meta.d.ts.map +1 -1
- package/package.json +10 -2
- package/dist/execution-recorder/call-aware-event-buffer.d.ts +0 -23
- package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +0 -1
|
@@ -0,0 +1,1430 @@
|
|
|
1
|
+
// src/auto-instrumentation/instrument-compiled-esm.ts
|
|
2
|
+
import { mkdirSync, readdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname as dirname2, join as join2, relative as relative2, resolve as resolve5 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/auto-instrumentation/instrumentation-manifest-registry.ts
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import {
|
|
9
|
+
INSTRUMENTATION_MANIFEST_SCHEMA_VERSION,
|
|
10
|
+
sourceLocatorFromFile as sourceLocatorFromFile2
|
|
11
|
+
} from "@rasputin-ai/core";
|
|
12
|
+
|
|
13
|
+
// src/execution-recorder/function-plan.ts
|
|
14
|
+
import {
|
|
15
|
+
runtimeFunctionId,
|
|
16
|
+
sourceLocatorFromFile
|
|
17
|
+
} from "@rasputin-ai/core";
|
|
18
|
+
var encodeFunctionPlan = (source) => {
|
|
19
|
+
const locator = sourceLocatorFromFile(source.filePath);
|
|
20
|
+
const definition = {
|
|
21
|
+
name: source.name,
|
|
22
|
+
...locator ? { source: { ...locator, line: source.line, column: source.column } } : {}
|
|
23
|
+
};
|
|
24
|
+
return [
|
|
25
|
+
runtimeFunctionId(definition),
|
|
26
|
+
source.name,
|
|
27
|
+
locator?.packageName ?? null,
|
|
28
|
+
locator?.packageRelativePath ?? null,
|
|
29
|
+
source.line,
|
|
30
|
+
source.column
|
|
31
|
+
];
|
|
32
|
+
};
|
|
33
|
+
var definitionFromEncodedPlan = (plan) => {
|
|
34
|
+
const [functionId, name, packageName, packageRelativePath, line, column] = plan;
|
|
35
|
+
const source = packageRelativePath ? {
|
|
36
|
+
...packageName ? { packageName } : {},
|
|
37
|
+
packageRelativePath,
|
|
38
|
+
...line == null ? {} : { line },
|
|
39
|
+
...column == null ? {} : { column }
|
|
40
|
+
} : void 0;
|
|
41
|
+
return {
|
|
42
|
+
functionId,
|
|
43
|
+
name,
|
|
44
|
+
...source ? { source } : {}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/auto-instrumentation/instrumentation-manifest-registry.ts
|
|
49
|
+
var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v3";
|
|
50
|
+
var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
|
|
51
|
+
var getRegistry = () => {
|
|
52
|
+
const existing = Reflect.get(globalThis, registrySymbol);
|
|
53
|
+
if (existing?.__rasputinManifestRegistry && existing.files instanceof Map && typeof existing.generation === "number" && typeof existing.uploadedGeneration === "number") {
|
|
54
|
+
return existing;
|
|
55
|
+
}
|
|
56
|
+
const registry = {
|
|
57
|
+
__rasputinManifestRegistry: true,
|
|
58
|
+
files: /* @__PURE__ */ new Map(),
|
|
59
|
+
generation: 0,
|
|
60
|
+
uploadedGeneration: 0
|
|
61
|
+
};
|
|
62
|
+
Reflect.set(globalThis, registrySymbol, registry);
|
|
63
|
+
return registry;
|
|
64
|
+
};
|
|
65
|
+
var sourceAt = (filePath, line, column) => {
|
|
66
|
+
const source = sourceLocatorFromFile2(filePath);
|
|
67
|
+
return source ? { ...source, line, column } : void 0;
|
|
68
|
+
};
|
|
69
|
+
var stableId = (kind, ...parts) => `${kind}_${createHash("sha256").update(parts.join("\0")).digest("base64url").slice(0, 24)}`;
|
|
70
|
+
var recordInstrumentationManifestDelta = (filePath, delta) => {
|
|
71
|
+
const registry = getRegistry();
|
|
72
|
+
registry.files.set(resolve(filePath), delta);
|
|
73
|
+
registry.generation += 1;
|
|
74
|
+
registry.onDirty?.();
|
|
75
|
+
};
|
|
76
|
+
var snapshotInstrumentationManifest = (options = {}) => {
|
|
77
|
+
const functions = [];
|
|
78
|
+
const callSites = [];
|
|
79
|
+
const destructures = [];
|
|
80
|
+
const derivationPlans = [];
|
|
81
|
+
for (const delta of getRegistry().files.values()) {
|
|
82
|
+
for (const fn of delta.functions) {
|
|
83
|
+
const definition = definitionFromEncodedPlan(fn.plan);
|
|
84
|
+
if (!definition.source) continue;
|
|
85
|
+
functions.push({
|
|
86
|
+
functionId: definition.functionId,
|
|
87
|
+
source: definition.source,
|
|
88
|
+
name: fn.name,
|
|
89
|
+
startLine: fn.startLine,
|
|
90
|
+
startColumn: fn.startColumn,
|
|
91
|
+
endLine: fn.endLine,
|
|
92
|
+
endColumn: fn.endColumn,
|
|
93
|
+
params: fn.params
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
for (const site of delta.callSites) {
|
|
97
|
+
const source = sourceAt(site.filePath, site.line, site.column);
|
|
98
|
+
if (!source) continue;
|
|
99
|
+
callSites.push({
|
|
100
|
+
id: stableId(
|
|
101
|
+
"cs",
|
|
102
|
+
site.callerFunctionId,
|
|
103
|
+
source.packageName ?? "",
|
|
104
|
+
source.packageRelativePath,
|
|
105
|
+
site.line,
|
|
106
|
+
site.column
|
|
107
|
+
),
|
|
108
|
+
callerFunctionId: site.callerFunctionId,
|
|
109
|
+
calleeName: site.calleeName,
|
|
110
|
+
calleeText: site.calleeText,
|
|
111
|
+
source,
|
|
112
|
+
line: site.line,
|
|
113
|
+
column: site.column,
|
|
114
|
+
endLine: site.endLine,
|
|
115
|
+
endColumn: site.endColumn,
|
|
116
|
+
resultBinding: site.resultBinding,
|
|
117
|
+
argIdentifiers: site.argIdentifiers
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
for (const destructure of delta.destructures) {
|
|
121
|
+
const source = sourceAt(destructure.filePath, destructure.line, destructure.column);
|
|
122
|
+
if (!source) continue;
|
|
123
|
+
destructures.push({
|
|
124
|
+
callerFunctionId: destructure.callerFunctionId,
|
|
125
|
+
sourceParam: destructure.sourceParam,
|
|
126
|
+
source,
|
|
127
|
+
line: destructure.line,
|
|
128
|
+
column: destructure.column,
|
|
129
|
+
binding: destructure.binding
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
derivationPlans.push(...delta.derivationPlans ?? []);
|
|
133
|
+
}
|
|
134
|
+
const registry = getRegistry();
|
|
135
|
+
if (registry.prebuilt && registry.files.size === 0) {
|
|
136
|
+
const prebuilt = registry.prebuilt;
|
|
137
|
+
if (!options.sourceRoot && !options.sourceRoots) return prebuilt;
|
|
138
|
+
return {
|
|
139
|
+
...prebuilt,
|
|
140
|
+
sourceRoots: {
|
|
141
|
+
...options.sourceRoot ? { default: options.sourceRoot } : {},
|
|
142
|
+
packages: { ...options.sourceRoots ?? {} }
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (functions.length === 0 && callSites.length === 0 && destructures.length === 0) {
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
const packages = { ...options.sourceRoots ?? {} };
|
|
150
|
+
return {
|
|
151
|
+
schema_version: INSTRUMENTATION_MANIFEST_SCHEMA_VERSION,
|
|
152
|
+
...options.sourceRoot || Object.keys(packages).length > 0 ? {
|
|
153
|
+
sourceRoots: {
|
|
154
|
+
...options.sourceRoot ? { default: options.sourceRoot } : {},
|
|
155
|
+
packages
|
|
156
|
+
}
|
|
157
|
+
} : {},
|
|
158
|
+
functions,
|
|
159
|
+
callSites,
|
|
160
|
+
destructures,
|
|
161
|
+
derivationPlans
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
var resetInstrumentationManifestRegistry = () => {
|
|
165
|
+
Reflect.deleteProperty(globalThis, registrySymbol);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/auto-instrumentation/source-classification.ts
|
|
169
|
+
import { isAbsolute, relative, resolve as resolve2, sep } from "node:path";
|
|
170
|
+
var sourceExtension = /\.[cm]?[jt]sx?$/i;
|
|
171
|
+
var alwaysExcludedDirectory = /(^|[\\/])(node_modules|coverage|generated|\.git|\.next|\.svelte-kit|\.output|\.vercel|\.rasputin|\.turbo|\.yarn|\.pnpm|\.bun)([\\/]|$)/;
|
|
172
|
+
var buildOutputDirectory = /(^|[\\/])(dist|build|out)([\\/]|$)/;
|
|
173
|
+
var matches = (pattern, value) => {
|
|
174
|
+
pattern.lastIndex = 0;
|
|
175
|
+
const result2 = pattern.test(value);
|
|
176
|
+
pattern.lastIndex = 0;
|
|
177
|
+
return result2;
|
|
178
|
+
};
|
|
179
|
+
var isWithinRoot = (filePath, root) => {
|
|
180
|
+
const fromRoot = relative(resolve2(root), filePath);
|
|
181
|
+
return fromRoot === "" || !fromRoot.startsWith(`..${sep}`) && fromRoot !== ".." && !isAbsolute(fromRoot);
|
|
182
|
+
};
|
|
183
|
+
var isApplicationSource = (filePath, options = {}) => {
|
|
184
|
+
const absolutePath = resolve2(filePath);
|
|
185
|
+
if (!sourceExtension.test(absolutePath) || alwaysExcludedDirectory.test(absolutePath))
|
|
186
|
+
return false;
|
|
187
|
+
if (options.includeBuildOutput !== true && buildOutputDirectory.test(absolutePath)) return false;
|
|
188
|
+
if (options.excludedRoots?.some((root) => isWithinRoot(absolutePath, root))) return false;
|
|
189
|
+
if (options.exclude && matches(options.exclude, absolutePath)) return false;
|
|
190
|
+
if (options.include && !matches(options.include, absolutePath)) return false;
|
|
191
|
+
if (options.rootDir) {
|
|
192
|
+
if (!isWithinRoot(absolutePath, options.rootDir)) return false;
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// src/auto-instrumentation/source-map-locations.ts
|
|
198
|
+
import { readFileSync } from "node:fs";
|
|
199
|
+
import { dirname, isAbsolute as isAbsolute2, join, resolve as resolve3 } from "node:path";
|
|
200
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
201
|
+
import { LEAST_UPPER_BOUND, originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
|
|
202
|
+
var sourceMapComment = /(?:\/\/[#@][ \t]*sourceMappingURL=([^\s'"]+)|\/\*[#@][ \t]*sourceMappingURL=([^\s*'"]+)[ \t]*\*\/)\s*$/;
|
|
203
|
+
var decodeDataUrl = (url) => {
|
|
204
|
+
const base64 = /^data:application\/json(?:;charset=[^;,]+)?;base64,(.+)$/i.exec(url);
|
|
205
|
+
if (base64?.[1]) return Buffer.from(base64[1], "base64").toString("utf8");
|
|
206
|
+
const raw = /^data:application\/json(?:;charset=[^;,]+)?,(.+)$/i.exec(url);
|
|
207
|
+
return raw?.[1] ? decodeURIComponent(raw[1]) : void 0;
|
|
208
|
+
};
|
|
209
|
+
var sourceMapFor = (generatedPath, source) => {
|
|
210
|
+
const match = sourceMapComment.exec(source);
|
|
211
|
+
const reference = match?.[1] ?? match?.[2];
|
|
212
|
+
if (reference?.startsWith("data:")) {
|
|
213
|
+
const payload = decodeDataUrl(reference);
|
|
214
|
+
return payload ? { payload, mapUrl: pathToFileURL(generatedPath).href } : void 0;
|
|
215
|
+
}
|
|
216
|
+
const mapPath = reference ? resolve3(dirname(generatedPath), reference) : `${generatedPath}.map`;
|
|
217
|
+
return {
|
|
218
|
+
payload: readFileSync(mapPath, "utf8"),
|
|
219
|
+
mapUrl: pathToFileURL(mapPath).href
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
var toFilePath = (source, generatedPath) => {
|
|
223
|
+
if (source.startsWith("file:")) return fileURLToPath(source);
|
|
224
|
+
return isAbsolute2(source) ? source : join(dirname(generatedPath), source);
|
|
225
|
+
};
|
|
226
|
+
var createSourceMapLocationMapper = (generatedPath, source) => {
|
|
227
|
+
try {
|
|
228
|
+
const sourceMap = sourceMapFor(generatedPath, source);
|
|
229
|
+
if (!sourceMap) return void 0;
|
|
230
|
+
const traceMap = new TraceMap(sourceMap.payload, sourceMap.mapUrl);
|
|
231
|
+
return (generated) => {
|
|
232
|
+
let original = originalPositionFor(traceMap, {
|
|
233
|
+
line: generated.line,
|
|
234
|
+
column: Math.max(0, generated.column - 1)
|
|
235
|
+
});
|
|
236
|
+
if (!original.source || original.line == null) {
|
|
237
|
+
original = originalPositionFor(traceMap, {
|
|
238
|
+
line: generated.line,
|
|
239
|
+
column: 0,
|
|
240
|
+
bias: LEAST_UPPER_BOUND
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (!original.source || original.line == null) return void 0;
|
|
244
|
+
return {
|
|
245
|
+
filePath: toFilePath(original.source, generatedPath),
|
|
246
|
+
line: original.line,
|
|
247
|
+
column: (original.column ?? 0) + 1
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
} catch {
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// src/auto-instrumentation/transform-source.ts
|
|
256
|
+
import { resolve as resolve4 } from "node:path";
|
|
257
|
+
import ts3 from "typescript";
|
|
258
|
+
|
|
259
|
+
// src/derivation/analyze-function.ts
|
|
260
|
+
import ts from "typescript";
|
|
261
|
+
|
|
262
|
+
// src/derivation/build-dependency-graph.ts
|
|
263
|
+
var buildDependencyGraph = (functions, edges) => {
|
|
264
|
+
const names = new Set(functions.map((fn) => fn.name));
|
|
265
|
+
const localEdges = edges.filter((edge) => names.has(edge.calleeName));
|
|
266
|
+
const calleesByCaller = /* @__PURE__ */ new Map();
|
|
267
|
+
for (const edge of localEdges) {
|
|
268
|
+
const existing = calleesByCaller.get(edge.callerFunctionId) ?? [];
|
|
269
|
+
existing.push(edge.calleeName);
|
|
270
|
+
calleesByCaller.set(edge.callerFunctionId, existing);
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
functions: functions.map((fn) => fn.functionId),
|
|
274
|
+
edges: localEdges,
|
|
275
|
+
calleesByCaller
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
var topologicalFunctionOrder = (functions, graph) => {
|
|
279
|
+
const idByName = new Map(functions.map((fn) => [fn.name, fn.functionId]));
|
|
280
|
+
const remainingCallees = new Map(
|
|
281
|
+
functions.map((fn) => {
|
|
282
|
+
const unique = new Set(
|
|
283
|
+
(graph.calleesByCaller.get(fn.functionId) ?? []).map((name) => idByName.get(name)).filter((id) => Boolean(id) && id !== fn.functionId)
|
|
284
|
+
);
|
|
285
|
+
return [fn.functionId, unique.size];
|
|
286
|
+
})
|
|
287
|
+
);
|
|
288
|
+
const callersOf = /* @__PURE__ */ new Map();
|
|
289
|
+
for (const fn of functions) {
|
|
290
|
+
for (const calleeName of graph.calleesByCaller.get(fn.functionId) ?? []) {
|
|
291
|
+
const calleeId = idByName.get(calleeName);
|
|
292
|
+
if (!calleeId || calleeId === fn.functionId) continue;
|
|
293
|
+
const callers = callersOf.get(calleeId) ?? [];
|
|
294
|
+
callers.push(fn.functionId);
|
|
295
|
+
callersOf.set(calleeId, callers);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const queue = functions.filter((fn) => (remainingCallees.get(fn.functionId) ?? 0) === 0).map((fn) => fn.functionId);
|
|
299
|
+
const ordered = [];
|
|
300
|
+
const queued = new Set(queue);
|
|
301
|
+
while (queue.length > 0) {
|
|
302
|
+
const current = queue.shift();
|
|
303
|
+
if (current === void 0) break;
|
|
304
|
+
ordered.push(current);
|
|
305
|
+
for (const caller of callersOf.get(current) ?? []) {
|
|
306
|
+
const next = (remainingCallees.get(caller) ?? 0) - 1;
|
|
307
|
+
remainingCallees.set(caller, next);
|
|
308
|
+
if (next === 0 && !queued.has(caller)) {
|
|
309
|
+
queued.add(caller);
|
|
310
|
+
queue.push(caller);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
for (const fn of functions) {
|
|
315
|
+
if (!queued.has(fn.functionId)) ordered.push(fn.functionId);
|
|
316
|
+
}
|
|
317
|
+
return ordered;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// src/derivation/derivation-ir.ts
|
|
321
|
+
var DerivationIrBuilder = class {
|
|
322
|
+
expressions = [];
|
|
323
|
+
nextId = 0;
|
|
324
|
+
constValue(value) {
|
|
325
|
+
return this.add({ id: this.nextId++, kind: "const", value });
|
|
326
|
+
}
|
|
327
|
+
param(index) {
|
|
328
|
+
return this.add({ id: this.nextId++, kind: "param", index });
|
|
329
|
+
}
|
|
330
|
+
root(rootId) {
|
|
331
|
+
return this.add({ id: this.nextId++, kind: "root", rootId });
|
|
332
|
+
}
|
|
333
|
+
alias(from) {
|
|
334
|
+
return this.add({ id: this.nextId++, kind: "alias", from });
|
|
335
|
+
}
|
|
336
|
+
binary(op, left, right, guard) {
|
|
337
|
+
return this.add({ id: this.nextId++, kind: "binary", op, left, right, guard });
|
|
338
|
+
}
|
|
339
|
+
unary(op, operand) {
|
|
340
|
+
return this.add({ id: this.nextId++, kind: "unary", op, operand });
|
|
341
|
+
}
|
|
342
|
+
logical(op, left, right) {
|
|
343
|
+
return this.add({ id: this.nextId++, kind: "logical", op, left, right });
|
|
344
|
+
}
|
|
345
|
+
cond(test, consequent, alternate) {
|
|
346
|
+
return this.add({ id: this.nextId++, kind: "cond", test, consequent, alternate });
|
|
347
|
+
}
|
|
348
|
+
object(properties) {
|
|
349
|
+
return this.add({ id: this.nextId++, kind: "object", properties });
|
|
350
|
+
}
|
|
351
|
+
array(elements) {
|
|
352
|
+
return this.add({ id: this.nextId++, kind: "array", elements });
|
|
353
|
+
}
|
|
354
|
+
prop(object, key) {
|
|
355
|
+
return this.add({ id: this.nextId++, kind: "prop", object, key });
|
|
356
|
+
}
|
|
357
|
+
index(array, index) {
|
|
358
|
+
return this.add({ id: this.nextId++, kind: "index", array, index });
|
|
359
|
+
}
|
|
360
|
+
pureCall(calleeFunctionId, args2) {
|
|
361
|
+
return this.add({ id: this.nextId++, kind: "pure_call", calleeFunctionId, args: args2 });
|
|
362
|
+
}
|
|
363
|
+
template(parts) {
|
|
364
|
+
return this.add({ id: this.nextId++, kind: "template", parts });
|
|
365
|
+
}
|
|
366
|
+
add(expression) {
|
|
367
|
+
this.expressions.push(expression);
|
|
368
|
+
return expression.id;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
var emptyPlan = (functionId) => ({
|
|
372
|
+
planId: `dp_${functionId}`,
|
|
373
|
+
functionId,
|
|
374
|
+
expressions: [],
|
|
375
|
+
variables: [],
|
|
376
|
+
captureBoundaries: [],
|
|
377
|
+
controlFacts: []
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// src/derivation/analyze-function.ts
|
|
381
|
+
var loc = (node, sourceFile) => {
|
|
382
|
+
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
383
|
+
return { line: position.line + 1, column: position.character + 1 };
|
|
384
|
+
};
|
|
385
|
+
var lookup = (ctx, name) => {
|
|
386
|
+
for (let index = ctx.scopes.length - 1; index >= 0; index--) {
|
|
387
|
+
const binding = ctx.scopes[index]?.get(name);
|
|
388
|
+
if (binding) return binding;
|
|
389
|
+
}
|
|
390
|
+
return void 0;
|
|
391
|
+
};
|
|
392
|
+
var fallback = (ctx, reason) => {
|
|
393
|
+
ctx.fallback ??= reason;
|
|
394
|
+
return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "unknown" };
|
|
395
|
+
};
|
|
396
|
+
var capture = (ctx, node, reason) => {
|
|
397
|
+
const rootId = ctx.nextRootId.value++;
|
|
398
|
+
const position = loc(node, ctx.sourceFile);
|
|
399
|
+
const expressionId = ctx.ir.root(rootId);
|
|
400
|
+
ctx.captureWraps.push({
|
|
401
|
+
rootId,
|
|
402
|
+
reason,
|
|
403
|
+
start: node.getStart(ctx.sourceFile),
|
|
404
|
+
end: node.end,
|
|
405
|
+
line: position.line,
|
|
406
|
+
column: position.column,
|
|
407
|
+
expressionId
|
|
408
|
+
});
|
|
409
|
+
return { expressionId, shape: "unknown", captured: true };
|
|
410
|
+
};
|
|
411
|
+
var binaryOp = (kind) => {
|
|
412
|
+
switch (kind) {
|
|
413
|
+
case ts.SyntaxKind.PlusToken:
|
|
414
|
+
return "add";
|
|
415
|
+
case ts.SyntaxKind.MinusToken:
|
|
416
|
+
return "sub";
|
|
417
|
+
case ts.SyntaxKind.AsteriskToken:
|
|
418
|
+
return "mul";
|
|
419
|
+
case ts.SyntaxKind.SlashToken:
|
|
420
|
+
return "div";
|
|
421
|
+
case ts.SyntaxKind.PercentToken:
|
|
422
|
+
return "mod";
|
|
423
|
+
case ts.SyntaxKind.EqualsEqualsToken:
|
|
424
|
+
return "eq";
|
|
425
|
+
case ts.SyntaxKind.ExclamationEqualsToken:
|
|
426
|
+
return "neq";
|
|
427
|
+
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
|
428
|
+
return "seq";
|
|
429
|
+
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
|
|
430
|
+
return "sneq";
|
|
431
|
+
case ts.SyntaxKind.LessThanToken:
|
|
432
|
+
return "lt";
|
|
433
|
+
case ts.SyntaxKind.LessThanEqualsToken:
|
|
434
|
+
return "lte";
|
|
435
|
+
case ts.SyntaxKind.GreaterThanToken:
|
|
436
|
+
return "gt";
|
|
437
|
+
case ts.SyntaxKind.GreaterThanEqualsToken:
|
|
438
|
+
return "gte";
|
|
439
|
+
default:
|
|
440
|
+
return void 0;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
var isNumericBinaryOp = (op) => op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod";
|
|
444
|
+
var unwrapParens = (node) => {
|
|
445
|
+
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
|
446
|
+
return node;
|
|
447
|
+
};
|
|
448
|
+
var numericLiteralValue = (node) => {
|
|
449
|
+
const value = Number(node.text);
|
|
450
|
+
return { type: "number", value };
|
|
451
|
+
};
|
|
452
|
+
var analyzeExpression = (ctx, node) => {
|
|
453
|
+
if (ctx.fallback) return fallback(ctx, ctx.fallback);
|
|
454
|
+
if (ts.isParenthesizedExpression(node)) return analyzeExpression(ctx, node.expression);
|
|
455
|
+
if (ts.isAsExpression(node) || ts.isNonNullExpression(node)) {
|
|
456
|
+
return analyzeExpression(ctx, node.expression);
|
|
457
|
+
}
|
|
458
|
+
if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
|
|
459
|
+
return analyzeExpression(ctx, node.expression);
|
|
460
|
+
}
|
|
461
|
+
if (ts.isSatisfiesExpression(node)) return analyzeExpression(ctx, node.expression);
|
|
462
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) {
|
|
463
|
+
return { expressionId: ctx.ir.constValue({ type: "null" }), shape: "primitive" };
|
|
464
|
+
}
|
|
465
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
466
|
+
return {
|
|
467
|
+
expressionId: ctx.ir.constValue({ type: "boolean", value: true }),
|
|
468
|
+
shape: "primitive"
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
472
|
+
return {
|
|
473
|
+
expressionId: ctx.ir.constValue({ type: "boolean", value: false }),
|
|
474
|
+
shape: "primitive"
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
if (ts.isNumericLiteral(node)) {
|
|
478
|
+
return { expressionId: ctx.ir.constValue(numericLiteralValue(node)), shape: "primitive" };
|
|
479
|
+
}
|
|
480
|
+
if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand) && node.operator === ts.SyntaxKind.MinusToken) {
|
|
481
|
+
const value = -Number(node.operand.text);
|
|
482
|
+
return {
|
|
483
|
+
expressionId: ctx.ir.constValue(
|
|
484
|
+
Object.is(value, -0) ? { type: "number", value: 0, negativeZero: true } : { type: "number", value }
|
|
485
|
+
),
|
|
486
|
+
shape: "primitive"
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
490
|
+
return {
|
|
491
|
+
expressionId: ctx.ir.constValue({ type: "string", value: node.text }),
|
|
492
|
+
shape: "primitive"
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
if (ts.isBigIntLiteral(node)) {
|
|
496
|
+
return {
|
|
497
|
+
expressionId: ctx.ir.constValue({ type: "bigint", value: node.text.replace(/n$/, "") }),
|
|
498
|
+
shape: "primitive"
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
if (ts.isIdentifier(node)) {
|
|
502
|
+
if (node.text === "undefined") {
|
|
503
|
+
return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "primitive" };
|
|
504
|
+
}
|
|
505
|
+
const binding = lookup(ctx, node.text);
|
|
506
|
+
if (!binding) return capture(ctx, node, "external_state");
|
|
507
|
+
if (binding.mutable) return capture(ctx, node, "mutable_object");
|
|
508
|
+
return {
|
|
509
|
+
expressionId: ctx.ir.alias(binding.expressionId),
|
|
510
|
+
shape: binding.shape
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
if (ts.isTemplateExpression(node)) {
|
|
514
|
+
const parts = [
|
|
515
|
+
{ kind: "lit", value: node.head.text }
|
|
516
|
+
];
|
|
517
|
+
for (const span of node.templateSpans) {
|
|
518
|
+
const nested = analyzeExpression(ctx, span.expression);
|
|
519
|
+
if (nested.shape !== "primitive" && !nested.captured) {
|
|
520
|
+
return capture(ctx, node, "unsupported_expression");
|
|
521
|
+
}
|
|
522
|
+
parts.push({ kind: "expr", expressionId: nested.expressionId });
|
|
523
|
+
parts.push({ kind: "lit", value: span.literal.text });
|
|
524
|
+
}
|
|
525
|
+
return { expressionId: ctx.ir.template(parts), shape: "primitive" };
|
|
526
|
+
}
|
|
527
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
528
|
+
if (node.elements.some((element) => ts.isSpreadElement(element))) {
|
|
529
|
+
return capture(ctx, node, "unsupported_expression");
|
|
530
|
+
}
|
|
531
|
+
const elements = node.elements.map((element) => {
|
|
532
|
+
if (ts.isOmittedExpression(element)) return { kind: "hole" };
|
|
533
|
+
return analyzeExpression(ctx, element).expressionId;
|
|
534
|
+
});
|
|
535
|
+
return { expressionId: ctx.ir.array(elements), shape: "array" };
|
|
536
|
+
}
|
|
537
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
538
|
+
const properties = [];
|
|
539
|
+
for (const property of node.properties) {
|
|
540
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
541
|
+
const nested = analyzeExpression(ctx, property.name);
|
|
542
|
+
if (nested.captured) {
|
|
543
|
+
const wrap = ctx.captureWraps.find(
|
|
544
|
+
(candidate) => candidate.expressionId === nested.expressionId
|
|
545
|
+
);
|
|
546
|
+
if (wrap) wrap.shorthandPropertyName = property.name.text;
|
|
547
|
+
}
|
|
548
|
+
properties.push({ key: property.name.text, value: nested.expressionId });
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
552
|
+
return capture(ctx, node, "unsupported_expression");
|
|
553
|
+
}
|
|
554
|
+
if (ts.isPrivateIdentifier(property.name) || ts.isComputedPropertyName(property.name)) {
|
|
555
|
+
return capture(ctx, node, "unsupported_expression");
|
|
556
|
+
}
|
|
557
|
+
const key = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : void 0;
|
|
558
|
+
if (key === void 0) return capture(ctx, node, "unsupported_expression");
|
|
559
|
+
properties.push({ key, value: analyzeExpression(ctx, property.initializer).expressionId });
|
|
560
|
+
}
|
|
561
|
+
return { expressionId: ctx.ir.object(properties), shape: "object" };
|
|
562
|
+
}
|
|
563
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
564
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
565
|
+
const object = analyzeExpression(ctx, node.expression);
|
|
566
|
+
if (object.shape === "object" && ts.isIdentifier(node.name)) {
|
|
567
|
+
return { expressionId: ctx.ir.prop(object.expressionId, node.name.text), shape: "unknown" };
|
|
568
|
+
}
|
|
569
|
+
return capture(ctx, node, "unknown_property");
|
|
570
|
+
}
|
|
571
|
+
if (ts.isElementAccessExpression(node)) {
|
|
572
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
573
|
+
const array = analyzeExpression(ctx, node.expression);
|
|
574
|
+
if (array.shape === "array" && ts.isNumericLiteral(node.argumentExpression)) {
|
|
575
|
+
return {
|
|
576
|
+
expressionId: ctx.ir.index(array.expressionId, Number(node.argumentExpression.text)),
|
|
577
|
+
shape: "unknown"
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
return capture(ctx, node, "unknown_property");
|
|
581
|
+
}
|
|
582
|
+
if (ts.isCallExpression(node)) {
|
|
583
|
+
if (node.questionDotToken || node.typeArguments?.length) {
|
|
584
|
+
return capture(ctx, node, "unsupported_expression");
|
|
585
|
+
}
|
|
586
|
+
if (!ts.isIdentifier(node.expression)) return capture(ctx, node, "dynamic_callee");
|
|
587
|
+
const calleeName = node.expression.text;
|
|
588
|
+
if (lookup(ctx, calleeName)) return capture(ctx, node, "opaque_call");
|
|
589
|
+
const provenId = ctx.moduleCallees.get(calleeName);
|
|
590
|
+
ctx.callEdges.push({
|
|
591
|
+
callerFunctionId: ctx.functionId,
|
|
592
|
+
calleeName,
|
|
593
|
+
...provenId ? { calleeFunctionId: provenId } : {}
|
|
594
|
+
});
|
|
595
|
+
if (!provenId) return capture(ctx, node, "opaque_call");
|
|
596
|
+
const args2 = node.arguments.map((argument) => {
|
|
597
|
+
if (ts.isSpreadElement(argument)) return void 0;
|
|
598
|
+
return analyzeExpression(ctx, argument).expressionId;
|
|
599
|
+
});
|
|
600
|
+
if (args2.some((argument) => argument === void 0))
|
|
601
|
+
return capture(ctx, node, "unsupported_expression");
|
|
602
|
+
return {
|
|
603
|
+
expressionId: ctx.ir.pureCall(
|
|
604
|
+
provenId,
|
|
605
|
+
args2.filter((id) => id !== void 0)
|
|
606
|
+
),
|
|
607
|
+
shape: "unknown"
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
if (ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node)) {
|
|
611
|
+
return capture(
|
|
612
|
+
ctx,
|
|
613
|
+
node,
|
|
614
|
+
node.kind === ts.SyntaxKind.AwaitExpression ? "nondeterministic" : "unsupported_expression"
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
if (ts.isConditionalExpression(node)) {
|
|
618
|
+
const test = analyzeExpression(ctx, node.condition);
|
|
619
|
+
const consequent = analyzeExpression(ctx, node.whenTrue);
|
|
620
|
+
const alternate = analyzeExpression(ctx, node.whenFalse);
|
|
621
|
+
return {
|
|
622
|
+
expressionId: ctx.ir.cond(test.expressionId, consequent.expressionId, alternate.expressionId),
|
|
623
|
+
shape: consequent.shape === alternate.shape ? consequent.shape : "unknown"
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (ts.isBinaryExpression(node)) {
|
|
627
|
+
if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
628
|
+
ctx.fallback ??= "mutable_state";
|
|
629
|
+
return capture(ctx, node, "mutable_object");
|
|
630
|
+
}
|
|
631
|
+
if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
632
|
+
const left2 = analyzeExpression(ctx, node.left);
|
|
633
|
+
const right2 = analyzeExpression(ctx, node.right);
|
|
634
|
+
const op2 = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "and" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "or" : "nullish";
|
|
635
|
+
return {
|
|
636
|
+
expressionId: ctx.ir.logical(op2, left2.expressionId, right2.expressionId),
|
|
637
|
+
shape: "unknown"
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
const op = binaryOp(node.operatorToken.kind);
|
|
641
|
+
if (!op) return capture(ctx, node, "unsupported_expression");
|
|
642
|
+
if (!ts.isExpression(node.left)) return capture(ctx, node, "unsupported_expression");
|
|
643
|
+
const left = analyzeExpression(ctx, node.left);
|
|
644
|
+
const right = analyzeExpression(ctx, node.right);
|
|
645
|
+
const numeric = isNumericBinaryOp(op);
|
|
646
|
+
const expressionId = ctx.ir.binary(
|
|
647
|
+
op,
|
|
648
|
+
left.expressionId,
|
|
649
|
+
right.expressionId,
|
|
650
|
+
numeric ? "numeric" : "primitive"
|
|
651
|
+
);
|
|
652
|
+
if (numeric && (left.captured || right.captured || left.shape !== "primitive" || right.shape !== "primitive")) {
|
|
653
|
+
ctx.hasCoercionGuard = true;
|
|
654
|
+
const fallbackRootId = ctx.nextRootId.value++;
|
|
655
|
+
ctx.guardWraps.push({
|
|
656
|
+
fallbackRootId,
|
|
657
|
+
start: node.getStart(ctx.sourceFile),
|
|
658
|
+
end: node.end,
|
|
659
|
+
op
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
return { expressionId, shape: "primitive" };
|
|
663
|
+
}
|
|
664
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
665
|
+
const operand = analyzeExpression(ctx, node.operand);
|
|
666
|
+
if (node.operator === ts.SyntaxKind.ExclamationToken) {
|
|
667
|
+
return { expressionId: ctx.ir.unary("not", operand.expressionId), shape: "primitive" };
|
|
668
|
+
}
|
|
669
|
+
if (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) {
|
|
670
|
+
if (operand.captured || operand.shape !== "primitive") ctx.hasCoercionGuard = true;
|
|
671
|
+
return {
|
|
672
|
+
expressionId: ctx.ir.unary(
|
|
673
|
+
node.operator === ts.SyntaxKind.MinusToken ? "minus" : "plus",
|
|
674
|
+
operand.expressionId
|
|
675
|
+
),
|
|
676
|
+
shape: "primitive"
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
return capture(ctx, node, "unsupported_expression");
|
|
680
|
+
}
|
|
681
|
+
if (ts.isTypeOfExpression(node)) {
|
|
682
|
+
const operand = unwrapParens(node.expression);
|
|
683
|
+
if (ts.isIdentifier(operand) && operand.text !== "undefined" && !lookup(ctx, operand.text)) {
|
|
684
|
+
return capture(ctx, node, "external_state");
|
|
685
|
+
}
|
|
686
|
+
const inner = analyzeExpression(ctx, node.expression);
|
|
687
|
+
return { expressionId: ctx.ir.unary("typeof", inner.expressionId), shape: "primitive" };
|
|
688
|
+
}
|
|
689
|
+
return capture(ctx, node, "unsupported_expression");
|
|
690
|
+
};
|
|
691
|
+
var bindIdentifier = (ctx, name, result2, declaration) => {
|
|
692
|
+
const current = ctx.scopes[ctx.scopes.length - 1];
|
|
693
|
+
current?.set(name.text, {
|
|
694
|
+
expressionId: result2.expressionId,
|
|
695
|
+
shape: result2.shape,
|
|
696
|
+
mutable: false
|
|
697
|
+
});
|
|
698
|
+
const position = loc(name, ctx.sourceFile);
|
|
699
|
+
const available = loc(declaration, ctx.sourceFile);
|
|
700
|
+
ctx.variables.push({
|
|
701
|
+
id: ctx.nextVariableId.value++,
|
|
702
|
+
name: name.text,
|
|
703
|
+
functionId: ctx.functionId,
|
|
704
|
+
scopeId: `${ctx.functionId}:${ctx.scopes.length}`,
|
|
705
|
+
line: position.line,
|
|
706
|
+
column: position.column,
|
|
707
|
+
assignmentId: ctx.nextAssignmentId.value++,
|
|
708
|
+
expressionId: result2.expressionId,
|
|
709
|
+
availableAtLine: available.line,
|
|
710
|
+
availableAtColumn: available.column
|
|
711
|
+
});
|
|
712
|
+
};
|
|
713
|
+
var analyzeStatement = (ctx, node) => {
|
|
714
|
+
if (ctx.fallback) return;
|
|
715
|
+
if (ts.isEmptyStatement(node) || ts.isDebuggerStatement(node)) return;
|
|
716
|
+
if (ts.isBlock(node)) {
|
|
717
|
+
ctx.scopes.push(/* @__PURE__ */ new Map());
|
|
718
|
+
for (const statement of node.statements) analyzeStatement(ctx, statement);
|
|
719
|
+
ctx.scopes.pop();
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (ts.isReturnStatement(node)) {
|
|
723
|
+
if (!node.expression) {
|
|
724
|
+
ctx.returnExpressionId = ctx.ir.constValue({ type: "undefined" });
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.expression).expressionId;
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (ts.isVariableStatement(node)) {
|
|
731
|
+
if (node.declarationList.flags & ts.NodeFlags.Using) {
|
|
732
|
+
fallback(ctx, "side_effect");
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
for (const declaration of node.declarationList.declarations) {
|
|
736
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
737
|
+
fallback(ctx, "unsupported_expression");
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const result2 = analyzeExpression(ctx, declaration.initializer);
|
|
741
|
+
bindIdentifier(ctx, declaration.name, result2, declaration);
|
|
742
|
+
}
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (ts.isExpressionStatement(node)) {
|
|
746
|
+
analyzeExpression(ctx, node.expression);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (ts.isThrowStatement(node)) {
|
|
750
|
+
ctx.hasThrow = true;
|
|
751
|
+
analyzeExpression(ctx, node.expression);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || ts.isTryStatement(node)) {
|
|
755
|
+
fallback(ctx, "unsupported_expression");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
|
|
759
|
+
fallback(ctx, "has_loop");
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
fallback(ctx, "unsupported_expression");
|
|
763
|
+
};
|
|
764
|
+
var bindParams = (ctx, node) => {
|
|
765
|
+
let index = 0;
|
|
766
|
+
for (const parameter of node.parameters) {
|
|
767
|
+
if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
768
|
+
if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken) {
|
|
769
|
+
fallback(ctx, "unsupported_expression");
|
|
770
|
+
index++;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const expressionId = ctx.ir.param(index);
|
|
774
|
+
ctx.identifierParams.push(parameter.name.text);
|
|
775
|
+
bindIdentifier(ctx, parameter.name, { expressionId, shape: "unknown" }, parameter);
|
|
776
|
+
index++;
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
var analyzeOne = (node, functionId, name, sourceFile, moduleCallees) => {
|
|
780
|
+
const ctx = {
|
|
781
|
+
sourceFile,
|
|
782
|
+
functionId,
|
|
783
|
+
name,
|
|
784
|
+
ir: new DerivationIrBuilder(),
|
|
785
|
+
variables: [],
|
|
786
|
+
captureWraps: [],
|
|
787
|
+
guardWraps: [],
|
|
788
|
+
callEdges: [],
|
|
789
|
+
scopes: [/* @__PURE__ */ new Map()],
|
|
790
|
+
nextRootId: { value: 0 },
|
|
791
|
+
nextVariableId: { value: 0 },
|
|
792
|
+
nextAssignmentId: { value: 0 },
|
|
793
|
+
hasThrow: false,
|
|
794
|
+
hasCoercionGuard: false,
|
|
795
|
+
moduleCallees,
|
|
796
|
+
identifierParams: []
|
|
797
|
+
};
|
|
798
|
+
if (node.asteriskToken || node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
|
|
799
|
+
ctx.fallback = "async";
|
|
800
|
+
} else {
|
|
801
|
+
bindParams(ctx, node);
|
|
802
|
+
if (ts.isBlock(node.body)) {
|
|
803
|
+
for (const statement of node.body.statements) analyzeStatement(ctx, statement);
|
|
804
|
+
} else {
|
|
805
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.body).expressionId;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const plan = {
|
|
809
|
+
...emptyPlan(functionId),
|
|
810
|
+
expressions: ctx.ir.expressions,
|
|
811
|
+
variables: ctx.variables,
|
|
812
|
+
captureBoundaries: ctx.captureWraps.map((wrap) => ({
|
|
813
|
+
id: wrap.rootId,
|
|
814
|
+
reason: wrap.reason,
|
|
815
|
+
expressionId: wrap.expressionId,
|
|
816
|
+
line: wrap.line,
|
|
817
|
+
column: wrap.column
|
|
818
|
+
})),
|
|
819
|
+
controlFacts: [],
|
|
820
|
+
...ctx.returnExpressionId === void 0 ? {} : { returnExpressionId: ctx.returnExpressionId },
|
|
821
|
+
...ctx.fallback ? { fallbackReason: ctx.fallback } : {}
|
|
822
|
+
};
|
|
823
|
+
return {
|
|
824
|
+
functionId,
|
|
825
|
+
name,
|
|
826
|
+
node,
|
|
827
|
+
plan,
|
|
828
|
+
captureWraps: ctx.fallback ? [] : ctx.captureWraps,
|
|
829
|
+
guardWraps: ctx.fallback ? [] : ctx.guardWraps,
|
|
830
|
+
callEdges: ctx.callEdges,
|
|
831
|
+
hasThrow: ctx.hasThrow,
|
|
832
|
+
hasCoercionGuard: ctx.hasCoercionGuard
|
|
833
|
+
};
|
|
834
|
+
};
|
|
835
|
+
var analyzeModuleFunctions = (sourceFile, targets) => {
|
|
836
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
837
|
+
for (const target of targets) {
|
|
838
|
+
nameCounts.set(target.name, (nameCounts.get(target.name) ?? 0) + 1);
|
|
839
|
+
}
|
|
840
|
+
const firstPass = targets.map(
|
|
841
|
+
(target) => analyzeOne(target.node, target.functionId, target.name, sourceFile, /* @__PURE__ */ new Map())
|
|
842
|
+
);
|
|
843
|
+
const graph = buildDependencyGraph(
|
|
844
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
845
|
+
firstPass.flatMap((fn) => fn.callEdges)
|
|
846
|
+
);
|
|
847
|
+
const order = topologicalFunctionOrder(
|
|
848
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
849
|
+
graph
|
|
850
|
+
);
|
|
851
|
+
const byId = new Map(targets.map((target) => [target.functionId, target]));
|
|
852
|
+
const moduleCallees = /* @__PURE__ */ new Map();
|
|
853
|
+
const analyzed = [];
|
|
854
|
+
for (const functionId of order) {
|
|
855
|
+
const target = byId.get(functionId);
|
|
856
|
+
if (!target) continue;
|
|
857
|
+
const result2 = analyzeOne(
|
|
858
|
+
target.node,
|
|
859
|
+
target.functionId,
|
|
860
|
+
target.name,
|
|
861
|
+
sourceFile,
|
|
862
|
+
moduleCallees
|
|
863
|
+
);
|
|
864
|
+
if (nameCounts.get(result2.name) === 1) {
|
|
865
|
+
moduleCallees.set(result2.name, result2.functionId);
|
|
866
|
+
}
|
|
867
|
+
analyzed.push(result2);
|
|
868
|
+
}
|
|
869
|
+
const remaining = targets.filter(
|
|
870
|
+
(target) => !analyzed.some((fn) => fn.functionId === target.functionId)
|
|
871
|
+
);
|
|
872
|
+
for (const target of remaining) {
|
|
873
|
+
analyzed.push(
|
|
874
|
+
analyzeOne(target.node, target.functionId, target.name, sourceFile, moduleCallees)
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
return analyzed;
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
// src/derivation/derivation-proof.ts
|
|
881
|
+
var functionIsExact = (plan) => plan.fallbackReason === void 0 && plan.captureBoundaries.length === 0 && plan.controlFacts.length === 0;
|
|
882
|
+
|
|
883
|
+
// src/derivation/emit-capture-plan.ts
|
|
884
|
+
var captureWrapEdits = (wraps, capName) => {
|
|
885
|
+
const edits = [];
|
|
886
|
+
let order = 0;
|
|
887
|
+
for (const wrap of wraps) {
|
|
888
|
+
edits.push({
|
|
889
|
+
position: wrap.start,
|
|
890
|
+
text: `${wrap.shorthandPropertyName ? `${wrap.shorthandPropertyName}:` : ""}${capName}(${wrap.rootId},`,
|
|
891
|
+
order: order++
|
|
892
|
+
});
|
|
893
|
+
edits.push({
|
|
894
|
+
position: wrap.end,
|
|
895
|
+
text: ")",
|
|
896
|
+
order: order++
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
return edits;
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
// src/execution-recorder/automatic-runtime.ts
|
|
903
|
+
var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
|
|
904
|
+
var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
|
|
905
|
+
|
|
906
|
+
// src/auto-instrumentation/collect-instrumentation-delta.ts
|
|
907
|
+
import ts2 from "typescript";
|
|
908
|
+
var paramBindingFromName = (name, sourceFile) => {
|
|
909
|
+
if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
|
|
910
|
+
if (ts2.isArrayBindingPattern(name)) {
|
|
911
|
+
const elements = name.elements.map(
|
|
912
|
+
(element) => {
|
|
913
|
+
if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
|
|
914
|
+
const binding = paramBindingFromName(element.name, sourceFile);
|
|
915
|
+
if (element.dotDotDotToken) return { kind: "rest", binding };
|
|
916
|
+
return binding;
|
|
917
|
+
}
|
|
918
|
+
);
|
|
919
|
+
return { kind: "array", elements };
|
|
920
|
+
}
|
|
921
|
+
const properties = name.elements.map((element) => {
|
|
922
|
+
const binding = paramBindingFromName(element.name, sourceFile);
|
|
923
|
+
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts2.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
|
|
924
|
+
if (element.dotDotDotToken) {
|
|
925
|
+
const rest = { kind: "rest", binding };
|
|
926
|
+
return { key, binding: rest };
|
|
927
|
+
}
|
|
928
|
+
return { key, binding };
|
|
929
|
+
});
|
|
930
|
+
return { kind: "object", properties };
|
|
931
|
+
};
|
|
932
|
+
var collectFunctionParams = (node, sourceFile) => {
|
|
933
|
+
const params = [];
|
|
934
|
+
for (const parameter of node.parameters) {
|
|
935
|
+
if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
936
|
+
const binding = paramBindingFromName(parameter.name, sourceFile);
|
|
937
|
+
params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
|
|
938
|
+
}
|
|
939
|
+
return { params, paramNames: identifierNames(params) };
|
|
940
|
+
};
|
|
941
|
+
var identifierNames = (bindings) => {
|
|
942
|
+
const names = /* @__PURE__ */ new Set();
|
|
943
|
+
const walk = (node) => {
|
|
944
|
+
if (node.kind === "identifier") {
|
|
945
|
+
names.add(node.name);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (node.kind === "rest") {
|
|
949
|
+
walk(node.binding);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
if (node.kind === "object") {
|
|
953
|
+
for (const property of node.properties) walk(property.binding);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
if (node.kind === "array") {
|
|
957
|
+
for (const element of node.elements) {
|
|
958
|
+
if (element.kind !== "omitted") walk(element);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
for (const item of bindings) walk(item);
|
|
963
|
+
return names;
|
|
964
|
+
};
|
|
965
|
+
var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
|
|
966
|
+
const generated = sourceFile.getLineAndCharacterOfPosition(position);
|
|
967
|
+
const raw = {
|
|
968
|
+
filePath: absolutePath,
|
|
969
|
+
line: generated.line + 1,
|
|
970
|
+
column: generated.character + 1
|
|
971
|
+
};
|
|
972
|
+
return mapLocation ? mapLocation(raw) : raw;
|
|
973
|
+
};
|
|
974
|
+
var unwrapExpression = (expression) => {
|
|
975
|
+
let current = expression;
|
|
976
|
+
while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
|
|
977
|
+
current = current.expression;
|
|
978
|
+
}
|
|
979
|
+
if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
|
|
980
|
+
return current;
|
|
981
|
+
};
|
|
982
|
+
var calleeFromExpression = (expression, sourceFile) => {
|
|
983
|
+
const current = unwrapExpression(expression);
|
|
984
|
+
if (ts2.isIdentifier(current)) {
|
|
985
|
+
return { calleeName: current.text, calleeText: current.text };
|
|
986
|
+
}
|
|
987
|
+
if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
|
|
988
|
+
return {
|
|
989
|
+
calleeName: current.name.text,
|
|
990
|
+
calleeText: current.getText(sourceFile).replace(/\s+/g, "")
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
return null;
|
|
994
|
+
};
|
|
995
|
+
var resultBindingForCall = (call) => {
|
|
996
|
+
let node = call.parent;
|
|
997
|
+
if (ts2.isAwaitExpression(node)) node = node.parent;
|
|
998
|
+
if (!ts2.isVariableDeclaration(node)) return null;
|
|
999
|
+
if (!ts2.isIdentifier(node.name)) return null;
|
|
1000
|
+
const initializer = node.initializer;
|
|
1001
|
+
if (!initializer) return null;
|
|
1002
|
+
const inner = unwrapExpression(initializer);
|
|
1003
|
+
if (inner !== call) return null;
|
|
1004
|
+
return node.name.text;
|
|
1005
|
+
};
|
|
1006
|
+
var argIdentifiersForCall = (call) => {
|
|
1007
|
+
const identifiers = [];
|
|
1008
|
+
call.arguments?.forEach((argument, index) => {
|
|
1009
|
+
if (ts2.isIdentifier(argument)) {
|
|
1010
|
+
identifiers.push({ index, kind: "identifier", name: argument.text });
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (!ts2.isObjectLiteralExpression(argument)) return;
|
|
1014
|
+
const properties = [];
|
|
1015
|
+
for (const property of argument.properties) {
|
|
1016
|
+
if (ts2.isShorthandPropertyAssignment(property)) {
|
|
1017
|
+
properties.push({ key: property.name.text, name: property.name.text });
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (ts2.isPropertyAssignment(property) && ts2.isIdentifier(property.name) && ts2.isIdentifier(property.initializer)) {
|
|
1021
|
+
properties.push({ key: property.name.text, name: property.initializer.text });
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (properties.length > 0) {
|
|
1025
|
+
identifiers.push({ index, kind: "object", properties });
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
return identifiers;
|
|
1029
|
+
};
|
|
1030
|
+
var collectCallSite = (call, caller, context) => {
|
|
1031
|
+
const callee = calleeFromExpression(call.expression, context.sourceFile);
|
|
1032
|
+
if (!callee) return null;
|
|
1033
|
+
const start = mappedLocation(
|
|
1034
|
+
context.sourceFile,
|
|
1035
|
+
call.getStart(context.sourceFile),
|
|
1036
|
+
context.absolutePath,
|
|
1037
|
+
context.mapLocation
|
|
1038
|
+
);
|
|
1039
|
+
const end = mappedLocation(
|
|
1040
|
+
context.sourceFile,
|
|
1041
|
+
call.end,
|
|
1042
|
+
context.absolutePath,
|
|
1043
|
+
context.mapLocation
|
|
1044
|
+
);
|
|
1045
|
+
if (!start || !end) return null;
|
|
1046
|
+
return {
|
|
1047
|
+
callerFunctionId: caller.functionId,
|
|
1048
|
+
calleeName: callee.calleeName,
|
|
1049
|
+
calleeText: callee.calleeText,
|
|
1050
|
+
filePath: start.filePath,
|
|
1051
|
+
line: start.line,
|
|
1052
|
+
column: start.column,
|
|
1053
|
+
endLine: end.line,
|
|
1054
|
+
endColumn: end.column,
|
|
1055
|
+
resultBinding: resultBindingForCall(call),
|
|
1056
|
+
argIdentifiers: argIdentifiersForCall(call)
|
|
1057
|
+
};
|
|
1058
|
+
};
|
|
1059
|
+
var collectParamDestructure = (declaration, caller, context) => {
|
|
1060
|
+
if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
|
|
1061
|
+
if (!ts2.isObjectBindingPattern(declaration.name) && !ts2.isArrayBindingPattern(declaration.name)) {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
const sourceParam = declaration.initializer.text;
|
|
1065
|
+
if (!caller.paramNames.has(sourceParam)) return null;
|
|
1066
|
+
const start = mappedLocation(
|
|
1067
|
+
context.sourceFile,
|
|
1068
|
+
declaration.getStart(context.sourceFile),
|
|
1069
|
+
context.absolutePath,
|
|
1070
|
+
context.mapLocation
|
|
1071
|
+
);
|
|
1072
|
+
if (!start) return null;
|
|
1073
|
+
return {
|
|
1074
|
+
callerFunctionId: caller.functionId,
|
|
1075
|
+
sourceParam,
|
|
1076
|
+
filePath: start.filePath,
|
|
1077
|
+
line: start.line,
|
|
1078
|
+
column: start.column,
|
|
1079
|
+
binding: paramBindingFromName(declaration.name, context.sourceFile)
|
|
1080
|
+
};
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
// src/auto-instrumentation/transform-source.ts
|
|
1084
|
+
var scriptKindFor = (filePath) => {
|
|
1085
|
+
if (/\.tsx$/i.test(filePath)) return ts3.ScriptKind.TSX;
|
|
1086
|
+
if (/\.jsx$/i.test(filePath)) return ts3.ScriptKind.JSX;
|
|
1087
|
+
if (/\.[cm]?ts$/i.test(filePath)) return ts3.ScriptKind.TS;
|
|
1088
|
+
return ts3.ScriptKind.JS;
|
|
1089
|
+
};
|
|
1090
|
+
var functionName = (node, sourceFile) => {
|
|
1091
|
+
if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
|
|
1092
|
+
const parent = node.parent;
|
|
1093
|
+
if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) return parent.name.text;
|
|
1094
|
+
if (ts3.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
|
|
1095
|
+
if (ts3.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
|
|
1096
|
+
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
1097
|
+
return `anonymous@${location.line + 1}:${location.character + 1}`;
|
|
1098
|
+
};
|
|
1099
|
+
var functionLocationPosition = (node, sourceFile) => {
|
|
1100
|
+
if (node.name) return node.name.getStart(sourceFile);
|
|
1101
|
+
const parent = node.parent;
|
|
1102
|
+
if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
|
|
1103
|
+
return parent.name.getStart(sourceFile);
|
|
1104
|
+
}
|
|
1105
|
+
return node.getStart(sourceFile);
|
|
1106
|
+
};
|
|
1107
|
+
var bindingValue = (name, sourceFile) => {
|
|
1108
|
+
if (ts3.isIdentifier(name)) return name.text;
|
|
1109
|
+
if (ts3.isArrayBindingPattern(name)) {
|
|
1110
|
+
const values = name.elements.map((element) => {
|
|
1111
|
+
if (ts3.isOmittedExpression(element)) return "";
|
|
1112
|
+
const value = bindingValue(element.name, sourceFile);
|
|
1113
|
+
return element.dotDotDotToken ? `...${value}` : value;
|
|
1114
|
+
});
|
|
1115
|
+
return `[${values.join(",")}]`;
|
|
1116
|
+
}
|
|
1117
|
+
const properties = name.elements.map((element) => {
|
|
1118
|
+
const value = bindingValue(element.name, sourceFile);
|
|
1119
|
+
if (element.dotDotDotToken) return `...${value}`;
|
|
1120
|
+
if (element.propertyName) return `${element.propertyName.getText(sourceFile)}:${value}`;
|
|
1121
|
+
return value;
|
|
1122
|
+
});
|
|
1123
|
+
return `{${properties.join(",")}}`;
|
|
1124
|
+
};
|
|
1125
|
+
var arrowArguments = (node) => {
|
|
1126
|
+
const values = node.parameters.flatMap((parameter) => {
|
|
1127
|
+
if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
|
|
1128
|
+
const value = bindingValue(parameter.name, node.getSourceFile());
|
|
1129
|
+
return [parameter.dotDotDotToken ? `...${value}` : value];
|
|
1130
|
+
});
|
|
1131
|
+
return `[${values.join(",")}]`;
|
|
1132
|
+
};
|
|
1133
|
+
var isSupportedFunction = (node) => {
|
|
1134
|
+
if (!ts3.isFunctionDeclaration(node) && !ts3.isFunctionExpression(node) && !ts3.isArrowFunction(node) && !ts3.isMethodDeclaration(node) && !ts3.isGetAccessorDeclaration(node) && !ts3.isSetAccessorDeclaration(node)) {
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
|
|
1138
|
+
return true;
|
|
1139
|
+
};
|
|
1140
|
+
var uniqueName = (source, base) => {
|
|
1141
|
+
let name = base;
|
|
1142
|
+
while (source.includes(name)) name += "_";
|
|
1143
|
+
return name;
|
|
1144
|
+
};
|
|
1145
|
+
var isDirectivePrologue = (statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression);
|
|
1146
|
+
var insertPositionAfterPrologue = (source, sourceFile) => {
|
|
1147
|
+
const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
|
|
1148
|
+
let end = shebangEnd;
|
|
1149
|
+
for (const statement of sourceFile.statements) {
|
|
1150
|
+
if (!isDirectivePrologue(statement)) break;
|
|
1151
|
+
end = statement.end;
|
|
1152
|
+
}
|
|
1153
|
+
return end;
|
|
1154
|
+
};
|
|
1155
|
+
var preambleInsert = (source, position, preamble) => {
|
|
1156
|
+
if (position <= 0) return preamble;
|
|
1157
|
+
const previous = source[position - 1];
|
|
1158
|
+
return previous === ";" || previous === "\n" || previous === "\r" ? preamble : `;${preamble}`;
|
|
1159
|
+
};
|
|
1160
|
+
var modulePreamble = (runName, capName, plansName, plansJson) => `const ${plansName}=${plansJson};const ${runName}=((g,k)=>{const r=g[k]??(g[k]={runtimes:[],modules:[],captureRoot(i,v){return v},register(p){const d={run:(i,a,c)=>c()};const m={plans:p,cell:d};this.modules.push(m);const n=this.runtimes.at(-1);if(n)n.bind(m);return(i,a,c,l)=>d.run(i,a,c,l)}});return r.register(${plansName})})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));const ${capName}=((g,k)=>{const r=g[k];return(i,v)=>typeof r?.captureRoot==='function'?r.captureRoot(i,v):v})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));`;
|
|
1161
|
+
var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
1162
|
+
const start = mappedLocation(
|
|
1163
|
+
sourceFile,
|
|
1164
|
+
functionLocationPosition(node, sourceFile),
|
|
1165
|
+
absolutePath,
|
|
1166
|
+
mapLocation
|
|
1167
|
+
);
|
|
1168
|
+
if (!start) return void 0;
|
|
1169
|
+
const end = mappedLocation(sourceFile, node.end, absolutePath, mapLocation) ?? start;
|
|
1170
|
+
const name = functionName(node, sourceFile).replace(/@\d+:\d+$/, "");
|
|
1171
|
+
const { params, paramNames } = collectFunctionParams(node, sourceFile);
|
|
1172
|
+
const plan = encodeFunctionPlan({
|
|
1173
|
+
filePath: start.filePath,
|
|
1174
|
+
name,
|
|
1175
|
+
line: start.line,
|
|
1176
|
+
column: start.column
|
|
1177
|
+
});
|
|
1178
|
+
return {
|
|
1179
|
+
plan,
|
|
1180
|
+
functionId: plan[0],
|
|
1181
|
+
filePath: start.filePath,
|
|
1182
|
+
name,
|
|
1183
|
+
startLine: start.line,
|
|
1184
|
+
startColumn: start.column,
|
|
1185
|
+
endLine: end.line,
|
|
1186
|
+
endColumn: end.column,
|
|
1187
|
+
params,
|
|
1188
|
+
paramNames
|
|
1189
|
+
};
|
|
1190
|
+
};
|
|
1191
|
+
var canOmitCallValues = (analysis, analyzed) => {
|
|
1192
|
+
if (!functionIsExact(analysis.plan) || analysis.hasThrow || analysis.hasCoercionGuard) {
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
1195
|
+
const callers = analyzed.filter(
|
|
1196
|
+
(caller) => caller.callEdges.some((edge) => edge.calleeFunctionId === analysis.functionId)
|
|
1197
|
+
);
|
|
1198
|
+
if (callers.length === 0) return false;
|
|
1199
|
+
return callers.every((caller) => caller.plan.fallbackReason === void 0 && !caller.hasThrow);
|
|
1200
|
+
};
|
|
1201
|
+
var transformSource = (source, options) => {
|
|
1202
|
+
if (source.includes("@rasputin-ignore-file")) return void 0;
|
|
1203
|
+
const sourceFile = ts3.createSourceFile(
|
|
1204
|
+
options.filePath,
|
|
1205
|
+
source,
|
|
1206
|
+
ts3.ScriptTarget.Latest,
|
|
1207
|
+
true,
|
|
1208
|
+
scriptKindFor(options.filePath)
|
|
1209
|
+
);
|
|
1210
|
+
const runName = uniqueName(source, "__rasputinRun__");
|
|
1211
|
+
const capName = uniqueName(source, "__rasputinCap__");
|
|
1212
|
+
const plansName = uniqueName(source, "__rasputinPlans__");
|
|
1213
|
+
const absolutePath = resolve4(options.filePath);
|
|
1214
|
+
const derivationMode = options.derivationMode ?? "analysis";
|
|
1215
|
+
const edits = [];
|
|
1216
|
+
let order = 0;
|
|
1217
|
+
const delta = {
|
|
1218
|
+
functions: [],
|
|
1219
|
+
callSites: [],
|
|
1220
|
+
destructures: [],
|
|
1221
|
+
derivationPlans: []
|
|
1222
|
+
};
|
|
1223
|
+
const collectContext = {
|
|
1224
|
+
sourceFile,
|
|
1225
|
+
mapLocation: options.mapLocation,
|
|
1226
|
+
absolutePath
|
|
1227
|
+
};
|
|
1228
|
+
const pending = [];
|
|
1229
|
+
const visit = (node, caller) => {
|
|
1230
|
+
if (isSupportedFunction(node)) {
|
|
1231
|
+
const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
|
|
1232
|
+
if (!collected) {
|
|
1233
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
pending.push({ node, collected });
|
|
1237
|
+
delta.functions.push(collected);
|
|
1238
|
+
ts3.forEachChild(node, (child) => visit(child, collected));
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
|
|
1242
|
+
const site = collectCallSite(node, caller, collectContext);
|
|
1243
|
+
if (site) delta.callSites.push(site);
|
|
1244
|
+
}
|
|
1245
|
+
if (caller && ts3.isVariableDeclaration(node)) {
|
|
1246
|
+
const destructure = collectParamDestructure(node, caller, collectContext);
|
|
1247
|
+
if (destructure) delta.destructures.push(destructure);
|
|
1248
|
+
}
|
|
1249
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
1250
|
+
};
|
|
1251
|
+
visit(sourceFile);
|
|
1252
|
+
if (pending.length === 0) return void 0;
|
|
1253
|
+
const analyzed = analyzeModuleFunctions(
|
|
1254
|
+
sourceFile,
|
|
1255
|
+
pending.map((entry) => ({
|
|
1256
|
+
functionId: entry.collected.functionId,
|
|
1257
|
+
name: entry.collected.name,
|
|
1258
|
+
node: entry.node
|
|
1259
|
+
}))
|
|
1260
|
+
);
|
|
1261
|
+
delta.derivationPlans = analyzed.map((fn) => fn.plan);
|
|
1262
|
+
const analyzedById = new Map(analyzed.map((fn) => [fn.functionId, fn]));
|
|
1263
|
+
for (const [planIndex, entry] of pending.entries()) {
|
|
1264
|
+
const analysis = analyzedById.get(entry.collected.functionId);
|
|
1265
|
+
const omitValues = derivationMode === "selective" && analysis && canOmitCallValues(analysis, analyzed);
|
|
1266
|
+
const valueFlag = omitValues ? ",1" : "";
|
|
1267
|
+
const args2 = ts3.isArrowFunction(entry.node) ? arrowArguments(entry.node) : "arguments";
|
|
1268
|
+
const asyncCallback = entry.node.modifiers?.some(
|
|
1269
|
+
(modifier) => modifier.kind === ts3.SyntaxKind.AsyncKeyword
|
|
1270
|
+
) ? "async " : "";
|
|
1271
|
+
if (ts3.isBlock(entry.node.body)) {
|
|
1272
|
+
edits.push({
|
|
1273
|
+
position: entry.node.body.getStart(sourceFile) + 1,
|
|
1274
|
+
text: `return ${runName}(${planIndex},${args2},${asyncCallback}()=>{`,
|
|
1275
|
+
order: order++
|
|
1276
|
+
});
|
|
1277
|
+
edits.push({
|
|
1278
|
+
position: entry.node.body.end - 1,
|
|
1279
|
+
text: `}${valueFlag});`,
|
|
1280
|
+
order: order++
|
|
1281
|
+
});
|
|
1282
|
+
} else {
|
|
1283
|
+
edits.push({
|
|
1284
|
+
position: entry.node.body.getStart(sourceFile),
|
|
1285
|
+
text: `${runName}(${planIndex},${args2},${asyncCallback}()=>(`,
|
|
1286
|
+
order: order++
|
|
1287
|
+
});
|
|
1288
|
+
edits.push({
|
|
1289
|
+
position: entry.node.body.end,
|
|
1290
|
+
text: `)${valueFlag})`,
|
|
1291
|
+
order: order++
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
if (analysis) {
|
|
1295
|
+
for (const edit of captureWrapEdits(analysis.captureWraps, capName)) {
|
|
1296
|
+
edits.push({ ...edit, order: order++ });
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
const insertAt = insertPositionAfterPrologue(source, sourceFile);
|
|
1301
|
+
edits.push({
|
|
1302
|
+
position: insertAt,
|
|
1303
|
+
text: preambleInsert(
|
|
1304
|
+
source,
|
|
1305
|
+
insertAt,
|
|
1306
|
+
modulePreamble(
|
|
1307
|
+
runName,
|
|
1308
|
+
capName,
|
|
1309
|
+
plansName,
|
|
1310
|
+
JSON.stringify(delta.functions.map((fn) => fn.plan))
|
|
1311
|
+
)
|
|
1312
|
+
),
|
|
1313
|
+
order: -1
|
|
1314
|
+
});
|
|
1315
|
+
edits.sort((left, right) => right.position - left.position || right.order - left.order);
|
|
1316
|
+
let transformed = source;
|
|
1317
|
+
for (const edit of edits) {
|
|
1318
|
+
transformed = `${transformed.slice(0, edit.position)}${edit.text}${transformed.slice(edit.position)}`;
|
|
1319
|
+
}
|
|
1320
|
+
return { code: transformed, delta };
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
// src/auto-instrumentation/instrument-compiled-esm.ts
|
|
1324
|
+
var compiledJavaScript = /\.[cm]?js$/i;
|
|
1325
|
+
var walkCompiledFiles = (directory) => {
|
|
1326
|
+
const files = [];
|
|
1327
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
1328
|
+
if (entry.name === "node_modules" || entry.name === ".rasputin" || entry.name === ".git") {
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
const path = join2(directory, entry.name);
|
|
1332
|
+
if (entry.isDirectory()) {
|
|
1333
|
+
files.push(...walkCompiledFiles(path));
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
if (compiledJavaScript.test(entry.name)) files.push(path);
|
|
1337
|
+
}
|
|
1338
|
+
return files;
|
|
1339
|
+
};
|
|
1340
|
+
var writeText = (path, contents) => {
|
|
1341
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
1342
|
+
writeFileSync(path, contents);
|
|
1343
|
+
};
|
|
1344
|
+
var instrumentCompiledDirectory = (options) => {
|
|
1345
|
+
const inputDir = resolve5(options.inputDir);
|
|
1346
|
+
const outputDir = resolve5(options.outputDir ?? inputDir);
|
|
1347
|
+
const manifestPath = resolve5(
|
|
1348
|
+
options.manifestPath ?? join2(outputDir, ".rasputin", "instrumentation-manifest.json")
|
|
1349
|
+
);
|
|
1350
|
+
const transformedFiles = [];
|
|
1351
|
+
resetInstrumentationManifestRegistry();
|
|
1352
|
+
for (const filePath of walkCompiledFiles(inputDir)) {
|
|
1353
|
+
if (!isApplicationSource(filePath, {
|
|
1354
|
+
includeBuildOutput: true,
|
|
1355
|
+
excludedRoots: options.excludedRoots
|
|
1356
|
+
})) {
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
const source = readFileSync2(filePath, "utf8");
|
|
1360
|
+
if (source.includes("__rasputinRun__") || source.includes("__rasputinPlans__")) continue;
|
|
1361
|
+
const transformed = transformSource(source, {
|
|
1362
|
+
filePath,
|
|
1363
|
+
mapLocation: createSourceMapLocationMapper(filePath, source),
|
|
1364
|
+
...options.derivationMode ? { derivationMode: options.derivationMode } : {}
|
|
1365
|
+
});
|
|
1366
|
+
if (!transformed) continue;
|
|
1367
|
+
const outputPath = join2(outputDir, relative2(inputDir, filePath));
|
|
1368
|
+
writeText(outputPath, transformed.code);
|
|
1369
|
+
recordInstrumentationManifestDelta(filePath, transformed.delta);
|
|
1370
|
+
transformedFiles.push(outputPath);
|
|
1371
|
+
}
|
|
1372
|
+
const manifest = snapshotInstrumentationManifest({
|
|
1373
|
+
sourceRoot: options.sourceRoot,
|
|
1374
|
+
sourceRoots: options.sourceRoots
|
|
1375
|
+
});
|
|
1376
|
+
if (manifest) {
|
|
1377
|
+
writeText(manifestPath, `${JSON.stringify(manifest, null, " ")}
|
|
1378
|
+
`);
|
|
1379
|
+
}
|
|
1380
|
+
return { transformedFiles, manifest, manifestPath };
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
// src/auto-instrumentation/sdk-source-boundary.ts
|
|
1384
|
+
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
1385
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1386
|
+
var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve6(dirname3(fileURLToPath2(instrumentEntryUrl)), "../../..");
|
|
1387
|
+
|
|
1388
|
+
// src/instrument/build.ts
|
|
1389
|
+
var printUsage = () => {
|
|
1390
|
+
console.error(
|
|
1391
|
+
"Usage: rasputin-instrument <input-dir> [--out <output-dir>] [--manifest <path>] [--derivation-mode analysis|selective]"
|
|
1392
|
+
);
|
|
1393
|
+
};
|
|
1394
|
+
var parseDerivationMode = (value) => {
|
|
1395
|
+
if (value === void 0) return void 0;
|
|
1396
|
+
if (value === "analysis" || value === "selective") return value;
|
|
1397
|
+
console.error(`[rasputin] unknown --derivation-mode ${value} (expected analysis or selective)`);
|
|
1398
|
+
process.exit(1);
|
|
1399
|
+
};
|
|
1400
|
+
var parseArgs = (argv) => {
|
|
1401
|
+
const inputDir = argv.find((argument) => !argument.startsWith("--"));
|
|
1402
|
+
const option = (name) => {
|
|
1403
|
+
const index = argv.indexOf(name);
|
|
1404
|
+
return index >= 0 ? argv[index + 1] : void 0;
|
|
1405
|
+
};
|
|
1406
|
+
return {
|
|
1407
|
+
inputDir,
|
|
1408
|
+
outputDir: option("--out"),
|
|
1409
|
+
manifestPath: option("--manifest"),
|
|
1410
|
+
derivationMode: parseDerivationMode(option("--derivation-mode"))
|
|
1411
|
+
};
|
|
1412
|
+
};
|
|
1413
|
+
var args = parseArgs(process.argv.slice(2));
|
|
1414
|
+
if (!args.inputDir) {
|
|
1415
|
+
printUsage();
|
|
1416
|
+
process.exit(1);
|
|
1417
|
+
}
|
|
1418
|
+
var result = instrumentCompiledDirectory({
|
|
1419
|
+
inputDir: args.inputDir,
|
|
1420
|
+
outputDir: args.outputDir,
|
|
1421
|
+
manifestPath: args.manifestPath,
|
|
1422
|
+
excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)],
|
|
1423
|
+
...args.derivationMode ? { derivationMode: args.derivationMode } : {}
|
|
1424
|
+
});
|
|
1425
|
+
console.info(
|
|
1426
|
+
`[rasputin] instrumented ${result.transformedFiles.length} file${result.transformedFiles.length === 1 ? "" : "s"}`
|
|
1427
|
+
);
|
|
1428
|
+
if (result.manifest) {
|
|
1429
|
+
console.info(`[rasputin] wrote ${result.manifestPath}`);
|
|
1430
|
+
}
|