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