All files / format / error.ts

50.00% Branches 2/4
35.62% Lines 26/73
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
 
x2
x2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x2
x2
 
 
x19
 
x19
x19
 
 
x2
x2
 
x3
x3
x3
x3
x3
x3
 
 
 
 
 
 
 
x2
x15
x15
x15
x15
x15
x15
x15
 
x2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x2
 
x2






















I

I



































































import type { Fn, Stringy } from '../index.ts';
import { getCwd } from '../deps.ts';
import { bold, gray } from './ansi.ts';

export function formatError (e: Error, name?: Stringy) {
  const [head, ...tail] = (e?.stack||'').split('\n');
  const stack = tail.map(x=>x
    .replace('('+getCwd()+'/', '(')
    .replace('./node_modules/.pnpm/', ''));
  e.message = e.message.split('Logs:')[0].trim();
  if (name) e.message = name + ': ' + e.message;
  name = name ? `    ${name}` : null;
  e.stack = [head, name, ...stack].filter(Boolean).join('\n');
  return e;
}
/** Add originating test step to stack trace.
  *
  * Since there is a degree of indirection when composing curried functions
  * (the code is defined from one place but executed from another),
  * without this helper the real stack gets lost. */
export function addStepStack (
  step: { name?: string, stack?: string[] }, error: Error
) {
  if (typeof error !== 'object') error = new Error(error);
  error.stack ||= ''
  if (step.stack) error.stack += '\n  From:\n' + step.stack.join('\n')
  return error
}
/** Set `Error.stackTraceLimit` to `Infinity`,
  * run a function, then restore its previous value. */
export async function withInfiniteStack <F extends Fn> (
  fn: F, ...args: Parameters<F>
): Promise<ReturnType<F>> {
  const stackTraceLimit = Error.stackTraceLimit;
  Error.stackTraceLimit = Infinity;
  const result = await fn(...args);
  Error.stackTraceLimit = stackTraceLimit;
  return result as ReturnType<F>;
}
/** Generate a stack trace. */
export function stackTrace (slice = 3, length?: number): string[] {
  return new Error().stack.split('\n')
    .slice(slice, length)
    .map(x=>alignTrace(x.trim()));
}
/** Relativize paths in stack trace, colorize, and reduce indent. */
export function alignTrace (line: string) {
  line = line.replace('file://'+getCwd(), '.');
  line = line.replace(getCwd(), '.');
  const format = (x: string, i: number) => (i===0)
    ? bold(gray(2, x.padEnd(36))) : gray(4, x);
  line = line.split(' (').map(format).join(gray(4, ' ('));
  return line
}

class Oops extends Error {
  // Todos are handled differently by the tester.
  todo?: boolean
  constructor (message: string, args?: object) {
    super(message);
    args && Object.assign(this, args);
  }
  // Throw a TODO.
  static TODO (info: unknown) {
    throw new this(info as string, { todo: true })
  }
  static required = <T>(...info: string[]): T => {
    throw new Error('Missing required value: ' + info.join(' '));
  }
  static requiredLate = (...info: string[]) => () => {
    throw new Error('Missing required value: ' + info.join(' '));
  }
  /** Define an error subclass. */
  static define <T extends unknown[]> (
    /** Name of error class. Prepended to parent. */
    name: string,
    /** How to generate the error message from the arguments passed to the constructor. */
    getMessage: (string|((...args: T)=>string)) = (...args: T) => args.join(' '),
    /** Whether there are any further construction steps such as assigning properties. */
    construct?: (self: Error, ...args: T) => any
  ) {
    const fullName = `${this.name}_${name}`
    class OopsError extends this {
      name = fullName
      constructor (...args: T) {
        super((typeof getMessage === 'string') ? getMessage : getMessage(...args))
        if (construct) construct(this, ...args)
      }
    }
    return Object.defineProperty(OopsError, 'name', { value: fullName })
  }
}

export { Oops as Error }