All files / shared / filter-stack.ts

50.00% Branches 4/8
100.00% Functions 2/2
77.27% Lines 17/22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 
 
 
 
 
 
x1
 
 
 
 
x3
x3
x3
x3
 
x90
x90
x90
 
x90
 
 
 
 
x90
x90
 
x3
 
x90
x90
x90
x90
x90


















I

I
I






I




// Strips runtime-internal frames from an Error's `.stack` so failing-test output
// shows the user's code first and qunitx's wrapper frames second, with no trailing
// pages of `node:internal/test_runner/...` and `node:async_hooks:...` lines.
//
// Filtered prefixes are stable Node-internal modules (their paths haven't
// changed across recent Node versions) and unambiguously not user code. We do
// NOT filter qunitx's own dist frames — they're exactly two short lines per
// failure and are useful when a bug actually lands in qunitx itself.
//
// QUNITX_DEBUG=1 disables filtering entirely, for the rare case where someone
// is debugging qunitx and wants the full V8 stack.
const FILTER_PATTERNS = [
  /\bnode:internal\/test_runner\//,
  /\bnode:async_hooks\b/,
];

function isDebugEnabled(): boolean {
  try {
    const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;
    if (proc?.env?.QUNITX_DEBUG === '1') return true;
    const deno = (globalThis as { Deno?: { env?: { get(key: string): string | undefined } } }).Deno;
    if (deno?.env?.get?.('QUNITX_DEBUG') === '1') return true;
  } catch {
    // Deno without --allow-env throws on env access; treat as non-debug.
  }
  return false;
}

export function filterStack(stack: string | undefined): string | undefined {
  if (!stack || isDebugEnabled()) return stack;
  return stack
    .split('\n')
    .filter((line) => !FILTER_PATTERNS.some((p) => p.test(line)))
    .join('\n');
}