@rasputin-ai/node 0.5.0-alpha.6 → 0.5.0-alpha.8
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 +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 +3 -1
- package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
- package/dist/execution-recorder/execution-recorder-types.d.ts +13 -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/index.js +169 -36
- package/dist/instrument/build.js +767 -71
- package/dist/instrument/bun.js +748 -66
- package/dist/instrument/node.js +748 -66
- package/dist/sdk-meta.d.ts +1 -1
- package/package.json +2 -2
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,655 @@ 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
|
+
if (nested.captured) {
|
|
546
|
+
const wrap = ctx.captureWraps.find(
|
|
547
|
+
(candidate) => candidate.expressionId === nested.expressionId
|
|
548
|
+
);
|
|
549
|
+
if (wrap) wrap.shorthandPropertyName = property.name.text;
|
|
550
|
+
}
|
|
551
|
+
properties.push({ key: property.name.text, value: nested.expressionId });
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
555
|
+
return capture(ctx, node, "unsupported_expression");
|
|
556
|
+
}
|
|
557
|
+
if (ts.isPrivateIdentifier(property.name) || ts.isComputedPropertyName(property.name)) {
|
|
558
|
+
return capture(ctx, node, "unsupported_expression");
|
|
559
|
+
}
|
|
560
|
+
const key = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : void 0;
|
|
561
|
+
if (key === void 0) return capture(ctx, node, "unsupported_expression");
|
|
562
|
+
properties.push({ key, value: analyzeExpression(ctx, property.initializer).expressionId });
|
|
563
|
+
}
|
|
564
|
+
return { expressionId: ctx.ir.object(properties), shape: "object" };
|
|
565
|
+
}
|
|
566
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
567
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
568
|
+
const object = analyzeExpression(ctx, node.expression);
|
|
569
|
+
if (object.shape === "object" && ts.isIdentifier(node.name)) {
|
|
570
|
+
return { expressionId: ctx.ir.prop(object.expressionId, node.name.text), shape: "unknown" };
|
|
571
|
+
}
|
|
572
|
+
return capture(ctx, node, "unknown_property");
|
|
573
|
+
}
|
|
574
|
+
if (ts.isElementAccessExpression(node)) {
|
|
575
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
576
|
+
const array = analyzeExpression(ctx, node.expression);
|
|
577
|
+
if (array.shape === "array" && ts.isNumericLiteral(node.argumentExpression)) {
|
|
578
|
+
return {
|
|
579
|
+
expressionId: ctx.ir.index(array.expressionId, Number(node.argumentExpression.text)),
|
|
580
|
+
shape: "unknown"
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
return capture(ctx, node, "unknown_property");
|
|
584
|
+
}
|
|
585
|
+
if (ts.isCallExpression(node)) {
|
|
586
|
+
if (node.questionDotToken || node.typeArguments?.length) {
|
|
587
|
+
return capture(ctx, node, "unsupported_expression");
|
|
588
|
+
}
|
|
589
|
+
if (!ts.isIdentifier(node.expression)) return capture(ctx, node, "dynamic_callee");
|
|
590
|
+
const calleeName = node.expression.text;
|
|
591
|
+
if (lookup(ctx, calleeName)) return capture(ctx, node, "opaque_call");
|
|
592
|
+
const provenId = ctx.provenExact.get(calleeName);
|
|
593
|
+
ctx.callEdges.push({
|
|
594
|
+
callerFunctionId: ctx.functionId,
|
|
595
|
+
calleeName,
|
|
596
|
+
...provenId ? { calleeFunctionId: provenId } : {}
|
|
597
|
+
});
|
|
598
|
+
if (!provenId) return capture(ctx, node, "opaque_call");
|
|
599
|
+
const args2 = node.arguments.map((argument) => {
|
|
600
|
+
if (ts.isSpreadElement(argument)) return void 0;
|
|
601
|
+
return analyzeExpression(ctx, argument).expressionId;
|
|
602
|
+
});
|
|
603
|
+
if (args2.some((argument) => argument === void 0))
|
|
604
|
+
return capture(ctx, node, "unsupported_expression");
|
|
605
|
+
return {
|
|
606
|
+
expressionId: ctx.ir.pureCall(
|
|
607
|
+
provenId,
|
|
608
|
+
args2.filter((id) => id !== void 0)
|
|
609
|
+
),
|
|
610
|
+
shape: "unknown"
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
if (ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node)) {
|
|
614
|
+
return capture(
|
|
615
|
+
ctx,
|
|
616
|
+
node,
|
|
617
|
+
node.kind === ts.SyntaxKind.AwaitExpression ? "nondeterministic" : "unsupported_expression"
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
if (ts.isConditionalExpression(node)) {
|
|
621
|
+
const test = analyzeExpression(ctx, node.condition);
|
|
622
|
+
const consequent = analyzeExpression(ctx, node.whenTrue);
|
|
623
|
+
const alternate = analyzeExpression(ctx, node.whenFalse);
|
|
624
|
+
return {
|
|
625
|
+
expressionId: ctx.ir.cond(test.expressionId, consequent.expressionId, alternate.expressionId),
|
|
626
|
+
shape: consequent.shape === alternate.shape ? consequent.shape : "unknown"
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
if (ts.isBinaryExpression(node)) {
|
|
630
|
+
if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
631
|
+
ctx.fallback ??= "mutable_state";
|
|
632
|
+
return capture(ctx, node, "mutable_object");
|
|
633
|
+
}
|
|
634
|
+
if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
635
|
+
const left2 = analyzeExpression(ctx, node.left);
|
|
636
|
+
const right2 = analyzeExpression(ctx, node.right);
|
|
637
|
+
const op2 = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "and" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "or" : "nullish";
|
|
638
|
+
return {
|
|
639
|
+
expressionId: ctx.ir.logical(op2, left2.expressionId, right2.expressionId),
|
|
640
|
+
shape: "unknown"
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
const op = binaryOp(node.operatorToken.kind);
|
|
644
|
+
if (!op) return capture(ctx, node, "unsupported_expression");
|
|
645
|
+
if (!ts.isExpression(node.left)) return capture(ctx, node, "unsupported_expression");
|
|
646
|
+
const left = analyzeExpression(ctx, node.left);
|
|
647
|
+
const right = analyzeExpression(ctx, node.right);
|
|
648
|
+
const numeric = isNumericBinaryOp(op);
|
|
649
|
+
const expressionId = ctx.ir.binary(
|
|
650
|
+
op,
|
|
651
|
+
left.expressionId,
|
|
652
|
+
right.expressionId,
|
|
653
|
+
numeric ? "numeric" : "primitive"
|
|
654
|
+
);
|
|
655
|
+
if (numeric && (left.captured || right.captured || left.shape !== "primitive" || right.shape !== "primitive")) {
|
|
656
|
+
ctx.hasCoercionGuard = true;
|
|
657
|
+
const fallbackRootId = ctx.nextRootId.value++;
|
|
658
|
+
ctx.guardWraps.push({
|
|
659
|
+
fallbackRootId,
|
|
660
|
+
start: node.getStart(ctx.sourceFile),
|
|
661
|
+
end: node.end,
|
|
662
|
+
op
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
return { expressionId, shape: "primitive" };
|
|
666
|
+
}
|
|
667
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
668
|
+
const operand = analyzeExpression(ctx, node.operand);
|
|
669
|
+
if (node.operator === ts.SyntaxKind.ExclamationToken) {
|
|
670
|
+
return { expressionId: ctx.ir.unary("not", operand.expressionId), shape: "primitive" };
|
|
671
|
+
}
|
|
672
|
+
if (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) {
|
|
673
|
+
if (operand.captured || operand.shape !== "primitive") ctx.hasCoercionGuard = true;
|
|
674
|
+
return {
|
|
675
|
+
expressionId: ctx.ir.unary(
|
|
676
|
+
node.operator === ts.SyntaxKind.MinusToken ? "minus" : "plus",
|
|
677
|
+
operand.expressionId
|
|
678
|
+
),
|
|
679
|
+
shape: "primitive"
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
return capture(ctx, node, "unsupported_expression");
|
|
683
|
+
}
|
|
684
|
+
if (ts.isTypeOfExpression(node)) {
|
|
685
|
+
const operand = unwrapParens(node.expression);
|
|
686
|
+
if (ts.isIdentifier(operand) && operand.text !== "undefined" && !lookup(ctx, operand.text)) {
|
|
687
|
+
return capture(ctx, node, "external_state");
|
|
688
|
+
}
|
|
689
|
+
const inner = analyzeExpression(ctx, node.expression);
|
|
690
|
+
return { expressionId: ctx.ir.unary("typeof", inner.expressionId), shape: "primitive" };
|
|
691
|
+
}
|
|
692
|
+
return capture(ctx, node, "unsupported_expression");
|
|
693
|
+
};
|
|
694
|
+
var bindIdentifier = (ctx, name, result2, declaration) => {
|
|
695
|
+
const current = ctx.scopes[ctx.scopes.length - 1];
|
|
696
|
+
current?.set(name.text, {
|
|
697
|
+
expressionId: result2.expressionId,
|
|
698
|
+
shape: result2.shape,
|
|
699
|
+
mutable: false
|
|
700
|
+
});
|
|
701
|
+
const position = loc(name, ctx.sourceFile);
|
|
702
|
+
const available = loc(declaration, ctx.sourceFile);
|
|
703
|
+
ctx.variables.push({
|
|
704
|
+
id: ctx.nextVariableId.value++,
|
|
705
|
+
name: name.text,
|
|
706
|
+
functionId: ctx.functionId,
|
|
707
|
+
scopeId: `${ctx.functionId}:${ctx.scopes.length}`,
|
|
708
|
+
line: position.line,
|
|
709
|
+
column: position.column,
|
|
710
|
+
assignmentId: ctx.nextAssignmentId.value++,
|
|
711
|
+
expressionId: result2.expressionId,
|
|
712
|
+
availableAtLine: available.line,
|
|
713
|
+
availableAtColumn: available.column
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
var analyzeStatement = (ctx, node) => {
|
|
717
|
+
if (ctx.fallback) return;
|
|
718
|
+
if (ts.isEmptyStatement(node) || ts.isDebuggerStatement(node)) return;
|
|
719
|
+
if (ts.isBlock(node)) {
|
|
720
|
+
ctx.scopes.push(/* @__PURE__ */ new Map());
|
|
721
|
+
for (const statement of node.statements) analyzeStatement(ctx, statement);
|
|
722
|
+
ctx.scopes.pop();
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (ts.isReturnStatement(node)) {
|
|
726
|
+
if (!node.expression) {
|
|
727
|
+
ctx.returnExpressionId = ctx.ir.constValue({ type: "undefined" });
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.expression).expressionId;
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (ts.isVariableStatement(node)) {
|
|
734
|
+
if (node.declarationList.flags & ts.NodeFlags.Using) {
|
|
735
|
+
fallback(ctx, "side_effect");
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
for (const declaration of node.declarationList.declarations) {
|
|
739
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
740
|
+
fallback(ctx, "unsupported_expression");
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
const result2 = analyzeExpression(ctx, declaration.initializer);
|
|
744
|
+
bindIdentifier(ctx, declaration.name, result2, declaration);
|
|
745
|
+
}
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (ts.isExpressionStatement(node)) {
|
|
749
|
+
analyzeExpression(ctx, node.expression);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (ts.isThrowStatement(node)) {
|
|
753
|
+
ctx.hasThrow = true;
|
|
754
|
+
analyzeExpression(ctx, node.expression);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || ts.isTryStatement(node)) {
|
|
758
|
+
fallback(ctx, "unsupported_expression");
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
|
|
762
|
+
fallback(ctx, "has_loop");
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
fallback(ctx, "unsupported_expression");
|
|
766
|
+
};
|
|
767
|
+
var bindParams = (ctx, node) => {
|
|
768
|
+
let index = 0;
|
|
769
|
+
for (const parameter of node.parameters) {
|
|
770
|
+
if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
771
|
+
if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken) {
|
|
772
|
+
fallback(ctx, "unsupported_expression");
|
|
773
|
+
index++;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
const expressionId = ctx.ir.param(index);
|
|
777
|
+
ctx.identifierParams.push(parameter.name.text);
|
|
778
|
+
bindIdentifier(ctx, parameter.name, { expressionId, shape: "unknown" }, parameter);
|
|
779
|
+
index++;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
var analyzeOne = (node, functionId, name, sourceFile, provenExact) => {
|
|
783
|
+
const ctx = {
|
|
784
|
+
sourceFile,
|
|
785
|
+
functionId,
|
|
786
|
+
name,
|
|
787
|
+
ir: new DerivationIrBuilder(),
|
|
788
|
+
variables: [],
|
|
789
|
+
captureWraps: [],
|
|
790
|
+
guardWraps: [],
|
|
791
|
+
callEdges: [],
|
|
792
|
+
scopes: [/* @__PURE__ */ new Map()],
|
|
793
|
+
nextRootId: { value: 0 },
|
|
794
|
+
nextVariableId: { value: 0 },
|
|
795
|
+
nextAssignmentId: { value: 0 },
|
|
796
|
+
hasThrow: false,
|
|
797
|
+
hasCoercionGuard: false,
|
|
798
|
+
provenExact,
|
|
799
|
+
identifierParams: []
|
|
800
|
+
};
|
|
801
|
+
if (node.asteriskToken || node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
|
|
802
|
+
ctx.fallback = "async";
|
|
803
|
+
} else {
|
|
804
|
+
bindParams(ctx, node);
|
|
805
|
+
if (ts.isBlock(node.body)) {
|
|
806
|
+
for (const statement of node.body.statements) analyzeStatement(ctx, statement);
|
|
807
|
+
} else {
|
|
808
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.body).expressionId;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const plan = {
|
|
812
|
+
...emptyPlan(functionId),
|
|
813
|
+
expressions: ctx.ir.expressions,
|
|
814
|
+
variables: ctx.variables,
|
|
815
|
+
captureBoundaries: ctx.captureWraps.map((wrap) => ({
|
|
816
|
+
id: wrap.rootId,
|
|
817
|
+
reason: wrap.reason,
|
|
818
|
+
expressionId: wrap.expressionId,
|
|
819
|
+
line: wrap.line,
|
|
820
|
+
column: wrap.column
|
|
821
|
+
})),
|
|
822
|
+
controlFacts: [],
|
|
823
|
+
...ctx.returnExpressionId === void 0 ? {} : { returnExpressionId: ctx.returnExpressionId },
|
|
824
|
+
...ctx.fallback ? { fallbackReason: ctx.fallback } : {}
|
|
825
|
+
};
|
|
826
|
+
return {
|
|
827
|
+
functionId,
|
|
828
|
+
name,
|
|
829
|
+
node,
|
|
830
|
+
plan,
|
|
831
|
+
captureWraps: ctx.fallback ? [] : ctx.captureWraps,
|
|
832
|
+
guardWraps: ctx.fallback ? [] : ctx.guardWraps,
|
|
833
|
+
callEdges: ctx.callEdges,
|
|
834
|
+
hasThrow: ctx.hasThrow,
|
|
835
|
+
hasCoercionGuard: ctx.hasCoercionGuard
|
|
836
|
+
};
|
|
837
|
+
};
|
|
838
|
+
var analyzeModuleFunctions = (sourceFile, targets) => {
|
|
839
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
840
|
+
for (const target of targets) {
|
|
841
|
+
nameCounts.set(target.name, (nameCounts.get(target.name) ?? 0) + 1);
|
|
842
|
+
}
|
|
843
|
+
const firstPass = targets.map(
|
|
844
|
+
(target) => analyzeOne(target.node, target.functionId, target.name, sourceFile, /* @__PURE__ */ new Map())
|
|
845
|
+
);
|
|
846
|
+
const graph = buildDependencyGraph(
|
|
847
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
848
|
+
firstPass.flatMap((fn) => fn.callEdges)
|
|
849
|
+
);
|
|
850
|
+
const order = topologicalFunctionOrder(
|
|
851
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
852
|
+
graph
|
|
853
|
+
);
|
|
854
|
+
const byId = new Map(targets.map((target) => [target.functionId, target]));
|
|
855
|
+
const provenExact = /* @__PURE__ */ new Map();
|
|
856
|
+
const analyzed = [];
|
|
857
|
+
for (const functionId of order) {
|
|
858
|
+
const target = byId.get(functionId);
|
|
859
|
+
if (!target) continue;
|
|
860
|
+
const result2 = analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact);
|
|
861
|
+
if (functionIsExact(result2.plan) && nameCounts.get(result2.name) === 1) {
|
|
862
|
+
provenExact.set(result2.name, result2.functionId);
|
|
863
|
+
}
|
|
864
|
+
analyzed.push(result2);
|
|
865
|
+
}
|
|
866
|
+
const remaining = targets.filter(
|
|
867
|
+
(target) => !analyzed.some((fn) => fn.functionId === target.functionId)
|
|
868
|
+
);
|
|
869
|
+
for (const target of remaining) {
|
|
870
|
+
analyzed.push(analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact));
|
|
871
|
+
}
|
|
872
|
+
return analyzed;
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
// src/derivation/emit-capture-plan.ts
|
|
876
|
+
var captureWrapEdits = (wraps, capName) => {
|
|
877
|
+
const edits = [];
|
|
878
|
+
let order = 0;
|
|
879
|
+
for (const wrap of wraps) {
|
|
880
|
+
edits.push({
|
|
881
|
+
position: wrap.start,
|
|
882
|
+
text: `${wrap.shorthandPropertyName ? `${wrap.shorthandPropertyName}:` : ""}${capName}(${wrap.rootId},`,
|
|
883
|
+
order: order++
|
|
884
|
+
});
|
|
885
|
+
edits.push({
|
|
886
|
+
position: wrap.end,
|
|
887
|
+
text: ")",
|
|
888
|
+
order: order++
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
return edits;
|
|
892
|
+
};
|
|
255
893
|
|
|
256
894
|
// src/execution-recorder/automatic-runtime.ts
|
|
257
895
|
var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
|
|
258
896
|
var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
|
|
259
897
|
|
|
260
898
|
// src/auto-instrumentation/collect-instrumentation-delta.ts
|
|
261
|
-
import
|
|
899
|
+
import ts2 from "typescript";
|
|
262
900
|
var paramBindingFromName = (name, sourceFile) => {
|
|
263
|
-
if (
|
|
264
|
-
if (
|
|
901
|
+
if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
|
|
902
|
+
if (ts2.isArrayBindingPattern(name)) {
|
|
265
903
|
const elements = name.elements.map(
|
|
266
904
|
(element) => {
|
|
267
|
-
if (
|
|
905
|
+
if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
|
|
268
906
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
269
907
|
if (element.dotDotDotToken) return { kind: "rest", binding };
|
|
270
908
|
return binding;
|
|
@@ -274,7 +912,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
274
912
|
}
|
|
275
913
|
const properties = name.elements.map((element) => {
|
|
276
914
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
277
|
-
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") :
|
|
915
|
+
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts2.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
|
|
278
916
|
if (element.dotDotDotToken) {
|
|
279
917
|
const rest = { kind: "rest", binding };
|
|
280
918
|
return { key, binding: rest };
|
|
@@ -286,7 +924,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
286
924
|
var collectFunctionParams = (node, sourceFile) => {
|
|
287
925
|
const params = [];
|
|
288
926
|
for (const parameter of node.parameters) {
|
|
289
|
-
if (
|
|
927
|
+
if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
290
928
|
const binding = paramBindingFromName(parameter.name, sourceFile);
|
|
291
929
|
params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
|
|
292
930
|
}
|
|
@@ -327,18 +965,18 @@ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
|
|
|
327
965
|
};
|
|
328
966
|
var unwrapExpression = (expression) => {
|
|
329
967
|
let current = expression;
|
|
330
|
-
while (
|
|
968
|
+
while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
|
|
331
969
|
current = current.expression;
|
|
332
970
|
}
|
|
333
|
-
if (
|
|
971
|
+
if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
|
|
334
972
|
return current;
|
|
335
973
|
};
|
|
336
974
|
var calleeFromExpression = (expression, sourceFile) => {
|
|
337
975
|
const current = unwrapExpression(expression);
|
|
338
|
-
if (
|
|
976
|
+
if (ts2.isIdentifier(current)) {
|
|
339
977
|
return { calleeName: current.text, calleeText: current.text };
|
|
340
978
|
}
|
|
341
|
-
if (
|
|
979
|
+
if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
|
|
342
980
|
return {
|
|
343
981
|
calleeName: current.name.text,
|
|
344
982
|
calleeText: current.getText(sourceFile).replace(/\s+/g, "")
|
|
@@ -348,9 +986,9 @@ var calleeFromExpression = (expression, sourceFile) => {
|
|
|
348
986
|
};
|
|
349
987
|
var resultBindingForCall = (call) => {
|
|
350
988
|
let node = call.parent;
|
|
351
|
-
if (
|
|
352
|
-
if (!
|
|
353
|
-
if (!
|
|
989
|
+
if (ts2.isAwaitExpression(node)) node = node.parent;
|
|
990
|
+
if (!ts2.isVariableDeclaration(node)) return null;
|
|
991
|
+
if (!ts2.isIdentifier(node.name)) return null;
|
|
354
992
|
const initializer = node.initializer;
|
|
355
993
|
if (!initializer) return null;
|
|
356
994
|
const inner = unwrapExpression(initializer);
|
|
@@ -360,18 +998,18 @@ var resultBindingForCall = (call) => {
|
|
|
360
998
|
var argIdentifiersForCall = (call) => {
|
|
361
999
|
const identifiers = [];
|
|
362
1000
|
call.arguments?.forEach((argument, index) => {
|
|
363
|
-
if (
|
|
1001
|
+
if (ts2.isIdentifier(argument)) {
|
|
364
1002
|
identifiers.push({ index, kind: "identifier", name: argument.text });
|
|
365
1003
|
return;
|
|
366
1004
|
}
|
|
367
|
-
if (!
|
|
1005
|
+
if (!ts2.isObjectLiteralExpression(argument)) return;
|
|
368
1006
|
const properties = [];
|
|
369
1007
|
for (const property of argument.properties) {
|
|
370
|
-
if (
|
|
1008
|
+
if (ts2.isShorthandPropertyAssignment(property)) {
|
|
371
1009
|
properties.push({ key: property.name.text, name: property.name.text });
|
|
372
1010
|
continue;
|
|
373
1011
|
}
|
|
374
|
-
if (
|
|
1012
|
+
if (ts2.isPropertyAssignment(property) && ts2.isIdentifier(property.name) && ts2.isIdentifier(property.initializer)) {
|
|
375
1013
|
properties.push({ key: property.name.text, name: property.initializer.text });
|
|
376
1014
|
}
|
|
377
1015
|
}
|
|
@@ -411,8 +1049,8 @@ var collectCallSite = (call, caller, context) => {
|
|
|
411
1049
|
};
|
|
412
1050
|
};
|
|
413
1051
|
var collectParamDestructure = (declaration, caller, context) => {
|
|
414
|
-
if (!declaration.initializer || !
|
|
415
|
-
if (!
|
|
1052
|
+
if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
|
|
1053
|
+
if (!ts2.isObjectBindingPattern(declaration.name) && !ts2.isArrayBindingPattern(declaration.name)) {
|
|
416
1054
|
return null;
|
|
417
1055
|
}
|
|
418
1056
|
const sourceParam = declaration.initializer.text;
|
|
@@ -436,33 +1074,33 @@ var collectParamDestructure = (declaration, caller, context) => {
|
|
|
436
1074
|
|
|
437
1075
|
// src/auto-instrumentation/transform-source.ts
|
|
438
1076
|
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
|
|
1077
|
+
if (/\.tsx$/i.test(filePath)) return ts3.ScriptKind.TSX;
|
|
1078
|
+
if (/\.jsx$/i.test(filePath)) return ts3.ScriptKind.JSX;
|
|
1079
|
+
if (/\.[cm]?ts$/i.test(filePath)) return ts3.ScriptKind.TS;
|
|
1080
|
+
return ts3.ScriptKind.JS;
|
|
443
1081
|
};
|
|
444
1082
|
var functionName = (node, sourceFile) => {
|
|
445
1083
|
if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
|
|
446
1084
|
const parent = node.parent;
|
|
447
|
-
if (
|
|
448
|
-
if (
|
|
449
|
-
if (
|
|
1085
|
+
if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) return parent.name.text;
|
|
1086
|
+
if (ts3.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
|
|
1087
|
+
if (ts3.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
|
|
450
1088
|
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
451
1089
|
return `anonymous@${location.line + 1}:${location.character + 1}`;
|
|
452
1090
|
};
|
|
453
1091
|
var functionLocationPosition = (node, sourceFile) => {
|
|
454
1092
|
if (node.name) return node.name.getStart(sourceFile);
|
|
455
1093
|
const parent = node.parent;
|
|
456
|
-
if ((
|
|
1094
|
+
if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
|
|
457
1095
|
return parent.name.getStart(sourceFile);
|
|
458
1096
|
}
|
|
459
1097
|
return node.getStart(sourceFile);
|
|
460
1098
|
};
|
|
461
1099
|
var bindingValue = (name, sourceFile) => {
|
|
462
|
-
if (
|
|
463
|
-
if (
|
|
1100
|
+
if (ts3.isIdentifier(name)) return name.text;
|
|
1101
|
+
if (ts3.isArrayBindingPattern(name)) {
|
|
464
1102
|
const values = name.elements.map((element) => {
|
|
465
|
-
if (
|
|
1103
|
+
if (ts3.isOmittedExpression(element)) return "";
|
|
466
1104
|
const value = bindingValue(element.name, sourceFile);
|
|
467
1105
|
return element.dotDotDotToken ? `...${value}` : value;
|
|
468
1106
|
});
|
|
@@ -478,14 +1116,14 @@ var bindingValue = (name, sourceFile) => {
|
|
|
478
1116
|
};
|
|
479
1117
|
var arrowArguments = (node) => {
|
|
480
1118
|
const values = node.parameters.flatMap((parameter) => {
|
|
481
|
-
if (
|
|
1119
|
+
if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
|
|
482
1120
|
const value = bindingValue(parameter.name, node.getSourceFile());
|
|
483
1121
|
return [parameter.dotDotDotToken ? `...${value}` : value];
|
|
484
1122
|
});
|
|
485
1123
|
return `[${values.join(",")}]`;
|
|
486
1124
|
};
|
|
487
1125
|
var isSupportedFunction = (node) => {
|
|
488
|
-
if (!
|
|
1126
|
+
if (!ts3.isFunctionDeclaration(node) && !ts3.isFunctionExpression(node) && !ts3.isArrowFunction(node) && !ts3.isMethodDeclaration(node) && !ts3.isGetAccessorDeclaration(node) && !ts3.isSetAccessorDeclaration(node)) {
|
|
489
1127
|
return false;
|
|
490
1128
|
}
|
|
491
1129
|
if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
|
|
@@ -496,7 +1134,7 @@ var uniqueName = (source, base) => {
|
|
|
496
1134
|
while (source.includes(name)) name += "_";
|
|
497
1135
|
return name;
|
|
498
1136
|
};
|
|
499
|
-
var isDirectivePrologue = (statement) =>
|
|
1137
|
+
var isDirectivePrologue = (statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression);
|
|
500
1138
|
var insertPositionAfterPrologue = (source, sourceFile) => {
|
|
501
1139
|
const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
|
|
502
1140
|
let end = shebangEnd;
|
|
@@ -511,7 +1149,7 @@ var preambleInsert = (source, position, preamble) => {
|
|
|
511
1149
|
const previous = source[position - 1];
|
|
512
1150
|
return previous === ";" || previous === "\n" || previous === "\r" ? preamble : `;${preamble}`;
|
|
513
1151
|
};
|
|
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)}));`;
|
|
1152
|
+
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
1153
|
var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
516
1154
|
const start = mappedLocation(
|
|
517
1155
|
sourceFile,
|
|
@@ -542,80 +1180,127 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
|
542
1180
|
paramNames
|
|
543
1181
|
};
|
|
544
1182
|
};
|
|
1183
|
+
var canOmitCallValues = (analysis, analyzed) => {
|
|
1184
|
+
if (!functionIsExact(analysis.plan) || analysis.hasThrow || analysis.hasCoercionGuard) {
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
const callers = analyzed.filter(
|
|
1188
|
+
(caller) => caller.callEdges.some((edge) => edge.calleeFunctionId === analysis.functionId)
|
|
1189
|
+
);
|
|
1190
|
+
if (callers.length === 0) return false;
|
|
1191
|
+
return callers.every((caller) => caller.plan.fallbackReason === void 0 && !caller.hasThrow);
|
|
1192
|
+
};
|
|
545
1193
|
var transformSource = (source, options) => {
|
|
546
1194
|
if (source.includes("@rasputin-ignore-file")) return void 0;
|
|
547
|
-
const sourceFile =
|
|
1195
|
+
const sourceFile = ts3.createSourceFile(
|
|
548
1196
|
options.filePath,
|
|
549
1197
|
source,
|
|
550
|
-
|
|
1198
|
+
ts3.ScriptTarget.Latest,
|
|
551
1199
|
true,
|
|
552
1200
|
scriptKindFor(options.filePath)
|
|
553
1201
|
);
|
|
554
1202
|
const runName = uniqueName(source, "__rasputinRun__");
|
|
1203
|
+
const capName = uniqueName(source, "__rasputinCap__");
|
|
555
1204
|
const plansName = uniqueName(source, "__rasputinPlans__");
|
|
556
1205
|
const absolutePath = resolve4(options.filePath);
|
|
1206
|
+
const derivationMode = options.derivationMode ?? "analysis";
|
|
557
1207
|
const edits = [];
|
|
558
1208
|
let order = 0;
|
|
559
1209
|
const delta = {
|
|
560
1210
|
functions: [],
|
|
561
1211
|
callSites: [],
|
|
562
|
-
destructures: []
|
|
1212
|
+
destructures: [],
|
|
1213
|
+
derivationPlans: []
|
|
563
1214
|
};
|
|
564
1215
|
const collectContext = {
|
|
565
1216
|
sourceFile,
|
|
566
1217
|
mapLocation: options.mapLocation,
|
|
567
1218
|
absolutePath
|
|
568
1219
|
};
|
|
1220
|
+
const pending = [];
|
|
569
1221
|
const visit = (node, caller) => {
|
|
570
1222
|
if (isSupportedFunction(node)) {
|
|
571
1223
|
const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
|
|
572
1224
|
if (!collected) {
|
|
573
|
-
|
|
1225
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
574
1226
|
return;
|
|
575
1227
|
}
|
|
576
|
-
|
|
1228
|
+
pending.push({ node, collected });
|
|
577
1229
|
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));
|
|
1230
|
+
ts3.forEachChild(node, (child) => visit(child, collected));
|
|
598
1231
|
return;
|
|
599
1232
|
}
|
|
600
|
-
if (caller && (
|
|
1233
|
+
if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
|
|
601
1234
|
const site = collectCallSite(node, caller, collectContext);
|
|
602
1235
|
if (site) delta.callSites.push(site);
|
|
603
1236
|
}
|
|
604
|
-
if (caller &&
|
|
1237
|
+
if (caller && ts3.isVariableDeclaration(node)) {
|
|
605
1238
|
const destructure = collectParamDestructure(node, caller, collectContext);
|
|
606
1239
|
if (destructure) delta.destructures.push(destructure);
|
|
607
1240
|
}
|
|
608
|
-
|
|
1241
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
609
1242
|
};
|
|
610
1243
|
visit(sourceFile);
|
|
611
|
-
if (
|
|
1244
|
+
if (pending.length === 0) return void 0;
|
|
1245
|
+
const analyzed = analyzeModuleFunctions(
|
|
1246
|
+
sourceFile,
|
|
1247
|
+
pending.map((entry) => ({
|
|
1248
|
+
functionId: entry.collected.functionId,
|
|
1249
|
+
name: entry.collected.name,
|
|
1250
|
+
node: entry.node
|
|
1251
|
+
}))
|
|
1252
|
+
);
|
|
1253
|
+
delta.derivationPlans = analyzed.map((fn) => fn.plan);
|
|
1254
|
+
const analyzedById = new Map(analyzed.map((fn) => [fn.functionId, fn]));
|
|
1255
|
+
for (const [planIndex, entry] of pending.entries()) {
|
|
1256
|
+
const analysis = analyzedById.get(entry.collected.functionId);
|
|
1257
|
+
const omitValues = derivationMode === "selective" && analysis && canOmitCallValues(analysis, analyzed);
|
|
1258
|
+
const valueFlag = omitValues ? ",1" : "";
|
|
1259
|
+
const args2 = ts3.isArrowFunction(entry.node) ? arrowArguments(entry.node) : "arguments";
|
|
1260
|
+
const asyncCallback = entry.node.modifiers?.some(
|
|
1261
|
+
(modifier) => modifier.kind === ts3.SyntaxKind.AsyncKeyword
|
|
1262
|
+
) ? "async " : "";
|
|
1263
|
+
if (ts3.isBlock(entry.node.body)) {
|
|
1264
|
+
edits.push({
|
|
1265
|
+
position: entry.node.body.getStart(sourceFile) + 1,
|
|
1266
|
+
text: `return ${runName}(${planIndex},${args2},${asyncCallback}()=>{`,
|
|
1267
|
+
order: order++
|
|
1268
|
+
});
|
|
1269
|
+
edits.push({
|
|
1270
|
+
position: entry.node.body.end - 1,
|
|
1271
|
+
text: `}${valueFlag});`,
|
|
1272
|
+
order: order++
|
|
1273
|
+
});
|
|
1274
|
+
} else {
|
|
1275
|
+
edits.push({
|
|
1276
|
+
position: entry.node.body.getStart(sourceFile),
|
|
1277
|
+
text: `${runName}(${planIndex},${args2},${asyncCallback}()=>(`,
|
|
1278
|
+
order: order++
|
|
1279
|
+
});
|
|
1280
|
+
edits.push({
|
|
1281
|
+
position: entry.node.body.end,
|
|
1282
|
+
text: `)${valueFlag})`,
|
|
1283
|
+
order: order++
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
if (analysis) {
|
|
1287
|
+
for (const edit of captureWrapEdits(analysis.captureWraps, capName)) {
|
|
1288
|
+
edits.push({ ...edit, order: order++ });
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
612
1292
|
const insertAt = insertPositionAfterPrologue(source, sourceFile);
|
|
613
1293
|
edits.push({
|
|
614
1294
|
position: insertAt,
|
|
615
1295
|
text: preambleInsert(
|
|
616
1296
|
source,
|
|
617
1297
|
insertAt,
|
|
618
|
-
modulePreamble(
|
|
1298
|
+
modulePreamble(
|
|
1299
|
+
runName,
|
|
1300
|
+
capName,
|
|
1301
|
+
plansName,
|
|
1302
|
+
JSON.stringify(delta.functions.map((fn) => fn.plan))
|
|
1303
|
+
)
|
|
619
1304
|
),
|
|
620
1305
|
order: -1
|
|
621
1306
|
});
|
|
@@ -667,7 +1352,8 @@ var instrumentCompiledDirectory = (options) => {
|
|
|
667
1352
|
if (source.includes("__rasputinRun__") || source.includes("__rasputinPlans__")) continue;
|
|
668
1353
|
const transformed = transformSource(source, {
|
|
669
1354
|
filePath,
|
|
670
|
-
mapLocation: createSourceMapLocationMapper(filePath, source)
|
|
1355
|
+
mapLocation: createSourceMapLocationMapper(filePath, source),
|
|
1356
|
+
...options.derivationMode ? { derivationMode: options.derivationMode } : {}
|
|
671
1357
|
});
|
|
672
1358
|
if (!transformed) continue;
|
|
673
1359
|
const outputPath = join2(outputDir, relative2(inputDir, filePath));
|
|
@@ -693,7 +1379,15 @@ var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve6(dirname3(fileURLT
|
|
|
693
1379
|
|
|
694
1380
|
// src/instrument/build.ts
|
|
695
1381
|
var printUsage = () => {
|
|
696
|
-
console.error(
|
|
1382
|
+
console.error(
|
|
1383
|
+
"Usage: rasputin-instrument <input-dir> [--out <output-dir>] [--manifest <path>] [--derivation-mode analysis|selective]"
|
|
1384
|
+
);
|
|
1385
|
+
};
|
|
1386
|
+
var parseDerivationMode = (value) => {
|
|
1387
|
+
if (value === void 0) return void 0;
|
|
1388
|
+
if (value === "analysis" || value === "selective") return value;
|
|
1389
|
+
console.error(`[rasputin] unknown --derivation-mode ${value} (expected analysis or selective)`);
|
|
1390
|
+
process.exit(1);
|
|
697
1391
|
};
|
|
698
1392
|
var parseArgs = (argv) => {
|
|
699
1393
|
const inputDir = argv.find((argument) => !argument.startsWith("--"));
|
|
@@ -704,7 +1398,8 @@ var parseArgs = (argv) => {
|
|
|
704
1398
|
return {
|
|
705
1399
|
inputDir,
|
|
706
1400
|
outputDir: option("--out"),
|
|
707
|
-
manifestPath: option("--manifest")
|
|
1401
|
+
manifestPath: option("--manifest"),
|
|
1402
|
+
derivationMode: parseDerivationMode(option("--derivation-mode"))
|
|
708
1403
|
};
|
|
709
1404
|
};
|
|
710
1405
|
var args = parseArgs(process.argv.slice(2));
|
|
@@ -716,7 +1411,8 @@ var result = instrumentCompiledDirectory({
|
|
|
716
1411
|
inputDir: args.inputDir,
|
|
717
1412
|
outputDir: args.outputDir,
|
|
718
1413
|
manifestPath: args.manifestPath,
|
|
719
|
-
excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)]
|
|
1414
|
+
excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)],
|
|
1415
|
+
...args.derivationMode ? { derivationMode: args.derivationMode } : {}
|
|
720
1416
|
});
|
|
721
1417
|
console.info(
|
|
722
1418
|
`[rasputin] instrumented ${result.transformedFiles.length} file${result.transformedFiles.length === 1 ? "" : "s"}`
|