@tracecode/harness 0.4.0 → 0.5.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/browser.cjs +16 -4
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.cts +2 -2
  5. package/dist/browser.d.ts +2 -2
  6. package/dist/browser.js +16 -4
  7. package/dist/browser.js.map +1 -1
  8. package/dist/cli.js +0 -0
  9. package/dist/core.cjs +16 -4
  10. package/dist/core.cjs.map +1 -1
  11. package/dist/core.d.cts +9 -6
  12. package/dist/core.d.ts +9 -6
  13. package/dist/core.js +16 -4
  14. package/dist/core.js.map +1 -1
  15. package/dist/index.cjs +305 -13
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +2 -2
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +305 -13
  20. package/dist/index.js.map +1 -1
  21. package/dist/internal/browser.d.cts +1 -1
  22. package/dist/internal/browser.d.ts +1 -1
  23. package/dist/javascript.cjs +227 -9
  24. package/dist/javascript.cjs.map +1 -1
  25. package/dist/javascript.d.cts +4 -4
  26. package/dist/javascript.d.ts +4 -4
  27. package/dist/javascript.js +227 -9
  28. package/dist/javascript.js.map +1 -1
  29. package/dist/python.cjs +62 -0
  30. package/dist/python.cjs.map +1 -1
  31. package/dist/python.d.cts +2 -2
  32. package/dist/python.d.ts +2 -2
  33. package/dist/python.js +62 -0
  34. package/dist/python.js.map +1 -1
  35. package/dist/{runtime-types-Dvgn07z9.d.cts → runtime-types--lBQ6rYu.d.cts} +1 -1
  36. package/dist/{runtime-types-C7d1LFbx.d.ts → runtime-types-DtaaAhHL.d.ts} +1 -1
  37. package/dist/{types-Bzr1Ohcf.d.cts → types-DwIYM3Ku.d.cts} +5 -2
  38. package/dist/{types-Bzr1Ohcf.d.ts → types-DwIYM3Ku.d.ts} +5 -2
  39. package/package.json +12 -10
  40. package/workers/javascript/javascript-worker.js +455 -31
  41. package/workers/python/generated-python-harness-snippets.js +1 -1
  42. package/workers/python/pyodide-worker.js +31 -0
  43. package/workers/python/runtime-core.js +235 -8
@@ -141,6 +141,17 @@ function isLikelyListNodeValue(value) {
141
141
  return hasValue && hasListLinks && !hasTreeLinks;
142
142
  }
143
143
 
144
+ function getCustomClassName(value) {
145
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
146
+ if (value instanceof Map || value instanceof Set) return null;
147
+ if (isLikelyTreeNodeValue(value) || isLikelyListNodeValue(value)) return null;
148
+ const name = typeof value?.constructor?.name === 'string' ? value.constructor.name : '';
149
+ if (!name || name === 'Object' || name === 'Array' || name === 'Map' || name === 'Set') {
150
+ return null;
151
+ }
152
+ return name;
153
+ }
154
+
144
155
  function serializeValue(
145
156
  value,
146
157
  depth = 0,
@@ -209,6 +220,30 @@ function serializeValue(
209
220
  };
210
221
  }
211
222
 
223
+ const customClassName = getCustomClassName(value);
224
+ if (customClassName) {
225
+ const existingId = nodeRefState.ids.get(value);
226
+ if (existingId) {
227
+ return { __ref__: existingId };
228
+ }
229
+
230
+ const objectId = `object-${nodeRefState.nextId++}`;
231
+ nodeRefState.ids.set(value, objectId);
232
+
233
+ if (seen.has(value)) return { __ref__: objectId };
234
+ seen.add(value);
235
+ const out = {
236
+ __type__: 'object',
237
+ __class__: customClassName,
238
+ __id__: objectId,
239
+ };
240
+ for (const [k, v] of Object.entries(value)) {
241
+ out[k] = serializeValue(v, depth + 1, seen, nodeRefState);
242
+ }
243
+ seen.delete(value);
244
+ return out;
245
+ }
246
+
212
247
  if (seen.has(value)) return '<cycle>';
213
248
  seen.add(value);
214
249
  const out = {};
@@ -222,6 +257,67 @@ function serializeValue(
222
257
  return String(value);
223
258
  }
224
259
 
