Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 82 additions & 19 deletions packages/plugins/live-debugger/src/transform/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,27 +294,90 @@ describe('transformCode', () => {
});

describe('nested functions', () => {
it('should instrument nested functions independently', () => {
const result = transformCode({
...BASE_OPTIONS,
const nestedSyntaxCases = [
{
description: 'nested function declarations',
code: 'function outer(a) { function inner(b) { return b; } return inner(a); }',
});

expect(result.instrumentedCount).toBe(2);
expect(result.totalFunctions).toBe(2);
});

it('should handle nested arrow expression bodies without conflicts', () => {
const result = transformCode({
...BASE_OPTIONS,
namedOnly: false,
expectedInstrumentedCount: 2,
expectedTotalFunctions: 2,
},
{
description: 'arrow returning named function expression',
code: 'const make = (dep) => function named() { return dep; };',
namedOnly: false,
expectedInstrumentedCount: 2,
expectedTotalFunctions: 2,
},
{
description: 'arrow returning named function expression',
code: 'const make = (dep) => function named() { return dep; };',
namedOnly: true,
expectedInstrumentedCount: 2,
expectedTotalFunctions: 2,
},
{
description: 'exported arrow returning function expression with inner declaration',
code: 'export const remarkSymbol = (o) => function remarkSymbolPlugin() { const d = o; function add(){ d; } add(); };',
namedOnly: false,
expectedInstrumentedCount: 3,
expectedTotalFunctions: 3,
},
{
description: 'exported arrow returning function expression with inner declaration',
code: 'export const remarkSymbol = (o) => function remarkSymbolPlugin() { const d = o; function add(){ d; } add(); };',
namedOnly: true,
expectedInstrumentedCount: 3,
expectedTotalFunctions: 3,
},
{
description: 'triple-nested function expression callback',
code: 'const build = (n) => function useThing() { return cb(function record(){ size(n); }, []); };',
namedOnly: false,
expectedInstrumentedCount: 3,
expectedTotalFunctions: 3,
},
{
description: 'triple-nested function expression callback',
code: 'const build = (n) => function useThing() { return cb(function record(){ size(n); }, []); };',
namedOnly: true,
expectedInstrumentedCount: 3,
expectedTotalFunctions: 3,
},
{
description: 'nested arrow expression bodies',
code: 'const f = (x) => (y) => x + y;',
});

expect(result.instrumentedCount).toBe(2);
// Both functions should be instrumented
const probeMatches = result.code.match(/\$dd_probes/g);
expect(probeMatches?.length).toBe(2);
});
namedOnly: false,
expectedInstrumentedCount: 2,
expectedTotalFunctions: 2,
},
{
description: 'curried arrow expression body with anonymous inner arrow skipped',
code: 'const f = (a) => (b) => a + b;',
namedOnly: true,
expectedInstrumentedCount: 1,
expectedTotalFunctions: 2,
},
];

test.each(nestedSyntaxCases)(
'should produce valid output for $description with namedOnly $namedOnly',
({ code, namedOnly, expectedInstrumentedCount, expectedTotalFunctions }) => {
const result = transformCode({
...BASE_OPTIONS,
code,
namedOnly,
});

const syntaxError = validateSyntax(result.code, '/src/utils.ts');
const probeMatches = result.code.match(/\$dd_probes/g);
expect(syntaxError).toBeNull();
expect(result.instrumentedCount).toBe(expectedInstrumentedCount);
expect(result.totalFunctions).toBe(expectedTotalFunctions);
expect(probeMatches?.length).toBe(expectedInstrumentedCount);
expect(result.code).toContain('catch(e)');
},
);
});

describe('skipping', () => {
Expand Down
7 changes: 4 additions & 3 deletions packages/plugins/live-debugger/src/transform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,16 +526,17 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio
].join('\n');

// Anchor each injected line to the original expression's location by
// editing the boundary chars of the body. Two updates avoid losing
// per-character mappings of the original expression in between.
// editing the first boundary char and appending after the body. This
// avoids overwriting nested function instrumentation when a curried
// arrow's expression body ends at the inner function's closing brace.
// For a single-char body (e.g. `() => 1`) the two ranges would
// collide, so we fall back to one update covering the whole body.
if (bodyEnd - bodyStart >= 2) {
const bodyPrefix = hasSequenceExpressionBody
? `${prefix}(${code[bodyStart]}`
: prefix + code[bodyStart];
s.update(bodyStart, bodyStart + 1, bodyPrefix);
s.update(bodyEnd - 1, bodyEnd, code[bodyEnd - 1] + suffix);
s.appendRight(bodyEnd, suffix);
} else {
s.update(bodyStart, bodyEnd, prefix + code.slice(bodyStart, bodyEnd) + suffix);
}
Expand Down
Loading