@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.
- package/CHANGELOG.md +15 -0
- package/dist/browser.cjs +16 -4
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +16 -4
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +0 -0
- package/dist/core.cjs +16 -4
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +9 -6
- package/dist/core.d.ts +9 -6
- package/dist/core.js +16 -4
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +305 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +305 -13
- package/dist/index.js.map +1 -1
- package/dist/internal/browser.d.cts +1 -1
- package/dist/internal/browser.d.ts +1 -1
- package/dist/javascript.cjs +227 -9
- package/dist/javascript.cjs.map +1 -1
- package/dist/javascript.d.cts +4 -4
- package/dist/javascript.d.ts +4 -4
- package/dist/javascript.js +227 -9
- package/dist/javascript.js.map +1 -1
- package/dist/python.cjs +62 -0
- package/dist/python.cjs.map +1 -1
- package/dist/python.d.cts +2 -2
- package/dist/python.d.ts +2 -2
- package/dist/python.js +62 -0
- package/dist/python.js.map +1 -1
- package/dist/{runtime-types-Dvgn07z9.d.cts → runtime-types--lBQ6rYu.d.cts} +1 -1
- package/dist/{runtime-types-C7d1LFbx.d.ts → runtime-types-DtaaAhHL.d.ts} +1 -1
- package/dist/{types-Bzr1Ohcf.d.cts → types-DwIYM3Ku.d.cts} +5 -2
- package/dist/{types-Bzr1Ohcf.d.ts → types-DwIYM3Ku.d.ts} +5 -2
- package/package.json +12 -10
- package/workers/javascript/javascript-worker.js +455 -31
- package/workers/python/generated-python-harness-snippets.js +1 -1
- package/workers/python/pyodide-worker.js +31 -0
- 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(
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
1386
|
-
?
|
|
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(
|
|
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(
|
|
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) =>
|
|
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(
|
|
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(
|
|
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(
|
|
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) =>
|
|
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)
|