260
+ function serializeTopLevelValue(value, nodeRefState) {
261
+ if (value === null || value === undefined) return value;
262
+ if (typeof value !== 'object' || Array.isArray(value)) {
263
+ return serializeValue(value, 0, new WeakSet(), nodeRefState);
264
+ }
265
+
266
+ if (isLikelyTreeNodeValue(value) || isLikelyListNodeValue(value)) {
267
+ const objectValue = value;
268
+ const nodeValue = value;
269
+ const isTree = isLikelyTreeNodeValue(value);
270
+ let nodeId = nodeRefState.ids.get(objectValue);
271
+ if (!nodeId) {
272
+ nodeId = `${isTree ? 'tree' : 'list'}-${nodeRefState.nextId++}`;
273
+ nodeRefState.ids.set(objectValue, nodeId);
274
+ }
275
+
276
+ if (isTree) {
277
+ return {
278
+ __type__: 'TreeNode',
279
+ __id__: nodeId,
280
+ val: serializeValue(nodeValue.val ?? nodeValue.value ?? null, 1, new WeakSet(), nodeRefState),
281
+ left: serializeValue(nodeValue.left ?? null, 1, new WeakSet(), nodeRefState),
282
+ right: serializeValue(nodeValue.right ?? null, 1, new WeakSet(), nodeRefState),
283
+ };
284
+ }
285
+
286
+ return {
287
+ __type__: 'ListNode',
288
+ __id__: nodeId,
289
+ val: serializeValue(nodeValue.val ?? nodeValue.value ?? null, 1, new WeakSet(), nodeRefState),
290
+ next: serializeValue(nodeValue.next ?? null, 1, new WeakSet(), nodeRefState),
291
+ ...('prev' in nodeValue
292
+ ? { prev: serializeValue(nodeValue.prev ?? null, 1, new WeakSet(), nodeRefState) }
293
+ : {}),
294
+ };
295
+ }
296
+
297
+ const customClassName = getCustomClassName(value);
298
+ if (customClassName) {
299
+ const objectValue = value;
300
+ let objectId = nodeRefState.ids.get(objectValue);
301
+ if (!objectId) {
302
+ objectId = `object-${nodeRefState.nextId++}`;
303
+ nodeRefState.ids.set(objectValue, objectId);
304
+ }
305
+ const seen = new WeakSet();
306
+ seen.add(objectValue);
307
+ const out = {
308
+ __type__: 'object',
309
+ __class__: customClassName,
310
+ __id__: objectId,
311
+ };
312
+ for (const [k, v] of Object.entries(value)) {
313
+ out[k] = serializeValue(v, 1, seen, nodeRefState);
314
+ }
315
+ return out;
316
+ }
317
+
318
+ return serializeValue(value, 0, new WeakSet(), nodeRefState);
319
+ }
320
+
225
321
  function extractUserErrorLine(error) {
226
322
  if (error && typeof error === 'object' && '__tracecodeLine' in error) {
227
323
  const line = Number(error.__tracecodeLine);
@@ -314,6 +410,182 @@ function normalizeInputs(inputs) {
314
410
  return hydrated;
315
411
  }
316
412
 
413
+ function buildTreeNodeFromLevelOrder(values) {
414
+ if (!Array.isArray(values) || values.length === 0) return null;
415
+ const firstValue = values[0];
416
+ if (firstValue === null || firstValue === undefined) return null;
417
+ const root = { val: firstValue, value: firstValue, left: null, right: null };
418
+ const queue = [root];
419
+ let index = 1;
420
+
421
+ while (queue.length > 0 && index < values.length) {
422
+ const node = queue.shift();
423
+ if (!node) break;
424
+
425
+ const leftValue = values[index++];
426
+ if (leftValue !== null && leftValue !== undefined) {
427
+ node.left = { val: leftValue, value: leftValue, left: null, right: null };
428
+ queue.push(node.left);
429
+ }
430
+
431
+ if (index >= values.length) break;
432
+
433
+ const rightValue = values[index++];
434
+ if (rightValue !== null && rightValue !== undefined) {
435
+ node.right = { val: rightValue, value: rightValue, left: null, right: null };
436
+ queue.push(node.right);
437
+ }
438
+ }
439
+
440
+ return root;
441
+ }
442
+
443
+ function materializeTreeInput(value) {
444
+ if (value === null || value === undefined) return value;
445
+ if (Array.isArray(value)) {
446
+ return buildTreeNodeFromLevelOrder(value);
447
+ }
448
+ if (!isPlainObjectRecord(value)) {
449
+ return value;
450
+ }
451
+ if (isLikelyTreeNodeValue(value) || value.__type__ === 'TreeNode') {
452
+ const nodeValue = value;
453
+ return {
454
+ val: nodeValue.val ?? nodeValue.value ?? null,
455
+ value: nodeValue.val ?? nodeValue.value ?? null,
456
+ left: materializeTreeInput(nodeValue.left ?? null),
457
+ right: materializeTreeInput(nodeValue.right ?? null),
458
+ };
459
+ }
460
+ return value;
461
+ }
462
+
463
+ function materializeListInput(value, refs = new Map(), materialized = new WeakMap()) {
464
+ if (value === null || value === undefined) return value;
465
+ if (Array.isArray(value)) {
466
+ if (value.length === 0) return null;
467
+ const head = { val: value[0], value: value[0], next: null };
468
+ let current = head;
469
+ for (let i = 1; i < value.length; i++) {
470
+ current.next = { val: value[i], value: value[i], next: null };
471
+ current = current.next;
472
+ }
473
+ return head;
474
+ }
475
+ if (!isPlainObjectRecord(value)) {
476
+ return value;
477
+ }
478
+ if (typeof value.__ref__ === 'string') {
479
+ return refs.get(value.__ref__) ?? null;
480
+ }
481
+ if (isLikelyListNodeValue(value) || value.__type__ === 'ListNode') {
482
+ const existingMaterialized = materialized.get(value);
483
+ if (existingMaterialized) {
484
+ return existingMaterialized;
485
+ }
486
+ const node = {
487
+ val: value.val ?? value.value ?? null,
488
+ value: value.val ?? value.value ?? null,
489
+ next: null,
490
+ };
491
+ materialized.set(value, node);
492
+ if (typeof value.__id__ === 'string' && value.__id__.length > 0) {
493
+ refs.set(value.__id__, node);
494
+ }
495
+ node.next = materializeListInput(value.next ?? null, refs, materialized);
496
+ return node;
497
+ }
498
+ return value;
499
+ }
500
+
501
+ function detectMaterializerKind(ts, typeNode) {
502
+ if (!typeNode) return null;
503
+ if (ts.isParenthesizedTypeNode(typeNode)) {
504
+ return detectMaterializerKind(ts, typeNode.type);
505
+ }
506
+ if (ts.isUnionTypeNode(typeNode)) {
507
+ for (const child of typeNode.types) {
508
+ const resolved = detectMaterializerKind(ts, child);
509
+ if (resolved) return resolved;
510
+ }
511
+ return null;
512
+ }
513
+ if (ts.isTypeReferenceNode(typeNode)) {
514
+ const typeNameText = typeNode.typeName.getText();
515
+ if (typeNameText === 'TreeNode') return 'tree';
516
+ if (typeNameText === 'ListNode') return 'list';
517
+ return null;
518
+ }
519
+ return null;
520
+ }
521
+
522
+ function collectInputMaterializers(ts, functionLikeNode) {
523
+ const out = {};
524
+ for (const parameter of functionLikeNode.parameters ?? []) {
525
+ if (!ts.isIdentifier(parameter.name)) continue;
526
+ if (parameter.name.text === 'this') continue;
527
+ const kind = detectMaterializerKind(ts, parameter.type);
528
+ if (kind) {
529
+ out[parameter.name.text] = kind;
530
+ }
531
+ }
532
+ return out;
533
+ }
534
+
535
+ async function resolveInputMaterializers(code, functionName, executionStyle, language) {
536
+ if (!functionName || executionStyle === 'ops-class' || language !== 'typescript') {
537
+ return {};
538
+ }
539
+
540
+ try {
541
+ await ensureTypeScriptCompiler();
542
+ const ts = getTypeScriptCompiler();
543
+ if (!ts) return {};
544
+
545
+ const sourceFile = ts.createSourceFile(
546
+ 'runtime-input.ts',
547
+ code,
548
+ ts.ScriptTarget.ES2020,
549
+ true,
550
+ ts.ScriptKind.TS
551
+ );
552
+ const target = findFunctionLikeNode(ts, sourceFile, functionName, executionStyle);
553
+ if (!target) return {};
554
+ return collectInputMaterializers(ts, target);
555
+ } catch (_error) {
556
+ return {};
557
+ }
558
+ }
559
+
560
+ function applyInputMaterializers(inputs, materializers) {
561
+ const next = { ...inputs };
562
+ const combined = { ...inferFallbackInputMaterializers(inputs), ...(materializers ?? {}) };
563
+ if (Object.keys(combined).length === 0) return inputs;
564
+ for (const [name, kind] of Object.entries(combined)) {
565
+ if (!Object.prototype.hasOwnProperty.call(next, name)) continue;
566
+ next[name] = kind === 'tree' ? materializeTreeInput(next[name]) : materializeListInput(next[name]);
567
+ }
568
+ return next;
569
+ }
570
+
571
+ function inferFallbackInputMaterializers(inputs) {
572
+ if (!inputs || typeof inputs !== 'object' || Array.isArray(inputs)) return {};
573
+ const inferred = {};
574
+ for (const [name, value] of Object.entries(inputs)) {
575
+ const lowerName = String(name || '').toLowerCase();
576
+ if (!Array.isArray(value)) continue;
577
+ if (lowerName === 'root' || lowerName.endsWith('root') || lowerName.includes('tree')) {
578
+ inferred[name] = 'tree';
579
+ continue;
580
+ }
581
+ if (lowerName === 'head' || lowerName.endsWith('head') || lowerName.includes('list')) {
582
+ inferred[name] = 'list';
583
+ continue;
584
+ }
585
+ }
586
+ return inferred;
587
+ }
588
+
317
589
  function collectSimpleParameterNames(ts, functionLikeNode) {
318
590
  const names = [];
319
591
 
@@ -391,7 +663,7 @@ function findFunctionLikeNode(ts, sourceFile, functionName, executionStyle) {
391
663
  return found;
392
664
  }
393
665
 
394
- async function resolveOrderedInputKeys(code, functionName, inputs, executionStyle) {
666
+ async function resolveOrderedInputKeys(code, functionName, inputs, executionStyle, language = 'javascript') {
395
667
  const fallbackKeys = Object.keys(inputs ?? {});
396
668
  if (!functionName || executionStyle === 'ops-class' || fallbackKeys.length <= 1) {
397
669
  return fallbackKeys;
@@ -404,7 +676,13 @@ async function resolveOrderedInputKeys(code, functionName, inputs, executionStyl
404
676
  return fallbackKeys;
405
677
  }
406
678
 
407
- const sourceFile = ts.createSourceFile('runtime-input.js', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.JS);
679
+ const sourceFile = ts.createSourceFile(
680
+ `runtime-input.${language === 'typescript' ? 'ts' : 'js'}`,
681
+ code,
682
+ ts.ScriptTarget.ES2020,
683
+ true,
684
+ language === 'typescript' ? ts.ScriptKind.TS : ts.ScriptKind.JS
685
+ );
408
686
  const target = findFunctionLikeNode(ts, sourceFile, functionName, executionStyle);
409
687
  if (!target) {
410
688
  return fallbackKeys;
@@ -553,8 +831,13 @@ function createTraceRecorder(options = {}) {
553
831
  const result = {};
554
832
  for (const [key, variableValue] of Object.entries(value)) {
555
833
  if (variableValue === undefined) continue;
834
+ if (typeof variableValue === 'function') continue;
556
835
  try {
557
- result[key] = serializeValue(variableValue, 0, new WeakSet(), stableNodeRefState);
836
+ const refState =
837
+ key === 'this' && variableValue && typeof variableValue === 'object'
838
+ ? { ids: new Map(), nextId: 1 }
839
+ : stableNodeRefState;
840
+ result[key] = serializeTopLevelValue(variableValue, refState);
558
841
  } catch {
559
842
  // Skip variables that throw during serialization (e.g. transient proxy/getter failures).
560
843
  }
@@ -621,6 +904,7 @@ function createTraceRecorder(options = {}) {
621
904
  function shouldVisualizeObjectAsHashMap(name, value) {
622
905
  if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
623
906
  if (isLikelyTreeObject(value) || isLikelyLinkedListObject(value)) return false;
907
+ if (value.__type__ === 'object') return false;
624
908
  if (Object.keys(value).length === 1 && typeof value.__ref__ === 'string') return false;
625
909
  if (isLikelyAdjacencyListObject(value)) return false;
626
910
 
@@ -673,8 +957,53 @@ function createTraceRecorder(options = {}) {
673
957
  }
674
958
 
675
959
  if (variableValue && typeof variableValue === 'object' && !Array.isArray(variableValue)) {
960
+ const customClassName = getCustomClassName(variableValue);
961
+ if (customClassName) {
962
+ const serializedObject = serializeValue(variableValue);
963
+ objectKinds[name] = 'object';
964
+ visualizations.push({
965
+ name,
966
+ kind: 'object',
967
+ objectClassName: customClassName,
968
+ objectId:
969
+ serializedObject && typeof serializedObject === 'object' && typeof serializedObject.__id__ === 'string'
970
+ ? serializedObject.__id__
971
+ : undefined,
972
+ entries: Object.entries(serializedObject)
973
+ .filter(([key]) => key !== '__type__' && key !== '__class__' && key !== '__id__')
974
+ .map(([key, entryValue]) => ({
975
+ key,
976
+ value: entryValue,
977
+ })),
978
+ });
979
+ continue;
980
+ }
981
+
676
982
  const serializedValue = variableValue;
677
983
 
984
+ if (serializedValue.__type__ === 'object') {
985
+ objectKinds[name] = 'object';
986
+ visualizations.push({
987
+ name,
988
+ kind: 'object',
989
+ objectClassName:
990
+ typeof serializedValue.__class__ === 'string' && serializedValue.__class__.length > 0
991
+ ? serializedValue.__class__
992
+ : undefined,
993
+ objectId:
994
+ typeof serializedValue.__id__ === 'string' && serializedValue.__id__.length > 0
995
+ ? serializedValue.__id__
996
+ : undefined,
997
+ entries: Object.entries(serializedValue)
998
+ .filter(([key]) => key !== '__type__' && key !== '__class__' && key !== '__id__')
999
+ .map(([key, entryValue]) => ({
1000
+ key,
1001
+ value: entryValue,
1002
+ })),
1003
+ });
1004
+ continue;
1005
+ }
1006
+
678
1007
  if (serializedValue.__type__ === 'map' && Array.isArray(serializedValue.entries)) {
679
1008
  objectKinds[name] = 'map';
680
1009
  visualizations.push({
@@ -756,6 +1085,15 @@ function createTraceRecorder(options = {}) {
756
1085
  return callStack[callStack.length - 1]?.id;
757
1086
  }
758
1087
 
1088
+ function normalizeFrameMatchKey(functionName) {
1089
+ const normalized =
1090
+ typeof functionName === 'string' && functionName.length > 0 ? functionName : '<module>';
1091
+ if (normalized === 'constructor' || normalized.endsWith('.constructor')) {
1092
+ return 'constructor';
1093
+ }
1094
+ return normalized;
1095
+ }
1096
+
759
1097
  function flushPendingAccesses(frameId) {
760
1098
  if (frameId === undefined || frameId === null) {
761
1099
  return undefined;
@@ -852,8 +1190,49 @@ function createTraceRecorder(options = {}) {
852
1190
  return '<module>';
853
1191
  }
854
1192
 
1193
+ const normalizedMatchKey = normalizeFrameMatchKey(normalizedFunctionName);
1194
+ let matchingFrameIndex = -1;
1195
+ for (let index = callStack.length - 1; index >= 0; index -= 1) {
1196
+ const frame = callStack[index];
1197
+ if (!frame) continue;
1198
+ if (
1199
+ frame.function === normalizedFunctionName ||
1200
+ normalizeFrameMatchKey(frame.function) === normalizedMatchKey
1201
+ ) {
1202
+ matchingFrameIndex = index;
1203
+ break;
1204
+ }
1205
+ }
1206
+
1207
+ if (matchingFrameIndex >= 0) {
1208
+ while (callStack.length - 1 > matchingFrameIndex) {
1209
+ const frame = callStack.pop();
1210
+ if (frame?.id !== undefined) {
1211
+ pendingAccessesByFrame.delete(frame.id);
1212
+ }
1213
+ }
1214
+ }
1215
+
855
1216
  const topFrame = callStack[callStack.length - 1];
856
- if (!topFrame || topFrame.function !== normalizedFunctionName) {
1217
+ if (topFrame && normalizeFrameMatchKey(topFrame.function) === normalizedMatchKey) {
1218
+ if (
1219
+ normalizedMatchKey === 'constructor' &&
1220
+ topFrame.function !== normalizedFunctionName &&
1221
+ normalizedFunctionName.endsWith('.constructor')
1222
+ ) {
1223
+ topFrame.function = normalizedFunctionName;
1224
+ }
1225
+ if (
1226
+ Object.keys(topFrame.args ?? {}).length === 0 &&
1227
+ inferredArgs &&
1228
+ typeof inferredArgs === 'object'
1229
+ ) {
1230
+ topFrame.args = sanitizeVariables(inferredArgs);
1231
+ }
1232
+ return normalizedFunctionName;
1233
+ }
1234
+
1235
+ if (!topFrame) {
857
1236
  const callLine = normalizeLine(functionStartLine, lineNumber);
858
1237
  const inferredFrame = {
859
1238
  id: nextFrameId++,
@@ -870,13 +1249,7 @@ function createTraceRecorder(options = {}) {
870
1249
  callStack: snapshotCallStack(),
871
1250
  visualization: buildVisualizationPayload(inferredArgs),
872
1251
  });
873
- } else if (
874
- topFrame.function === normalizedFunctionName &&
875
- Object.keys(topFrame.args ?? {}).length === 0 &&
876
- inferredArgs &&
877
- typeof inferredArgs === 'object'
878
- ) {
879
- topFrame.args = sanitizeVariables(inferredArgs);
1252
+ return normalizedFunctionName;
880
1253
  }
881
1254
 
882
1255
  return normalizedFunctionName;
@@ -1381,9 +1754,16 @@ function inferTraceFunctionName(ts, node, fallbackFunctionName) {
1381
1754
  }
1382
1755
 
1383
1756
  if (ts.isConstructorDeclaration(node)) {
1757
+ const originalNode = ts.getOriginalNode(node);
1758
+ const classLikeParent =
1759
+ node.parent && ts.isClassLike(node.parent)
1760
+ ? node.parent
1761
+ : originalNode?.parent && ts.isClassLike(originalNode.parent)
1762
+ ? originalNode.parent
1763
+ : null;
1384
1764
  const className =
1385
- node.parent && ts.isClassLike(node.parent) && node.parent.name && ts.isIdentifier(node.parent.name)
1386
- ? node.parent.name.text
1765
+ classLikeParent && classLikeParent.name && ts.isIdentifier(classLikeParent.name)
1766
+ ? classLikeParent.name.text
1387
1767
  : null;
1388
1768
  return className ? `${className}.constructor` : 'constructor';
1389
1769
  }
@@ -1407,53 +1787,57 @@ function inferTraceFunctionName(ts, node, fallbackFunctionName) {
1407
1787
  function buildLineFunctionMap(ts, sourceFile, defaultFunctionName) {
1408
1788
  const lineFunctionMap = new Map();
1409
1789
 
1410
- function mapStatementLine(statementNode, functionName, functionStartLine) {
1790
+ function mapStatementLine(statementNode, functionName, functionStartLine, includeThisSnapshot) {
1411
1791
  const lineNumber = sourceFile.getLineAndCharacterOfPosition(statementNode.getStart(sourceFile)).line + 1;
1412
1792
  if (!lineFunctionMap.has(lineNumber)) {
1413
1793
  lineFunctionMap.set(lineNumber, {
1414
1794
  functionName,
1415
1795
  functionStartLine,
1796
+ includeThisSnapshot,
1416
1797
  });
1417
1798
  }
1418
1799
  }
1419
1800
 
1420
- function visitNode(node, currentFunctionName, currentFunctionStartLine) {
1801
+ function visitNode(node, currentFunctionName, currentFunctionStartLine, includeThisSnapshot) {
1421
1802
  const nextFunctionName = ts.isFunctionLike(node)
1422
1803
  ? inferTraceFunctionName(ts, node, currentFunctionName)
1423
1804
  : currentFunctionName;
1424
1805
  const nextFunctionStartLine = ts.isFunctionLike(node)
1425
1806
  ? sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1
1426
1807
  : currentFunctionStartLine;
1808
+ const nextIncludeThisSnapshot = ts.isFunctionLike(node)
1809
+ ? functionLikeSnapshotsReceiver(ts, node)
1810
+ : includeThisSnapshot;
1427
1811
 
1428
1812
  if (ts.isSourceFile(node) || ts.isBlock(node)) {
1429
1813
  for (const statement of node.statements) {
1430
- visitNode(statement, currentFunctionName, currentFunctionStartLine);
1814
+ visitNode(statement, currentFunctionName, currentFunctionStartLine, includeThisSnapshot);
1431
1815
  }
1432
1816
  return;
1433
1817
  }
1434
1818
 
1435
1819
  if (ts.isCaseClause(node) || ts.isDefaultClause(node)) {
1436
1820
  for (const statement of node.statements) {
1437
- visitNode(statement, currentFunctionName, currentFunctionStartLine);
1821
+ visitNode(statement, currentFunctionName, currentFunctionStartLine, includeThisSnapshot);
1438
1822
  }
1439
1823
  return;
1440
1824
  }
1441
1825
 
1442
1826
  if (ts.isFunctionLike(node)) {
1443
1827
  if (node.body) {
1444
- visitNode(node.body, nextFunctionName, nextFunctionStartLine);
1828
+ visitNode(node.body, nextFunctionName, nextFunctionStartLine, nextIncludeThisSnapshot);
1445
1829
  }
1446
1830
  return;
1447
1831
  }
1448
1832
 
1449
1833
  if (ts.isStatement(node) && shouldTraceStatement(ts, node)) {
1450
- mapStatementLine(node, currentFunctionName, currentFunctionStartLine);
1834
+ mapStatementLine(node, currentFunctionName, currentFunctionStartLine, includeThisSnapshot);
1451
1835
  }
1452
1836
 
1453
- ts.forEachChild(node, (child) => visitNode(child, currentFunctionName, currentFunctionStartLine));
1837
+ ts.forEachChild(node, (child) => visitNode(child, currentFunctionName, currentFunctionStartLine, includeThisSnapshot));
1454
1838
  }
1455
1839
 
1456
- visitNode(sourceFile, defaultFunctionName, 1);
1840
+ visitNode(sourceFile, defaultFunctionName, 1, false);
1457
1841
  return lineFunctionMap;
1458
1842
  }
1459
1843
 
@@ -1657,7 +2041,7 @@ function createTraceMutatingCallExpression(ts, variableName, methodName, args) {
1657
2041
  );
1658
2042
  }
1659
2043
 
1660
- function createSnapshotFactory(ts, variableNames) {
2044
+ function createSnapshotFactory(ts, variableNames, includeThis = false) {
1661
2045
  const properties = variableNames.map((name) =>
1662
2046
  ts.factory.createPropertyAssignment(
1663
2047
  ts.factory.createIdentifier(name),
@@ -1681,6 +2065,31 @@ function createSnapshotFactory(ts, variableNames) {
1681
2065
  )
1682
2066
  );
1683
2067
 
2068
+ if (includeThis) {
2069
+ properties.push(
2070
+ ts.factory.createPropertyAssignment(
2071
+ ts.factory.createStringLiteral('this'),
2072
+ ts.factory.createCallExpression(
2073
+ ts.factory.createPropertyAccessExpression(
2074
+ ts.factory.createIdentifier('__traceRecorder'),
2075
+ ts.factory.createIdentifier('read')
2076
+ ),
2077
+ undefined,
2078
+ [
2079
+ ts.factory.createArrowFunction(
2080
+ undefined,
2081
+ undefined,
2082
+ [],
2083
+ undefined,
2084
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
2085
+ ts.factory.createThis()
2086
+ ),
2087
+ ]
2088
+ )
2089
+ )
2090
+ );
2091
+ }
2092
+
1684
2093
  return ts.factory.createArrowFunction(
1685
2094
  undefined,
1686
2095
  undefined,
@@ -1693,11 +2102,21 @@ function createSnapshotFactory(ts, variableNames) {
1693
2102
  );
1694
2103
  }
1695
2104
 
2105
+ function functionLikeSnapshotsReceiver(ts, functionLikeNode) {
2106
+ return (
2107
+ ts.isMethodDeclaration(functionLikeNode) ||
2108
+ ts.isConstructorDeclaration(functionLikeNode) ||
2109
+ ts.isGetAccessorDeclaration(functionLikeNode) ||
2110
+ ts.isSetAccessorDeclaration(functionLikeNode)
2111
+ );
2112
+ }
2113
+
1696
2114
  function createTraceLineStatement(ts, sourceFile, statement, variableNames, lineFunctionMap, defaultFunctionName) {
1697
2115
  const lineNumber = sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile)).line + 1;
1698
2116
  const functionContext = lineFunctionMap.get(lineNumber);
1699
2117
  const traceFunctionName = functionContext?.functionName ?? defaultFunctionName;
1700
2118
  const traceFunctionStartLine = functionContext?.functionStartLine ?? lineNumber;
2119
+ const includeThisSnapshot = Boolean(functionContext?.includeThisSnapshot);
1701
2120
  return ts.factory.createExpressionStatement(
1702
2121
  ts.factory.createCallExpression(
1703
2122
  ts.factory.createPropertyAccessExpression(
@@ -1707,7 +2126,7 @@ function createTraceLineStatement(ts, sourceFile, statement, variableNames, line
1707
2126
  undefined,
1708
2127
  [
1709
2128
  ts.factory.createNumericLiteral(lineNumber),
1710
- createSnapshotFactory(ts, variableNames),
2129
+ createSnapshotFactory(ts, variableNames, includeThisSnapshot),
1711
2130
  ts.factory.createStringLiteral(traceFunctionName),
1712
2131
  ts.factory.createNumericLiteral(traceFunctionStartLine),
1713
2132
  ]
@@ -1917,6 +2336,7 @@ function wrapFunctionBodyForTracing(
1917
2336
  const functionEndPosition = Math.max(functionBody.getEnd() - 1, functionBody.getStart(sourceFile));
1918
2337
  const functionEndLine = sourceFile.getLineAndCharacterOfPosition(functionEndPosition).line + 1;
1919
2338
  const parameterNames = collectFunctionParameterNames(ts, functionLikeNode);
2339
+ const includeThisSnapshot = functionLikeSnapshotsReceiver(ts, functionLikeNode);
1920
2340
 
1921
2341
  const rewrittenBody = rewriteFunctionReturnStatements(
1922
2342
  ts,
@@ -1927,7 +2347,7 @@ function wrapFunctionBodyForTracing(
1927
2347
  );
1928
2348
 
1929
2349
  const argsSnapshotExpression = ts.factory.createCallExpression(
1930
- ts.factory.createParenthesizedExpression(createSnapshotFactory(ts, parameterNames)),
2350
+ ts.factory.createParenthesizedExpression(createSnapshotFactory(ts, parameterNames, includeThisSnapshot)),
1931
2351
  undefined,
1932
2352
  []
1933
2353
  );
@@ -2554,6 +2974,8 @@ async function executeCode(payload) {
2554
2974
  const consoleOutput = [];
2555
2975
  const consoleProxy = createConsoleProxy(consoleOutput);
2556
2976
  const normalizedInputs = normalizeInputs(inputs);
2977
+ const materializers = await resolveInputMaterializers(code, functionName, executionStyle, language);
2978
+ const materializedInputs = applyInputMaterializers(normalizedInputs, materializers);
2557
2979
 
2558
2980
  try {
2559
2981
  if (typeof code !== 'string') {
@@ -2569,13 +2991,13 @@ async function executeCode(payload) {
2569
2991
 
2570
2992
  if (hasNamedFunction) {
2571
2993
  if (executionStyle === 'ops-class') {
2572
- const { operations, argumentsList } = getOpsClassInputs(normalizedInputs);
2994
+ const { operations, argumentsList } = getOpsClassInputs(materializedInputs);
2573
2995
  const runner = buildFunctionExecutionRunner(executableCode, executionStyle, []);
2574
2996
  output = await Promise.resolve(runner(consoleProxy, functionName, operations, argumentsList));
2575
2997
  } else {
2576
- const inputKeys = await resolveOrderedInputKeys(executableCode, functionName, normalizedInputs, executionStyle);
2998
+ const inputKeys = await resolveOrderedInputKeys(code, functionName, materializedInputs, executionStyle, language);
2577
2999
  const argNames = inputKeys.map((_, index) => `__arg${index}`);
2578
- const argValues = inputKeys.map((key) => normalizedInputs[key]);
3000
+ const argValues = inputKeys.map((key) => materializedInputs[key]);
2579
3001
  const runner = buildFunctionExecutionRunner(executableCode, executionStyle, argNames);
2580
3002
  output = await Promise.resolve(runner(consoleProxy, functionName, ...argValues));
2581
3003
  }
@@ -2617,6 +3039,8 @@ async function executeWithTracing(payload) {
2617
3039
  const consoleOutput = [];
2618
3040
  const consoleProxy = createConsoleProxy(consoleOutput);
2619
3041
  const normalizedInputs = normalizeInputs(inputs);
3042
+ const materializers = await resolveInputMaterializers(code, functionName, executionStyle, language);
3043
+ const materializedInputs = applyInputMaterializers(normalizedInputs, materializers);
2620
3044
  const hasNamedFunction = typeof functionName === 'string' && functionName.length > 0;
2621
3045
  const traceFunctionName = hasNamedFunction ? functionName : '<module>';
2622
3046
  const traceRecorder = createTraceRecorder(options);
@@ -2680,14 +3104,14 @@ async function executeWithTracing(payload) {
2680
3104
  }
2681
3105
 
2682
3106
  const serializedInputs = {};
2683
- for (const [key, value] of Object.entries(normalizedInputs)) {
3107
+ for (const [key, value] of Object.entries(materializedInputs)) {
2684
3108
  serializedInputs[key] = serializeValue(value);
2685
3109
  }
2686
3110
 
2687
3111
  let output;
2688
3112
  if (hasNamedFunction) {
2689
3113
  if (executionStyle === 'ops-class') {
2690
- const { operations, argumentsList } = getOpsClassInputs(normalizedInputs);
3114
+ const { operations, argumentsList } = getOpsClassInputs(materializedInputs);
2691
3115
  const runner = buildFunctionTracingRunner(instrumentedCode, executionStyle, []);
2692
3116
  output = await Promise.resolve(
2693
3117
  runner(
@@ -2700,9 +3124,9 @@ async function executeWithTracing(payload) {
2700
3124
  )
2701
3125
  );
2702
3126
  } else {
2703
- const inputKeys = await resolveOrderedInputKeys(executableCode, functionName, normalizedInputs, executionStyle);
3127
+ const inputKeys = await resolveOrderedInputKeys(code, functionName, materializedInputs, executionStyle, language);
2704
3128
  const argNames = inputKeys.map((_, index) => `__arg${index}`);
2705
- const argValues = inputKeys.map((key) => normalizedInputs[key]);
3129
+ const argValues = inputKeys.map((key) => materializedInputs[key]);
2706
3130
  const runner = buildFunctionTracingRunner(instrumentedCode, executionStyle, argNames);
2707
3131
  output = await Promise.resolve(
2708
3132
  runner(consoleProxy, traceRecorder, { functionName: traceFunctionName }, functionName, ...argValues)