UNPKG

4.24 kBJavaScriptView Raw
1'use strict';
2const path = require('path');
3const cleanYamlObject = require('clean-yaml-object');
4const concordance = require('concordance');
5const isError = require('is-error');
6const StackUtils = require('stack-utils');
7const assert = require('./assert');
8const beautifyStack = require('./beautify-stack');
9const concordanceOptions = require('./concordance-options').default;
10
11function isAvaAssertionError(source) {
12 return source instanceof assert.AssertionError;
13}
14
15function filter(propertyName, isRoot) {
16 return !isRoot || (propertyName !== 'message' && propertyName !== 'name' && propertyName !== 'stack');
17}
18
19const stackUtils = new StackUtils();
20function extractSource(stack) {
21 if (!stack) {
22 return null;
23 }
24
25 const firstStackLine = stack.split('\n')[0];
26 return stackUtils.parseLine(firstStackLine);
27}
28
29function buildSource(source) {
30 if (!source) {
31 return null;
32 }
33
34 // Assume the CWD is the project directory. This holds since this function
35 // is only called in test workers, which are created with their working
36 // directory set to the project directory.
37 const projectDir = process.cwd();
38
39 const file = path.resolve(projectDir, source.file.trim());
40 const rel = path.relative(projectDir, file);
41
42 const [segment] = rel.split(path.sep);
43 const isWithinProject = segment !== '..' && (process.platform !== 'win32' || !segment.includes(':'));
44 const isDependency = isWithinProject && path.dirname(rel).split(path.sep).includes('node_modules');
45
46 return {
47 isDependency,
48 isWithinProject,
49 file,
50 line: source.line
51 };
52}
53
54function trySerializeError(err, shouldBeautifyStack) {
55 let stack = err.savedError ? err.savedError.stack : err.stack;
56
57 if (shouldBeautifyStack) {
58 stack = beautifyStack(stack);
59 }
60
61 const retval = {
62 avaAssertionError: isAvaAssertionError(err),
63 nonErrorObject: false,
64 source: buildSource(extractSource(stack)),
65 stack
66 };
67
68 if (err.actualStack) {
69 retval.stack = shouldBeautifyStack ? beautifyStack(err.actualStack) : err.actualStack;
70 }
71
72 if (retval.avaAssertionError) {
73 retval.improperUsage = err.improperUsage;
74 retval.message = err.message;
75 retval.name = err.name;
76 retval.statements = err.statements;
77 retval.values = err.values;
78
79 if (err.fixedSource) {
80 const source = buildSource(err.fixedSource);
81 if (source) {
82 retval.source = source;
83 }
84 }
85
86 if (err.assertion) {
87 retval.assertion = err.assertion;
88 }
89
90 if (err.operator) {
91 retval.operator = err.operator;
92 }
93 } else {
94 retval.object = cleanYamlObject(err, filter); // Cleanly copy non-standard properties
95 if (typeof err.message === 'string') {
96 retval.message = err.message;
97 }
98
99 if (typeof err.name === 'string') {
100 retval.name = err.name;
101 }
102 }
103
104 if (typeof err.stack === 'string') {
105 const lines = err.stack.split('\n');
106 if (err.name === 'SyntaxError' && !lines[0].startsWith('SyntaxError')) {
107 retval.summary = '';
108 for (const line of lines) {
109 retval.summary += line + '\n';
110 if (line.startsWith('SyntaxError')) {
111 break;
112 }
113 }
114
115 retval.summary = retval.summary.trim();
116 } else {
117 // Skip the source line header inserted by `esm`:
118 // <https://github.com/standard-things/esm/wiki/improved-errors>
119 const start = lines.findIndex(line => !/:\d+$/.test(line));
120 retval.summary = '';
121 for (let index = start; index < lines.length; index++) {
122 if (lines[index].startsWith(' at')) {
123 break;
124 }
125
126 const next = index + 1;
127 const end = next === lines.length || lines[next].startsWith(' at');
128 retval.summary += end ? lines[index] : lines[index] + '\n';
129 }
130 }
131 }
132
133 return retval;
134}
135
136function serializeError(origin, shouldBeautifyStack, err) {
137 if (!isError(err)) {
138 return {
139 avaAssertionError: false,
140 nonErrorObject: true,
141 formatted: concordance.formatDescriptor(concordance.describe(err, concordanceOptions), concordanceOptions)
142 };
143 }
144
145 try {
146 return trySerializeError(err, shouldBeautifyStack);
147 } catch (_) {
148 const replacement = new Error(`${origin}: Could not serialize error`);
149 return {
150 avaAssertionError: false,
151 nonErrorObject: false,
152 name: replacement.name,
153 message: replacement.message,
154 stack: replacement.stack,
155 summary: replacement.message
156 };
157 }
158}
159
160module.exports = serializeError;