@rasputin-ai/node 0.5.0-alpha.5 → 0.5.0-alpha.7
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/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/derivation/analyze-function.d.ts +40 -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 +3 -1
- 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 +19 -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 +1058 -317
- package/dist/instrument/build.js +761 -71
- package/dist/instrument/bun.js +742 -66
- package/dist/instrument/node.js +742 -66
- package/dist/sdk-meta.d.ts +1 -1
- package/package.json +2 -2
- package/dist/execution-recorder/call-aware-event-buffer.d.ts +0 -37
- 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,649 @@ 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/derivation-proof.ts
|
|
381
|
+
var functionIsExact = (plan) => plan.fallbackReason === void 0 && plan.captureBoundaries.length === 0 && plan.controlFacts.length === 0;
|
|
382
|
+
|
|
383
|
+
// src/derivation/analyze-function.ts
|
|
384
|
+
var loc = (node, sourceFile) => {
|
|
385
|
+
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
386
|
+
return { line: position.line + 1, column: position.character + 1 };
|
|
387
|
+
};
|
|
388
|
+
var lookup = (ctx, name) => {
|
|
389
|
+
for (let index = ctx.scopes.length - 1; index >= 0; index--) {
|
|
390
|
+
const binding = ctx.scopes[index]?.get(name);
|
|
391
|
+
if (binding) return binding;
|
|
392
|
+
}
|
|
393
|
+
return void 0;
|
|
394
|
+
};
|
|
395
|
+
var fallback = (ctx, reason) => {
|
|
396
|
+
ctx.fallback ??= reason;
|
|
397
|
+
return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "unknown" };
|
|
398
|
+
};
|
|
399
|
+
var capture = (ctx, node, reason) => {
|
|
400
|
+
const rootId = ctx.nextRootId.value++;
|
|
401
|
+
const position = loc(node, ctx.sourceFile);
|
|
402
|
+
const expressionId = ctx.ir.root(rootId);
|
|
403
|
+
ctx.captureWraps.push({
|
|
404
|
+
rootId,
|
|
405
|
+
reason,
|
|
406
|
+
start: node.getStart(ctx.sourceFile),
|
|
407
|
+
end: node.end,
|
|
408
|
+
line: position.line,
|
|
409
|
+
column: position.column,
|
|
410
|
+
expressionId
|
|
411
|
+
});
|
|
412
|
+
return { expressionId, shape: "unknown", captured: true };
|
|
413
|
+
};
|
|
414
|
+
var binaryOp = (kind) => {
|
|
415
|
+
switch (kind) {
|
|
416
|
+
case ts.SyntaxKind.PlusToken:
|
|
417
|
+
return "add";
|
|
418
|
+
case ts.SyntaxKind.MinusToken:
|
|
419
|
+
return "sub";
|
|
420
|
+
case ts.SyntaxKind.AsteriskToken:
|
|
421
|
+
return "mul";
|
|
422
|
+
case ts.SyntaxKind.SlashToken:
|
|
423
|
+
return "div";
|
|
424
|
+
case ts.SyntaxKind.PercentToken:
|
|
425
|
+
return "mod";
|
|
426
|
+
case ts.SyntaxKind.EqualsEqualsToken:
|
|
427
|
+
return "eq";
|
|
428
|
+
case ts.SyntaxKind.ExclamationEqualsToken:
|
|
429
|
+
return "neq";
|
|
430
|
+
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
|
431
|
+
return "seq";
|
|
432
|
+
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
|
|
433
|
+
return "sneq";
|
|
434
|
+
case ts.SyntaxKind.LessThanToken:
|
|
435
|
+
return "lt";
|
|
436
|
+
case ts.SyntaxKind.LessThanEqualsToken:
|
|
437
|
+
return "lte";
|
|
438
|
+
case ts.SyntaxKind.GreaterThanToken:
|
|
439
|
+
return "gt";
|
|
440
|
+
case ts.SyntaxKind.GreaterThanEqualsToken:
|
|
441
|
+
return "gte";
|
|
442
|
+
default:
|
|
443
|
+
return void 0;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
var isNumericBinaryOp = (op) => op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod";
|
|
447
|
+
var unwrapParens = (node) => {
|
|
448
|
+
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
|
449
|
+
return node;
|
|
450
|
+
};
|
|
451
|
+
var numericLiteralValue = (node) => {
|
|
452
|
+
const value = Number(node.text);
|
|
453
|
+
return { type: "number", value };
|
|
454
|
+
};
|
|
455
|
+
var analyzeExpression = (ctx, node) => {
|
|
456
|
+
if (ctx.fallback) return fallback(ctx, ctx.fallback);
|
|
457
|
+
if (ts.isParenthesizedExpression(node)) return analyzeExpression(ctx, node.expression);
|
|
458
|
+
if (ts.isAsExpression(node) || ts.isNonNullExpression(node)) {
|
|
459
|
+
return analyzeExpression(ctx, node.expression);
|
|
460
|
+
}
|
|
461
|
+
if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
|
|
462
|
+
return analyzeExpression(ctx, node.expression);
|
|
463
|
+
}
|
|
464
|
+
if (ts.isSatisfiesExpression(node)) return analyzeExpression(ctx, node.expression);
|
|
465
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) {
|
|
466
|
+
return { expressionId: ctx.ir.constValue({ type: "null" }), shape: "primitive" };
|
|
467
|
+
}
|
|
468
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
469
|
+
return {
|
|
470
|
+
expressionId: ctx.ir.constValue({ type: "boolean", value: true }),
|
|
471
|
+
shape: "primitive"
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
475
|
+
return {
|
|
476
|
+
expressionId: ctx.ir.constValue({ type: "boolean", value: false }),
|
|
477
|
+
shape: "primitive"
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (ts.isNumericLiteral(node)) {
|
|
481
|
+
return { expressionId: ctx.ir.constValue(numericLiteralValue(node)), shape: "primitive" };
|
|
482
|
+
}
|
|
483
|
+
if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand) && node.operator === ts.SyntaxKind.MinusToken) {
|
|
484
|
+
const value = -Number(node.operand.text);
|
|
485
|
+
return {
|
|
486
|
+
expressionId: ctx.ir.constValue(
|
|
487
|
+
Object.is(value, -0) ? { type: "number", value: 0, negativeZero: true } : { type: "number", value }
|
|
488
|
+
),
|
|
489
|
+
shape: "primitive"
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
493
|
+
return {
|
|
494
|
+
expressionId: ctx.ir.constValue({ type: "string", value: node.text }),
|
|
495
|
+
shape: "primitive"
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
if (ts.isBigIntLiteral(node)) {
|
|
499
|
+
return {
|
|
500
|
+
expressionId: ctx.ir.constValue({ type: "bigint", value: node.text.replace(/n$/, "") }),
|
|
501
|
+
shape: "primitive"
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
if (ts.isIdentifier(node)) {
|
|
505
|
+
if (node.text === "undefined") {
|
|
506
|
+
return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "primitive" };
|
|
507
|
+
}
|
|
508
|
+
const binding = lookup(ctx, node.text);
|
|
509
|
+
if (!binding) return capture(ctx, node, "external_state");
|
|
510
|
+
if (binding.mutable) return capture(ctx, node, "mutable_object");
|
|
511
|
+
return {
|
|
512
|
+
expressionId: ctx.ir.alias(binding.expressionId),
|
|
513
|
+
shape: binding.shape
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
if (ts.isTemplateExpression(node)) {
|
|
517
|
+
const parts = [
|
|
518
|
+
{ kind: "lit", value: node.head.text }
|
|
519
|
+
];
|
|
520
|
+
for (const span of node.templateSpans) {
|
|
521
|
+
const nested = analyzeExpression(ctx, span.expression);
|
|
522
|
+
if (nested.shape !== "primitive" && !nested.captured) {
|
|
523
|
+
return capture(ctx, node, "unsupported_expression");
|
|
524
|
+
}
|
|
525
|
+
parts.push({ kind: "expr", expressionId: nested.expressionId });
|
|
526
|
+
parts.push({ kind: "lit", value: span.literal.text });
|
|
527
|
+
}
|
|
528
|
+
return { expressionId: ctx.ir.template(parts), shape: "primitive" };
|
|
529
|
+
}
|
|
530
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
531
|
+
if (node.elements.some((element) => ts.isSpreadElement(element))) {
|
|
532
|
+
return capture(ctx, node, "unsupported_expression");
|
|
533
|
+
}
|
|
534
|
+
const elements = node.elements.map((element) => {
|
|
535
|
+
if (ts.isOmittedExpression(element)) return { kind: "hole" };
|
|
536
|
+
return analyzeExpression(ctx, element).expressionId;
|
|
537
|
+
});
|
|
538
|
+
return { expressionId: ctx.ir.array(elements), shape: "array" };
|
|
539
|
+
}
|
|
540
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
541
|
+
const properties = [];
|
|
542
|
+
for (const property of node.properties) {
|
|
543
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
544
|
+
const nested = analyzeExpression(ctx, property.name);
|
|
545
|
+
properties.push({ key: property.name.text, value: nested.expressionId });
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
549
|
+
return capture(ctx, node, "unsupported_expression");
|
|
550
|
+
}
|
|
551
|
+
if (ts.isPrivateIdentifier(property.name) || ts.isComputedPropertyName(property.name)) {
|
|
552
|
+
return capture(ctx, node, "unsupported_expression");
|
|
553
|
+
}
|
|
554
|
+
const key = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : void 0;
|
|
555
|
+
if (key === void 0) return capture(ctx, node, "unsupported_expression");
|
|
556
|
+
properties.push({ key, value: analyzeExpression(ctx, property.initializer).expressionId });
|
|
557
|
+
}
|
|
558
|
+
return { expressionId: ctx.ir.object(properties), shape: "object" };
|
|
559
|
+
}
|
|
560
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
561
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
562
|
+
const object = analyzeExpression(ctx, node.expression);
|
|
563
|
+
if (object.shape === "object" && ts.isIdentifier(node.name)) {
|
|
564
|
+
return { expressionId: ctx.ir.prop(object.expressionId, node.name.text), shape: "unknown" };
|
|
565
|
+
}
|
|
566
|
+
return capture(ctx, node, "unknown_property");
|
|
567
|
+
}
|
|
568
|
+
if (ts.isElementAccessExpression(node)) {
|
|
569
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
570
|
+
const array = analyzeExpression(ctx, node.expression);
|
|
571
|
+
if (array.shape === "array" && ts.isNumericLiteral(node.argumentExpression)) {
|
|
572
|
+
return {
|
|
573
|
+
expressionId: ctx.ir.index(array.expressionId, Number(node.argumentExpression.text)),
|
|
574
|
+
shape: "unknown"
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
return capture(ctx, node, "unknown_property");
|
|
578
|
+
}
|
|
579
|
+
if (ts.isCallExpression(node)) {
|
|
580
|
+
if (node.questionDotToken || node.typeArguments?.length) {
|
|
581
|
+
return capture(ctx, node, "unsupported_expression");
|
|
582
|
+
}
|
|
583
|
+
if (!ts.isIdentifier(node.expression)) return capture(ctx, node, "dynamic_callee");
|
|
584
|
+
const calleeName = node.expression.text;
|
|
585
|
+
if (lookup(ctx, calleeName)) return capture(ctx, node, "opaque_call");
|
|
586
|
+
const provenId = ctx.provenExact.get(calleeName);
|
|
587
|
+
ctx.callEdges.push({
|
|
588
|
+
callerFunctionId: ctx.functionId,
|
|
589
|
+
calleeName,
|
|
590
|
+
...provenId ? { calleeFunctionId: provenId } : {}
|
|
591
|
+
});
|
|
592
|
+
if (!provenId) return capture(ctx, node, "opaque_call");
|
|
593
|
+
const args2 = node.arguments.map((argument) => {
|
|
594
|
+
if (ts.isSpreadElement(argument)) return void 0;
|
|
595
|
+
return analyzeExpression(ctx, argument).expressionId;
|
|
596
|
+
});
|
|
597
|
+
if (args2.some((argument) => argument === void 0))
|
|
598
|
+
return capture(ctx, node, "unsupported_expression");
|
|
599
|
+
return {
|
|
600
|
+
expressionId: ctx.ir.pureCall(
|
|
601
|
+
provenId,
|
|
602
|
+
args2.filter((id) => id !== void 0)
|
|
603
|
+
),
|
|
604
|
+
shape: "unknown"
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
if (ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node)) {
|
|
608
|
+
return capture(
|
|
609
|
+
ctx,
|
|
610
|
+
node,
|
|
611
|
+
node.kind === ts.SyntaxKind.AwaitExpression ? "nondeterministic" : "unsupported_expression"
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
if (ts.isConditionalExpression(node)) {
|
|
615
|
+
const test = analyzeExpression(ctx, node.condition);
|
|
616
|
+
const consequent = analyzeExpression(ctx, node.whenTrue);
|
|
617
|
+
const alternate = analyzeExpression(ctx, node.whenFalse);
|
|
618
|
+
return {
|
|
619
|
+
expressionId: ctx.ir.cond(test.expressionId, consequent.expressionId, alternate.expressionId),
|
|
620
|
+
shape: consequent.shape === alternate.shape ? consequent.shape : "unknown"
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
if (ts.isBinaryExpression(node)) {
|
|
624
|
+
if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
625
|
+
ctx.fallback ??= "mutable_state";
|
|
626
|
+
return capture(ctx, node, "mutable_object");
|
|
627
|
+
}
|
|
628
|
+
if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
629
|
+
const left2 = analyzeExpression(ctx, node.left);
|
|
630
|
+
const right2 = analyzeExpression(ctx, node.right);
|
|
631
|
+
const op2 = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "and" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "or" : "nullish";
|
|
632
|
+
return {
|
|
633
|
+
expressionId: ctx.ir.logical(op2, left2.expressionId, right2.expressionId),
|
|
634
|
+
shape: "unknown"
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
const op = binaryOp(node.operatorToken.kind);
|
|
638
|
+
if (!op) return capture(ctx, node, "unsupported_expression");
|
|
639
|
+
if (!ts.isExpression(node.left)) return capture(ctx, node, "unsupported_expression");
|
|
640
|
+
const left = analyzeExpression(ctx, node.left);
|
|
641
|
+
const right = analyzeExpression(ctx, node.right);
|
|
642
|
+
const numeric = isNumericBinaryOp(op);
|
|
643
|
+
const expressionId = ctx.ir.binary(
|
|
644
|
+
op,
|
|
645
|
+
left.expressionId,
|
|
646
|
+
right.expressionId,
|
|
647
|
+
numeric ? "numeric" : "primitive"
|
|
648
|
+
);
|
|
649
|
+
if (numeric && (left.captured || right.captured || left.shape !== "primitive" || right.shape !== "primitive")) {
|
|
650
|
+
ctx.hasCoercionGuard = true;
|
|
651
|
+
const fallbackRootId = ctx.nextRootId.value++;
|
|
652
|
+
ctx.guardWraps.push({
|
|
653
|
+
fallbackRootId,
|
|
654
|
+
start: node.getStart(ctx.sourceFile),
|
|
655
|
+
end: node.end,
|
|
656
|
+
op
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
return { expressionId, shape: "primitive" };
|
|
660
|
+
}
|
|
661
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
662
|
+
const operand = analyzeExpression(ctx, node.operand);
|
|
663
|
+
if (node.operator === ts.SyntaxKind.ExclamationToken) {
|
|
664
|
+
return { expressionId: ctx.ir.unary("not", operand.expressionId), shape: "primitive" };
|
|
665
|
+
}
|
|
666
|
+
if (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) {
|
|
667
|
+
if (operand.captured || operand.shape !== "primitive") ctx.hasCoercionGuard = true;
|
|
668
|
+
return {
|
|
669
|
+
expressionId: ctx.ir.unary(
|
|
670
|
+
node.operator === ts.SyntaxKind.MinusToken ? "minus" : "plus",
|
|
671
|
+
operand.expressionId
|
|
672
|
+
),
|
|
673
|
+
shape: "primitive"
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
return capture(ctx, node, "unsupported_expression");
|
|
677
|
+
}
|
|
678
|
+
if (ts.isTypeOfExpression(node)) {
|
|
679
|
+
const operand = unwrapParens(node.expression);
|
|
680
|
+
if (ts.isIdentifier(operand) && operand.text !== "undefined" && !lookup(ctx, operand.text)) {
|
|
681
|
+
return capture(ctx, node, "external_state");
|
|
682
|
+
}
|
|
683
|
+
const inner = analyzeExpression(ctx, node.expression);
|
|
684
|
+
return { expressionId: ctx.ir.unary("typeof", inner.expressionId), shape: "primitive" };
|
|
685
|
+
}
|
|
686
|
+
return capture(ctx, node, "unsupported_expression");
|
|
687
|
+
};
|
|
688
|
+
var bindIdentifier = (ctx, name, result2, declaration) => {
|
|
689
|
+
const current = ctx.scopes[ctx.scopes.length - 1];
|
|
690
|
+
current?.set(name.text, {
|
|
691
|
+
expressionId: result2.expressionId,
|
|
692
|
+
shape: result2.shape,
|
|
693
|
+
mutable: false
|
|
694
|
+
});
|
|
695
|
+
const position = loc(name, ctx.sourceFile);
|
|
696
|
+
const available = loc(declaration, ctx.sourceFile);
|
|
697
|
+
ctx.variables.push({
|
|
698
|
+
id: ctx.nextVariableId.value++,
|
|
699
|
+
name: name.text,
|
|
700
|
+
functionId: ctx.functionId,
|
|
701
|
+
scopeId: `${ctx.functionId}:${ctx.scopes.length}`,
|
|
702
|
+
line: position.line,
|
|
703
|
+
column: position.column,
|
|
704
|
+
assignmentId: ctx.nextAssignmentId.value++,
|
|
705
|
+
expressionId: result2.expressionId,
|
|
706
|
+
availableAtLine: available.line,
|
|
707
|
+
availableAtColumn: available.column
|
|
708
|
+
});
|
|
709
|
+
};
|
|
710
|
+
var analyzeStatement = (ctx, node) => {
|
|
711
|
+
if (ctx.fallback) return;
|
|
712
|
+
if (ts.isEmptyStatement(node) || ts.isDebuggerStatement(node)) return;
|
|
713
|
+
if (ts.isBlock(node)) {
|
|
714
|
+
ctx.scopes.push(/* @__PURE__ */ new Map());
|
|
715
|
+
for (const statement of node.statements) analyzeStatement(ctx, statement);
|
|
716
|
+
ctx.scopes.pop();
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (ts.isReturnStatement(node)) {
|
|
720
|
+
if (!node.expression) {
|
|
721
|
+
ctx.returnExpressionId = ctx.ir.constValue({ type: "undefined" });
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.expression).expressionId;
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (ts.isVariableStatement(node)) {
|
|
728
|
+
if (node.declarationList.flags & ts.NodeFlags.Using) {
|
|
729
|
+
fallback(ctx, "side_effect");
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
for (const declaration of node.declarationList.declarations) {
|
|
733
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
734
|
+
fallback(ctx, "unsupported_expression");
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const result2 = analyzeExpression(ctx, declaration.initializer);
|
|
738
|
+
bindIdentifier(ctx, declaration.name, result2, declaration);
|
|
739
|
+
}
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (ts.isExpressionStatement(node)) {
|
|
743
|
+
analyzeExpression(ctx, node.expression);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (ts.isThrowStatement(node)) {
|
|
747
|
+
ctx.hasThrow = true;
|
|
748
|
+
analyzeExpression(ctx, node.expression);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || ts.isTryStatement(node)) {
|
|
752
|
+
fallback(ctx, "unsupported_expression");
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
|
|
756
|
+
fallback(ctx, "has_loop");
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
fallback(ctx, "unsupported_expression");
|
|
760
|
+
};
|
|
761
|
+
var bindParams = (ctx, node) => {
|
|
762
|
+
let index = 0;
|
|
763
|
+
for (const parameter of node.parameters) {
|
|
764
|
+
if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
765
|
+
if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken) {
|
|
766
|
+
fallback(ctx, "unsupported_expression");
|
|
767
|
+
index++;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
const expressionId = ctx.ir.param(index);
|
|
771
|
+
ctx.identifierParams.push(parameter.name.text);
|
|
772
|
+
bindIdentifier(ctx, parameter.name, { expressionId, shape: "unknown" }, parameter);
|
|
773
|
+
index++;
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
var analyzeOne = (node, functionId, name, sourceFile, provenExact) => {
|
|
777
|
+
const ctx = {
|
|
778
|
+
sourceFile,
|
|
779
|
+
functionId,
|
|
780
|
+
name,
|
|
781
|
+
ir: new DerivationIrBuilder(),
|
|
782
|
+
variables: [],
|
|
783
|
+
captureWraps: [],
|
|
784
|
+
guardWraps: [],
|
|
785
|
+
callEdges: [],
|
|
786
|
+
scopes: [/* @__PURE__ */ new Map()],
|
|
787
|
+
nextRootId: { value: 0 },
|
|
788
|
+
nextVariableId: { value: 0 },
|
|
789
|
+
nextAssignmentId: { value: 0 },
|
|
790
|
+
hasThrow: false,
|
|
791
|
+
hasCoercionGuard: false,
|
|
792
|
+
provenExact,
|
|
793
|
+
identifierParams: []
|
|
794
|
+
};
|
|
795
|
+
if (node.asteriskToken || node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
|
|
796
|
+
ctx.fallback = "async";
|
|
797
|
+
} else {
|
|
798
|
+
bindParams(ctx, node);
|
|
799
|
+
if (ts.isBlock(node.body)) {
|
|
800
|
+
for (const statement of node.body.statements) analyzeStatement(ctx, statement);
|
|
801
|
+
} else {
|
|
802
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.body).expressionId;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
const plan = {
|
|
806
|
+
...emptyPlan(functionId),
|
|
807
|
+
expressions: ctx.ir.expressions,
|
|
808
|
+
variables: ctx.variables,
|
|
809
|
+
captureBoundaries: ctx.captureWraps.map((wrap) => ({
|
|
810
|
+
id: wrap.rootId,
|
|
811
|
+
reason: wrap.reason,
|
|
812
|
+
expressionId: wrap.expressionId,
|
|
813
|
+
line: wrap.line,
|
|
814
|
+
column: wrap.column
|
|
815
|
+
})),
|
|
816
|
+
controlFacts: [],
|
|
817
|
+
...ctx.returnExpressionId === void 0 ? {} : { returnExpressionId: ctx.returnExpressionId },
|
|
818
|
+
...ctx.fallback ? { fallbackReason: ctx.fallback } : {}
|
|
819
|
+
};
|
|
820
|
+
return {
|
|
821
|
+
functionId,
|
|
822
|
+
name,
|
|
823
|
+
node,
|
|
824
|
+
plan,
|
|
825
|
+
captureWraps: ctx.fallback ? [] : ctx.captureWraps,
|
|
826
|
+
guardWraps: ctx.fallback ? [] : ctx.guardWraps,
|
|
827
|
+
callEdges: ctx.callEdges,
|
|
828
|
+
hasThrow: ctx.hasThrow,
|
|
829
|
+
hasCoercionGuard: ctx.hasCoercionGuard
|
|
830
|
+
};
|
|
831
|
+
};
|
|
832
|
+
var analyzeModuleFunctions = (sourceFile, targets) => {
|
|
833
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
834
|
+
for (const target of targets) {
|
|
835
|
+
nameCounts.set(target.name, (nameCounts.get(target.name) ?? 0) + 1);
|
|
836
|
+
}
|
|
837
|
+
const firstPass = targets.map(
|
|
838
|
+
(target) => analyzeOne(target.node, target.functionId, target.name, sourceFile, /* @__PURE__ */ new Map())
|
|
839
|
+
);
|
|
840
|
+
const graph = buildDependencyGraph(
|
|
841
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
842
|
+
firstPass.flatMap((fn) => fn.callEdges)
|
|
843
|
+
);
|
|
844
|
+
const order = topologicalFunctionOrder(
|
|
845
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
846
|
+
graph
|
|
847
|
+
);
|
|
848
|
+
const byId = new Map(targets.map((target) => [target.functionId, target]));
|
|
849
|
+
const provenExact = /* @__PURE__ */ new Map();
|
|
850
|
+
const analyzed = [];
|
|
851
|
+
for (const functionId of order) {
|
|
852
|
+
const target = byId.get(functionId);
|
|
853
|
+
if (!target) continue;
|
|
854
|
+
const result2 = analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact);
|
|
855
|
+
if (functionIsExact(result2.plan) && nameCounts.get(result2.name) === 1) {
|
|
856
|
+
provenExact.set(result2.name, result2.functionId);
|
|
857
|
+
}
|
|
858
|
+
analyzed.push(result2);
|
|
859
|
+
}
|
|
860
|
+
const remaining = targets.filter(
|
|
861
|
+
(target) => !analyzed.some((fn) => fn.functionId === target.functionId)
|
|
862
|
+
);
|
|
863
|
+
for (const target of remaining) {
|
|
864
|
+
analyzed.push(analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact));
|
|
865
|
+
}
|
|
866
|
+
return analyzed;
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
// src/derivation/emit-capture-plan.ts
|
|
870
|
+
var captureWrapEdits = (wraps, capName) => {
|
|
871
|
+
const edits = [];
|
|
872
|
+
let order = 0;
|
|
873
|
+
for (const wrap of wraps) {
|
|
874
|
+
edits.push({
|
|
875
|
+
position: wrap.start,
|
|
876
|
+
text: `${capName}(${wrap.rootId},`,
|
|
877
|
+
order: order++
|
|
878
|
+
});
|
|
879
|
+
edits.push({
|
|
880
|
+
position: wrap.end,
|
|
881
|
+
text: ")",
|
|
882
|
+
order: order++
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
return edits;
|
|
886
|
+
};
|
|
255
887
|
|
|
256
888
|
// src/execution-recorder/automatic-runtime.ts
|
|
257
889
|
var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
|
|
258
890
|
var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
|
|
259
891
|
|
|
260
892
|
// src/auto-instrumentation/collect-instrumentation-delta.ts
|
|
261
|
-
import
|
|
893
|
+
import ts2 from "typescript";
|
|
262
894
|
var paramBindingFromName = (name, sourceFile) => {
|
|
263
|
-
if (
|
|
264
|
-
if (
|
|
895
|
+
if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
|
|
896
|
+
if (ts2.isArrayBindingPattern(name)) {
|
|
265
897
|
const elements = name.elements.map(
|
|
266
898
|
(element) => {
|
|
267
|
-
if (
|
|
899
|
+
if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
|
|
268
900
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
269
901
|
if (element.dotDotDotToken) return { kind: "rest", binding };
|
|
270
902
|
return binding;
|
|
@@ -274,7 +906,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
274
906
|
}
|
|
275
907
|
const properties = name.elements.map((element) => {
|
|
276
908
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
277
|
-
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") :
|
|
909
|
+
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts2.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
|
|
278
910
|
if (element.dotDotDotToken) {
|
|
279
911
|
const rest = { kind: "rest", binding };
|
|
280
912
|
return { key, binding: rest };
|
|
@@ -286,7 +918,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
286
918
|
var collectFunctionParams = (node, sourceFile) => {
|
|
287
919
|
const params = [];
|
|
288
920
|
for (const parameter of node.parameters) {
|
|
289
|
-
if (
|
|
921
|
+
if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
290
922
|
const binding = paramBindingFromName(parameter.name, sourceFile);
|
|
291
923
|
params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
|
|
292
924
|
}
|
|
@@ -327,18 +959,18 @@ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
|
|
|
327
959
|
};
|
|
328
960
|
var unwrapExpression = (expression) => {
|
|
329
961
|
let current = expression;
|
|
330
|
-
while (
|
|
962
|
+
while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
|
|
331
963
|
current = current.expression;
|
|
332
964
|
}
|
|
333
|
-
if (
|
|
965
|
+
if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
|
|
334
966
|
return current;
|
|
335
967
|
};
|
|
336
968
|
var calleeFromExpression = (expression, sourceFile) => {
|
|
337
969
|
const current = unwrapExpression(expression);
|
|
338
|
-
if (
|
|
970
|
+
if (ts2.isIdentifier(current)) {
|
|
339
971
|
return { calleeName: current.text, calleeText: current.text };
|
|
340
972
|
}
|
|
341
|
-
if (
|
|
973
|
+
if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
|
|
342
974
|
return {
|
|
343
975
|
calleeName: current.name.text,
|
|
344
976
|
calleeText: current.getText(sourceFile).replace(/\s+/g, "")
|
|
@@ -348,9 +980,9 @@ var calleeFromExpression = (expression, sourceFile) => {
|
|
|
348
980
|
};
|
|
349
981
|
var resultBindingForCall = (call) => {
|
|
350
982
|
let node = call.parent;
|
|
351
|
-
if (
|
|
352
|
-
if (!
|
|
353
|
-
if (!
|
|
983
|
+
if (ts2.isAwaitExpression(node)) node = node.parent;
|
|
984
|
+
if (!ts2.isVariableDeclaration(node)) return null;
|
|
985
|
+
if (!ts2.isIdentifier(node.name)) return null;
|
|
354
986
|
const initializer = node.initializer;
|
|
355
987
|
if (!initializer) return null;
|
|
356
988
|
const inner = unwrapExpression(initializer);
|
|
@@ -360,18 +992,18 @@ var resultBindingForCall = (call) => {
|
|
|
360
992
|
var argIdentifiersForCall = (call) => {
|
|
361
993
|
const identifiers = [];
|
|
362
994
|
call.arguments?.forEach((argument, index) => {
|
|
363
|
-
if (
|
|
995
|
+
if (ts2.isIdentifier(argument)) {
|
|
364
996
|
identifiers.push({ index, kind: "identifier", name: argument.text });
|
|
365
997
|
return;
|
|
366
998
|
}
|
|
367
|
-
if (!
|
|
999
|
+
if (!ts2.isObjectLiteralExpression(argument)) return;
|
|
368
1000
|
const properties = [];
|
|
369
1001
|
for (const property of argument.properties) {
|
|
370
|
-
if (
|
|
1002
|
+
if (ts2.isShorthandPropertyAssignment(property)) {
|
|
371
1003
|
properties.push({ key: property.name.text, name: property.name.text });
|
|
372
1004
|
continue;
|
|
373
1005
|
}
|
|
374
|
-
if (
|
|
1006
|
+
if (ts2.isPropertyAssignment(property) && ts2.isIdentifier(property.name) && ts2.isIdentifier(property.initializer)) {
|
|
375
1007
|
properties.push({ key: property.name.text, name: property.initializer.text });
|
|
376
1008
|
}
|
|
377
1009
|
}
|
|
@@ -411,8 +1043,8 @@ var collectCallSite = (call, caller, context) => {
|
|
|
411
1043
|
};
|
|
412
1044
|
};
|
|
413
1045
|
var collectParamDestructure = (declaration, caller, context) => {
|
|
414
|
-
if (!declaration.initializer || !
|
|
415
|
-
if (!
|
|
1046
|
+
if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
|
|
1047
|
+
if (!ts2.isObjectBindingPattern(declaration.name) && !ts2.isArrayBindingPattern(declaration.name)) {
|
|
416
1048
|
return null;
|
|
417
1049
|
}
|
|
418
1050
|
const sourceParam = declaration.initializer.text;
|
|
@@ -436,33 +1068,33 @@ var collectParamDestructure = (declaration, caller, context) => {
|
|
|
436
1068
|
|
|
437
1069
|
// src/auto-instrumentation/transform-source.ts
|
|
438
1070
|
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
|
|
1071
|
+
if (/\.tsx$/i.test(filePath)) return ts3.ScriptKind.TSX;
|
|
1072
|
+
if (/\.jsx$/i.test(filePath)) return ts3.ScriptKind.JSX;
|
|
1073
|
+
if (/\.[cm]?ts$/i.test(filePath)) return ts3.ScriptKind.TS;
|
|
1074
|
+
return ts3.ScriptKind.JS;
|
|
443
1075
|
};
|
|
444
1076
|
var functionName = (node, sourceFile) => {
|
|
445
1077
|
if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
|
|
446
1078
|
const parent = node.parent;
|
|
447
|
-
if (
|
|
448
|
-
if (
|
|
449
|
-
if (
|
|
1079
|
+
if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) return parent.name.text;
|
|
1080
|
+
if (ts3.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
|
|
1081
|
+
if (ts3.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
|
|
450
1082
|
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
451
1083
|
return `anonymous@${location.line + 1}:${location.character + 1}`;
|
|
452
1084
|
};
|
|
453
1085
|
var functionLocationPosition = (node, sourceFile) => {
|
|
454
1086
|
if (node.name) return node.name.getStart(sourceFile);
|
|
455
1087
|
const parent = node.parent;
|
|
456
|
-
if ((
|
|
1088
|
+
if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
|
|
457
1089
|
return parent.name.getStart(sourceFile);
|
|
458
1090
|
}
|
|
459
1091
|
return node.getStart(sourceFile);
|
|
460
1092
|
};
|
|
461
1093
|
var bindingValue = (name, sourceFile) => {
|
|
462
|
-
if (
|
|
463
|
-
if (
|
|
1094
|
+
if (ts3.isIdentifier(name)) return name.text;
|
|
1095
|
+
if (ts3.isArrayBindingPattern(name)) {
|
|
464
1096
|
const values = name.elements.map((element) => {
|
|
465
|
-
if (
|
|
1097
|
+
if (ts3.isOmittedExpression(element)) return "";
|
|
466
1098
|
const value = bindingValue(element.name, sourceFile);
|
|
467
1099
|
return element.dotDotDotToken ? `...${value}` : value;
|
|
468
1100
|
});
|
|
@@ -478,14 +1110,14 @@ var bindingValue = (name, sourceFile) => {
|
|
|
478
1110
|
};
|
|
479
1111
|
var arrowArguments = (node) => {
|
|
480
1112
|
const values = node.parameters.flatMap((parameter) => {
|
|
481
|
-
if (
|
|
1113
|
+
if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
|
|
482
1114
|
const value = bindingValue(parameter.name, node.getSourceFile());
|
|
483
1115
|
return [parameter.dotDotDotToken ? `...${value}` : value];
|
|
484
1116
|
});
|
|
485
1117
|
return `[${values.join(",")}]`;
|
|
486
1118
|
};
|
|
487
1119
|
var isSupportedFunction = (node) => {
|
|
488
|
-
if (!
|
|
1120
|
+
if (!ts3.isFunctionDeclaration(node) && !ts3.isFunctionExpression(node) && !ts3.isArrowFunction(node) && !ts3.isMethodDeclaration(node) && !ts3.isGetAccessorDeclaration(node) && !ts3.isSetAccessorDeclaration(node)) {
|
|
489
1121
|
return false;
|
|
490
1122
|
}
|
|
491
1123
|
if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
|
|
@@ -496,7 +1128,7 @@ var uniqueName = (source, base) => {
|
|
|
496
1128
|
while (source.includes(name)) name += "_";
|
|
497
1129
|
return name;
|
|
498
1130
|
};
|
|
499
|
-
var isDirectivePrologue = (statement) =>
|
|
1131
|
+
var isDirectivePrologue = (statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression);
|
|
500
1132
|
var insertPositionAfterPrologue = (source, sourceFile) => {
|
|
501
1133
|
const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
|
|
502
1134
|
let end = shebangEnd;
|
|
@@ -511,7 +1143,7 @@ var preambleInsert = (source, position, preamble) => {
|
|
|
511
1143
|
const previous = source[position - 1];
|
|
512
1144
|
return previous === ";" || previous === "\n" || previous === "\r" ? preamble : `;${preamble}`;
|
|
513
1145
|
};
|
|
514
|
-
var modulePreamble = (runName, plansName, plansJson) => `const ${plansName}=${plansJson};const ${runName}=((g,k)=>{const r=g[k]??(g[k]={runtimes:[],modules:[],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)=>d.run(i,a,c)}});return r.register(${plansName})})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));`;
|
|
1146
|
+
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)}));`;
|
|
515
1147
|
var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
516
1148
|
const start = mappedLocation(
|
|
517
1149
|
sourceFile,
|
|
@@ -542,80 +1174,127 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
|
542
1174
|
paramNames
|
|
543
1175
|
};
|
|
544
1176
|
};
|
|
1177
|
+
var canOmitCallValues = (analysis, analyzed) => {
|
|
1178
|
+
if (!functionIsExact(analysis.plan) || analysis.hasThrow || analysis.hasCoercionGuard) {
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
const callers = analyzed.filter(
|
|
1182
|
+
(caller) => caller.callEdges.some((edge) => edge.calleeFunctionId === analysis.functionId)
|
|
1183
|
+
);
|
|
1184
|
+
if (callers.length === 0) return false;
|
|
1185
|
+
return callers.every((caller) => caller.plan.fallbackReason === void 0 && !caller.hasThrow);
|
|
1186
|
+
};
|
|
545
1187
|
var transformSource = (source, options) => {
|
|
546
1188
|
if (source.includes("@rasputin-ignore-file")) return void 0;
|
|
547
|
-
const sourceFile =
|
|
1189
|
+
const sourceFile = ts3.createSourceFile(
|
|
548
1190
|
options.filePath,
|
|
549
1191
|
source,
|
|
550
|
-
|
|
1192
|
+
ts3.ScriptTarget.Latest,
|
|
551
1193
|
true,
|
|
552
1194
|
scriptKindFor(options.filePath)
|
|
553
1195
|
);
|
|
554
1196
|
const runName = uniqueName(source, "__rasputinRun__");
|
|
1197
|
+
const capName = uniqueName(source, "__rasputinCap__");
|
|
555
1198
|
const plansName = uniqueName(source, "__rasputinPlans__");
|
|
556
1199
|
const absolutePath = resolve4(options.filePath);
|
|
1200
|
+
const derivationMode = options.derivationMode ?? "analysis";
|
|
557
1201
|
const edits = [];
|
|
558
1202
|
let order = 0;
|
|
559
1203
|
const delta = {
|
|
560
1204
|
functions: [],
|
|
561
1205
|
callSites: [],
|
|
562
|
-
destructures: []
|
|
1206
|
+
destructures: [],
|
|
1207
|
+
derivationPlans: []
|
|
563
1208
|
};
|
|
564
1209
|
const collectContext = {
|
|
565
1210
|
sourceFile,
|
|
566
1211
|
mapLocation: options.mapLocation,
|
|
567
1212
|
absolutePath
|
|
568
1213
|
};
|
|
1214
|
+
const pending = [];
|
|
569
1215
|
const visit = (node, caller) => {
|
|
570
1216
|
if (isSupportedFunction(node)) {
|
|
571
1217
|
const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
|
|
572
1218
|
if (!collected) {
|
|
573
|
-
|
|
1219
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
574
1220
|
return;
|
|
575
1221
|
}
|
|
576
|
-
|
|
1222
|
+
pending.push({ node, collected });
|
|
577
1223
|
delta.functions.push(collected);
|
|
578
|
-
|
|
579
|
-
const asyncCallback = node.modifiers?.some(
|
|
580
|
-
(modifier) => modifier.kind === ts2.SyntaxKind.AsyncKeyword
|
|
581
|
-
) ? "async " : "";
|
|
582
|
-
if (ts2.isBlock(node.body)) {
|
|
583
|
-
edits.push({
|
|
584
|
-
position: node.body.getStart(sourceFile) + 1,
|
|
585
|
-
text: `return ${runName}(${planIndex},${args2},${asyncCallback}()=>{`,
|
|
586
|
-
order: order++
|
|
587
|
-
});
|
|
588
|
-
edits.push({ position: node.body.end - 1, text: "});", order: order++ });
|
|
589
|
-
} else {
|
|
590
|
-
edits.push({
|
|
591
|
-
position: node.body.getStart(sourceFile),
|
|
592
|
-
text: `${runName}(${planIndex},${args2},${asyncCallback}()=>(`,
|
|
593
|
-
order: order++
|
|
594
|
-
});
|
|
595
|
-
edits.push({ position: node.body.end, text: "))", order: order++ });
|
|
596
|
-
}
|
|
597
|
-
ts2.forEachChild(node, (child) => visit(child, collected));
|
|
1224
|
+
ts3.forEachChild(node, (child) => visit(child, collected));
|
|
598
1225
|
return;
|
|
599
1226
|
}
|
|
600
|
-
if (caller && (
|
|
1227
|
+
if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
|
|
601
1228
|
const site = collectCallSite(node, caller, collectContext);
|
|
602
1229
|
if (site) delta.callSites.push(site);
|
|
603
1230
|
}
|
|
604
|
-
if (caller &&
|
|
1231
|
+
if (caller && ts3.isVariableDeclaration(node)) {
|
|
605
1232
|
const destructure = collectParamDestructure(node, caller, collectContext);
|
|
606
1233
|
if (destructure) delta.destructures.push(destructure);
|
|
607
1234
|
}
|
|
608
|
-
|
|
1235
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
609
1236
|
};
|
|
610
1237
|
visit(sourceFile);
|
|
611
|
-
if (
|
|
1238
|
+
if (pending.length === 0) return void 0;
|
|
1239
|
+
const analyzed = analyzeModuleFunctions(
|
|
1240
|
+
sourceFile,
|
|
1241
|
+
pending.map((entry) => ({
|
|
1242
|
+
functionId: entry.collected.functionId,
|
|
1243
|
+
name: entry.collected.name,
|
|
1244
|
+
node: entry.node
|
|
1245
|
+
}))
|
|
1246
|
+
);
|
|
1247
|
+
delta.derivationPlans = analyzed.map((fn) => fn.plan);
|
|
1248
|
+
const analyzedById = new Map(analyzed.map((fn) => [fn.functionId, fn]));
|
|
1249
|
+
for (const [planIndex, entry] of pending.entries()) {
|
|
1250
|
+
const analysis = analyzedById.get(entry.collected.functionId);
|
|
1251
|
+
const omitValues = derivationMode === "selective" && analysis && canOmitCallValues(analysis, analyzed);
|
|
1252
|
+
const valueFlag = omitValues ? ",1" : "";
|
|
1253
|
+
const args2 = ts3.isArrowFunction(entry.node) ? arrowArguments(entry.node) : "arguments";
|
|
1254
|
+
const asyncCallback = entry.node.modifiers?.some(
|
|
1255
|
+
(modifier) => modifier.kind === ts3.SyntaxKind.AsyncKeyword
|
|
1256
|
+
) ? "async " : "";
|
|
1257
|
+
if (ts3.isBlock(entry.node.body)) {
|
|
1258
|
+
edits.push({
|
|
1259
|
+
position: entry.node.body.getStart(sourceFile) + 1,
|
|
1260
|
+
text: `return ${runName}(${planIndex},${args2},${asyncCallback}()=>{`,
|
|
1261
|
+
order: order++
|
|
1262
|
+
});
|
|
1263
|
+
edits.push({
|
|
1264
|
+
position: entry.node.body.end - 1,
|
|
1265
|
+
text: `}${valueFlag});`,
|
|
1266
|
+
order: order++
|
|
1267
|
+
});
|
|
1268
|
+
} else {
|
|
1269
|
+
edits.push({
|
|
1270
|
+
position: entry.node.body.getStart(sourceFile),
|
|
1271
|
+
text: `${runName}(${planIndex},${args2},${asyncCallback}()=>(`,
|
|
1272
|
+
order: order++
|
|
1273
|
+
});
|
|
1274
|
+
edits.push({
|
|
1275
|
+
position: entry.node.body.end,
|
|
1276
|
+
text: `)${valueFlag})`,
|
|
1277
|
+
order: order++
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
if (analysis) {
|
|
1281
|
+
for (const edit of captureWrapEdits(analysis.captureWraps, capName)) {
|
|
1282
|
+
edits.push({ ...edit, order: order++ });
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
612
1286
|
const insertAt = insertPositionAfterPrologue(source, sourceFile);
|
|
613
1287
|
edits.push({
|
|
614
1288
|
position: insertAt,
|
|
615
1289
|
text: preambleInsert(
|
|
616
1290
|
source,
|
|
617
1291
|
insertAt,
|
|
618
|
-
modulePreamble(
|
|
1292
|
+
modulePreamble(
|
|
1293
|
+
runName,
|
|
1294
|
+
capName,
|
|
1295
|
+
plansName,
|
|
1296
|
+
JSON.stringify(delta.functions.map((fn) => fn.plan))
|
|
1297
|
+
)
|
|
619
1298
|
),
|
|
620
1299
|
order: -1
|
|
621
1300
|
});
|
|
@@ -667,7 +1346,8 @@ var instrumentCompiledDirectory = (options) => {
|
|
|
667
1346
|
if (source.includes("__rasputinRun__") || source.includes("__rasputinPlans__")) continue;
|
|
668
1347
|
const transformed = transformSource(source, {
|
|
669
1348
|
filePath,
|
|
670
|
-
mapLocation: createSourceMapLocationMapper(filePath, source)
|
|
1349
|
+
mapLocation: createSourceMapLocationMapper(filePath, source),
|
|
1350
|
+
...options.derivationMode ? { derivationMode: options.derivationMode } : {}
|
|
671
1351
|
});
|
|
672
1352
|
if (!transformed) continue;
|
|
673
1353
|
const outputPath = join2(outputDir, relative2(inputDir, filePath));
|
|
@@ -693,7 +1373,15 @@ var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve6(dirname3(fileURLT
|
|
|
693
1373
|
|
|
694
1374
|
// src/instrument/build.ts
|
|
695
1375
|
var printUsage = () => {
|
|
696
|
-
console.error(
|
|
1376
|
+
console.error(
|
|
1377
|
+
"Usage: rasputin-instrument <input-dir> [--out <output-dir>] [--manifest <path>] [--derivation-mode analysis|selective]"
|
|
1378
|
+
);
|
|
1379
|
+
};
|
|
1380
|
+
var parseDerivationMode = (value) => {
|
|
1381
|
+
if (value === void 0) return void 0;
|
|
1382
|
+
if (value === "analysis" || value === "selective") return value;
|
|
1383
|
+
console.error(`[rasputin] unknown --derivation-mode ${value} (expected analysis or selective)`);
|
|
1384
|
+
process.exit(1);
|
|
697
1385
|
};
|
|
698
1386
|
var parseArgs = (argv) => {
|
|
699
1387
|
const inputDir = argv.find((argument) => !argument.startsWith("--"));
|
|
@@ -704,7 +1392,8 @@ var parseArgs = (argv) => {
|
|
|
704
1392
|
return {
|
|
705
1393
|
inputDir,
|
|
706
1394
|
outputDir: option("--out"),
|
|
707
|
-
manifestPath: option("--manifest")
|
|
1395
|
+
manifestPath: option("--manifest"),
|
|
1396
|
+
derivationMode: parseDerivationMode(option("--derivation-mode"))
|
|
708
1397
|
};
|
|
709
1398
|
};
|
|
710
1399
|
var args = parseArgs(process.argv.slice(2));
|
|
@@ -716,7 +1405,8 @@ var result = instrumentCompiledDirectory({
|
|
|
716
1405
|
inputDir: args.inputDir,
|
|
717
1406
|
outputDir: args.outputDir,
|
|
718
1407
|
manifestPath: args.manifestPath,
|
|
719
|
-
excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)]
|
|
1408
|
+
excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)],
|
|
1409
|
+
...args.derivationMode ? { derivationMode: args.derivationMode } : {}
|
|
720
1410
|
});
|
|
721
1411
|
console.info(
|
|
722
1412
|
`[rasputin] instrumented ${result.transformedFiles.length} file${result.transformedFiles.length === 1 ? "" : "s"}`
|