@rasputin-ai/node 0.5.0-alpha.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 +15 -15
- package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +2 -0
- package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -1
- package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +2 -1
- package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -1
- package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +1 -1
- package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
- package/dist/auto-instrumentation/transform-source.d.ts +6 -0
- 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 +12 -3
- 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 +21 -2
- 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/recorder-memory-budget.d.ts +1 -0
- package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
- package/dist/execution-recorder/safe-serialize.d.ts +1 -0
- package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
- package/dist/index.js +1162 -328
- package/dist/instrument/build.js +795 -72
- package/dist/instrument/bun.js +790 -67
- package/dist/instrument/node.js +790 -67
- package/dist/sdk-meta.d.ts +1 -1
- package/dist/sdk-meta.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/execution-recorder/call-aware-event-buffer.d.ts +0 -24
- package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +0 -1
package/dist/instrument/build.js
CHANGED
|
@@ -46,7 +46,7 @@ var definitionFromEncodedPlan = (plan) => {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
// src/auto-instrumentation/instrumentation-manifest-registry.ts
|
|
49
|
-
var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.
|
|
49
|
+
var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v3";
|
|
50
50
|
var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
|
|
51
51
|
var getRegistry = () => {
|
|
52
52
|
const existing = Reflect.get(globalThis, registrySymbol);
|
|
@@ -77,6 +77,7 @@ var snapshotInstrumentationManifest = (options = {}) => {
|
|
|
77
77
|
const functions = [];
|
|
78
78
|
const callSites = [];
|
|
79
79
|
const destructures = [];
|
|
80
|
+
const derivationPlans = [];
|
|
80
81
|
for (const delta of getRegistry().files.values()) {
|
|
81
82
|
for (const fn of delta.functions) {
|
|
82
83
|
const definition = definitionFromEncodedPlan(fn.plan);
|
|
@@ -128,6 +129,7 @@ var snapshotInstrumentationManifest = (options = {}) => {
|
|
|
128
129
|
binding: destructure.binding
|
|
129
130
|
});
|
|
130
131
|
}
|
|
132
|
+
derivationPlans.push(...delta.derivationPlans ?? []);
|
|
131
133
|
}
|
|
132
134
|
const registry = getRegistry();
|
|
133
135
|
if (registry.prebuilt && registry.files.size === 0) {
|
|
@@ -155,7 +157,8 @@ var snapshotInstrumentationManifest = (options = {}) => {
|
|
|
155
157
|
} : {},
|
|
156
158
|
functions,
|
|
157
159
|
callSites,
|
|
158
|
-
destructures
|
|
160
|
+
destructures,
|
|
161
|
+
derivationPlans
|
|
159
162
|
};
|
|
160
163
|
};
|
|
161
164
|
var resetInstrumentationManifestRegistry = () => {
|
|
@@ -251,20 +254,663 @@ var createSourceMapLocationMapper = (generatedPath, source) => {
|
|
|
251
254
|
|
|
252
255
|
// src/auto-instrumentation/transform-source.ts
|
|
253
256
|
import { resolve as resolve4 } from "node:path";
|
|
254
|
-
import
|
|
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
|
+
};
|
|
255
901
|
|
|
256
902
|
// src/execution-recorder/automatic-runtime.ts
|
|
257
903
|
var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
|
|
258
904
|
var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
|
|
259
905
|
|
|
260
906
|
// src/auto-instrumentation/collect-instrumentation-delta.ts
|
|
261
|
-
import
|
|
907
|
+
import ts2 from "typescript";
|
|
262
908
|
var paramBindingFromName = (name, sourceFile) => {
|
|
263
|
-
if (
|
|
264
|
-
if (
|
|
909
|
+
if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
|
|
910
|
+
if (ts2.isArrayBindingPattern(name)) {
|
|
265
911
|
const elements = name.elements.map(
|
|
266
912
|
(element) => {
|
|
267
|
-
if (
|
|
913
|
+
if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
|
|
268
914
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
269
915
|
if (element.dotDotDotToken) return { kind: "rest", binding };
|
|
270
916
|
return binding;
|
|
@@ -274,7 +920,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
274
920
|
}
|
|
275
921
|
const properties = name.elements.map((element) => {
|
|
276
922
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
277
|
-
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") :
|
|
923
|
+
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts2.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
|
|
278
924
|
if (element.dotDotDotToken) {
|
|
279
925
|
const rest = { kind: "rest", binding };
|
|
280
926
|
return { key, binding: rest };
|
|
@@ -286,7 +932,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
286
932
|
var collectFunctionParams = (node, sourceFile) => {
|
|
287
933
|
const params = [];
|
|
288
934
|
for (const parameter of node.parameters) {
|
|
289
|
-
if (
|
|
935
|
+
if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
290
936
|
const binding = paramBindingFromName(parameter.name, sourceFile);
|
|
291
937
|
params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
|
|
292
938
|
}
|
|
@@ -327,18 +973,18 @@ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
|
|
|
327
973
|
};
|
|
328
974
|
var unwrapExpression = (expression) => {
|
|
329
975
|
let current = expression;
|
|
330
|
-
while (
|
|
976
|
+
while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
|
|
331
977
|
current = current.expression;
|
|
332
978
|
}
|
|
333
|
-
if (
|
|
979
|
+
if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
|
|
334
980
|
return current;
|
|
335
981
|
};
|
|
336
982
|
var calleeFromExpression = (expression, sourceFile) => {
|
|
337
983
|
const current = unwrapExpression(expression);
|
|
338
|
-
if (
|
|
984
|
+
if (ts2.isIdentifier(current)) {
|
|
339
985
|
return { calleeName: current.text, calleeText: current.text };
|
|
340
986
|
}
|
|
341
|
-
if (
|
|
987
|
+
if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
|
|
342
988
|
return {
|
|
343
989
|
calleeName: current.name.text,
|
|
344
990
|
calleeText: current.getText(sourceFile).replace(/\s+/g, "")
|
|
@@ -348,9 +994,9 @@ var calleeFromExpression = (expression, sourceFile) => {
|
|
|
348
994
|
};
|
|
349
995
|
var resultBindingForCall = (call) => {
|
|
350
996
|
let node = call.parent;
|
|
351
|
-
if (
|
|
352
|
-
if (!
|
|
353
|
-
if (!
|
|
997
|
+
if (ts2.isAwaitExpression(node)) node = node.parent;
|
|
998
|
+
if (!ts2.isVariableDeclaration(node)) return null;
|
|
999
|
+
if (!ts2.isIdentifier(node.name)) return null;
|
|
354
1000
|
const initializer = node.initializer;
|
|
355
1001
|
if (!initializer) return null;
|
|
356
1002
|
const inner = unwrapExpression(initializer);
|
|
@@ -360,18 +1006,18 @@ var resultBindingForCall = (call) => {
|
|
|
360
1006
|
var argIdentifiersForCall = (call) => {
|
|
361
1007
|
const identifiers = [];
|
|
362
1008
|
call.arguments?.forEach((argument, index) => {
|
|
363
|
-
if (
|
|
1009
|
+
if (ts2.isIdentifier(argument)) {
|
|
364
1010
|
identifiers.push({ index, kind: "identifier", name: argument.text });
|
|
365
1011
|
return;
|
|
366
1012
|
}
|
|
367
|
-
if (!
|
|
1013
|
+
if (!ts2.isObjectLiteralExpression(argument)) return;
|
|
368
1014
|
const properties = [];
|
|
369
1015
|
for (const property of argument.properties) {
|
|
370
|
-
if (
|
|
1016
|
+
if (ts2.isShorthandPropertyAssignment(property)) {
|
|
371
1017
|
properties.push({ key: property.name.text, name: property.name.text });
|
|
372
1018
|
continue;
|
|
373
1019
|
}
|
|
374
|
-
if (
|
|
1020
|
+
if (ts2.isPropertyAssignment(property) && ts2.isIdentifier(property.name) && ts2.isIdentifier(property.initializer)) {
|
|
375
1021
|
properties.push({ key: property.name.text, name: property.initializer.text });
|
|
376
1022
|
}
|
|
377
1023
|
}
|
|
@@ -411,8 +1057,8 @@ var collectCallSite = (call, caller, context) => {
|
|
|
411
1057
|
};
|
|
412
1058
|
};
|
|
413
1059
|
var collectParamDestructure = (declaration, caller, context) => {
|
|
414
|
-
if (!declaration.initializer || !
|
|
415
|
-
if (!
|
|
1060
|
+
if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
|
|
1061
|
+
if (!ts2.isObjectBindingPattern(declaration.name) && !ts2.isArrayBindingPattern(declaration.name)) {
|
|
416
1062
|
return null;
|
|
417
1063
|
}
|
|
418
1064
|
const sourceParam = declaration.initializer.text;
|
|
@@ -436,33 +1082,33 @@ var collectParamDestructure = (declaration, caller, context) => {
|
|
|
436
1082
|
|
|
437
1083
|
// src/auto-instrumentation/transform-source.ts
|
|
438
1084
|
var scriptKindFor = (filePath) => {
|
|
439
|
-
if (/\.tsx$/i.test(filePath)) return
|
|
440
|
-
if (/\.jsx$/i.test(filePath)) return
|
|
441
|
-
if (/\.[cm]?ts$/i.test(filePath)) return
|
|
442
|
-
return
|
|
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;
|
|
443
1089
|
};
|
|
444
1090
|
var functionName = (node, sourceFile) => {
|
|
445
1091
|
if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
|
|
446
1092
|
const parent = node.parent;
|
|
447
|
-
if (
|
|
448
|
-
if (
|
|
449
|
-
if (
|
|
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);
|
|
450
1096
|
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
451
1097
|
return `anonymous@${location.line + 1}:${location.character + 1}`;
|
|
452
1098
|
};
|
|
453
1099
|
var functionLocationPosition = (node, sourceFile) => {
|
|
454
1100
|
if (node.name) return node.name.getStart(sourceFile);
|
|
455
1101
|
const parent = node.parent;
|
|
456
|
-
if ((
|
|
1102
|
+
if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
|
|
457
1103
|
return parent.name.getStart(sourceFile);
|
|
458
1104
|
}
|
|
459
1105
|
return node.getStart(sourceFile);
|
|
460
1106
|
};
|
|
461
1107
|
var bindingValue = (name, sourceFile) => {
|
|
462
|
-
if (
|
|
463
|
-
if (
|
|
1108
|
+
if (ts3.isIdentifier(name)) return name.text;
|
|
1109
|
+
if (ts3.isArrayBindingPattern(name)) {
|
|
464
1110
|
const values = name.elements.map((element) => {
|
|
465
|
-
if (
|
|
1111
|
+
if (ts3.isOmittedExpression(element)) return "";
|
|
466
1112
|
const value = bindingValue(element.name, sourceFile);
|
|
467
1113
|
return element.dotDotDotToken ? `...${value}` : value;
|
|
468
1114
|
});
|
|
@@ -478,14 +1124,14 @@ var bindingValue = (name, sourceFile) => {
|
|
|
478
1124
|
};
|
|
479
1125
|
var arrowArguments = (node) => {
|
|
480
1126
|
const values = node.parameters.flatMap((parameter) => {
|
|
481
|
-
if (
|
|
1127
|
+
if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
|
|
482
1128
|
const value = bindingValue(parameter.name, node.getSourceFile());
|
|
483
1129
|
return [parameter.dotDotDotToken ? `...${value}` : value];
|
|
484
1130
|
});
|
|
485
1131
|
return `[${values.join(",")}]`;
|
|
486
1132
|
};
|
|
487
1133
|
var isSupportedFunction = (node) => {
|
|
488
|
-
if (!
|
|
1134
|
+
if (!ts3.isFunctionDeclaration(node) && !ts3.isFunctionExpression(node) && !ts3.isArrowFunction(node) && !ts3.isMethodDeclaration(node) && !ts3.isGetAccessorDeclaration(node) && !ts3.isSetAccessorDeclaration(node)) {
|
|
489
1135
|
return false;
|
|
490
1136
|
}
|
|
491
1137
|
if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
|
|
@@ -496,7 +1142,22 @@ var uniqueName = (source, base) => {
|
|
|
496
1142
|
while (source.includes(name)) name += "_";
|
|
497
1143
|
return name;
|
|
498
1144
|
};
|
|
499
|
-
var
|
|
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)}));`;
|
|
500
1161
|
var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
501
1162
|
const start = mappedLocation(
|
|
502
1163
|
sourceFile,
|
|
@@ -527,77 +1188,128 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
|
527
1188
|
paramNames
|
|
528
1189
|
};
|
|
529
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
|
+
};
|
|
530
1201
|
var transformSource = (source, options) => {
|
|
531
1202
|
if (source.includes("@rasputin-ignore-file")) return void 0;
|
|
532
|
-
const sourceFile =
|
|
1203
|
+
const sourceFile = ts3.createSourceFile(
|
|
533
1204
|
options.filePath,
|
|
534
1205
|
source,
|
|
535
|
-
|
|
1206
|
+
ts3.ScriptTarget.Latest,
|
|
536
1207
|
true,
|
|
537
1208
|
scriptKindFor(options.filePath)
|
|
538
1209
|
);
|
|
539
1210
|
const runName = uniqueName(source, "__rasputinRun__");
|
|
1211
|
+
const capName = uniqueName(source, "__rasputinCap__");
|
|
540
1212
|
const plansName = uniqueName(source, "__rasputinPlans__");
|
|
541
1213
|
const absolutePath = resolve4(options.filePath);
|
|
1214
|
+
const derivationMode = options.derivationMode ?? "analysis";
|
|
542
1215
|
const edits = [];
|
|
543
1216
|
let order = 0;
|
|
544
1217
|
const delta = {
|
|
545
1218
|
functions: [],
|
|
546
1219
|
callSites: [],
|
|
547
|
-
destructures: []
|
|
1220
|
+
destructures: [],
|
|
1221
|
+
derivationPlans: []
|
|
548
1222
|
};
|
|
549
1223
|
const collectContext = {
|
|
550
1224
|
sourceFile,
|
|
551
1225
|
mapLocation: options.mapLocation,
|
|
552
1226
|
absolutePath
|
|
553
1227
|
};
|
|
1228
|
+
const pending = [];
|
|
554
1229
|
const visit = (node, caller) => {
|
|
555
1230
|
if (isSupportedFunction(node)) {
|
|
556
1231
|
const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
|
|
557
1232
|
if (!collected) {
|
|
558
|
-
|
|
1233
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
559
1234
|
return;
|
|
560
1235
|
}
|
|
561
|
-
|
|
1236
|
+
pending.push({ node, collected });
|
|
562
1237
|
delta.functions.push(collected);
|
|
563
|
-
|
|
564
|
-
const asyncCallback = node.modifiers?.some(
|
|
565
|
-
(modifier) => modifier.kind === ts2.SyntaxKind.AsyncKeyword
|
|
566
|
-
) ? "async " : "";
|
|
567
|
-
if (ts2.isBlock(node.body)) {
|
|
568
|
-
edits.push({
|
|
569
|
-
position: node.body.getStart(sourceFile) + 1,
|
|
570
|
-
text: `return ${runName}(${planIndex},${args2},${asyncCallback}()=>{`,
|
|
571
|
-
order: order++
|
|
572
|
-
});
|
|
573
|
-
edits.push({ position: node.body.end - 1, text: "});", order: order++ });
|
|
574
|
-
} else {
|
|
575
|
-
edits.push({
|
|
576
|
-
position: node.body.getStart(sourceFile),
|
|
577
|
-
text: `${runName}(${planIndex},${args2},${asyncCallback}()=>(`,
|
|
578
|
-
order: order++
|
|
579
|
-
});
|
|
580
|
-
edits.push({ position: node.body.end, text: "))", order: order++ });
|
|
581
|
-
}
|
|
582
|
-
ts2.forEachChild(node, (child) => visit(child, collected));
|
|
1238
|
+
ts3.forEachChild(node, (child) => visit(child, collected));
|
|
583
1239
|
return;
|
|
584
1240
|
}
|
|
585
|
-
if (caller && (
|
|
1241
|
+
if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
|
|
586
1242
|
const site = collectCallSite(node, caller, collectContext);
|
|
587
1243
|
if (site) delta.callSites.push(site);
|
|
588
1244
|
}
|
|
589
|
-
if (caller &&
|
|
1245
|
+
if (caller && ts3.isVariableDeclaration(node)) {
|
|
590
1246
|
const destructure = collectParamDestructure(node, caller, collectContext);
|
|
591
1247
|
if (destructure) delta.destructures.push(destructure);
|
|
592
1248
|
}
|
|
593
|
-
|
|
1249
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
594
1250
|
};
|
|
595
1251
|
visit(sourceFile);
|
|
596
|
-
if (
|
|
597
|
-
const
|
|
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);
|
|
598
1301
|
edits.push({
|
|
599
|
-
position:
|
|
600
|
-
text:
|
|
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
|
+
),
|
|
601
1313
|
order: -1
|
|
602
1314
|
});
|
|
603
1315
|
edits.sort((left, right) => right.position - left.position || right.order - left.order);
|
|
@@ -648,7 +1360,8 @@ var instrumentCompiledDirectory = (options) => {
|
|
|
648
1360
|
if (source.includes("__rasputinRun__") || source.includes("__rasputinPlans__")) continue;
|
|
649
1361
|
const transformed = transformSource(source, {
|
|
650
1362
|
filePath,
|
|
651
|
-
mapLocation: createSourceMapLocationMapper(filePath, source)
|
|
1363
|
+
mapLocation: createSourceMapLocationMapper(filePath, source),
|
|
1364
|
+
...options.derivationMode ? { derivationMode: options.derivationMode } : {}
|
|
652
1365
|
});
|
|
653
1366
|
if (!transformed) continue;
|
|
654
1367
|
const outputPath = join2(outputDir, relative2(inputDir, filePath));
|
|
@@ -674,7 +1387,15 @@ var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve6(dirname3(fileURLT
|
|
|
674
1387
|
|
|
675
1388
|
// src/instrument/build.ts
|
|
676
1389
|
var printUsage = () => {
|
|
677
|
-
console.error(
|
|
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);
|
|
678
1399
|
};
|
|
679
1400
|
var parseArgs = (argv) => {
|
|
680
1401
|
const inputDir = argv.find((argument) => !argument.startsWith("--"));
|
|
@@ -685,7 +1406,8 @@ var parseArgs = (argv) => {
|
|
|
685
1406
|
return {
|
|
686
1407
|
inputDir,
|
|
687
1408
|
outputDir: option("--out"),
|
|
688
|
-
manifestPath: option("--manifest")
|
|
1409
|
+
manifestPath: option("--manifest"),
|
|
1410
|
+
derivationMode: parseDerivationMode(option("--derivation-mode"))
|
|
689
1411
|
};
|
|
690
1412
|
};
|
|
691
1413
|
var args = parseArgs(process.argv.slice(2));
|
|
@@ -697,7 +1419,8 @@ var result = instrumentCompiledDirectory({
|
|
|
697
1419
|
inputDir: args.inputDir,
|
|
698
1420
|
outputDir: args.outputDir,
|
|
699
1421
|
manifestPath: args.manifestPath,
|
|
700
|
-
excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)]
|
|
1422
|
+
excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)],
|
|
1423
|
+
...args.derivationMode ? { derivationMode: args.derivationMode } : {}
|
|
701
1424
|
});
|
|
702
1425
|
console.info(
|
|
703
1426
|
`[rasputin] instrumented ${result.transformedFiles.length} file${result.transformedFiles.length === 1 ? "" : "s"}`
|