Files
codeql-action/node_modules/ava/lib/serialize-error.js
2022-02-01 18:56:42 +00:00

171 lines
4.3 KiB
JavaScript

import path from 'node:path';
import process from 'node:process';
import {fileURLToPath, pathToFileURL} from 'node:url';
import cleanYamlObject from 'clean-yaml-object';
import concordance from 'concordance';
import isError from 'is-error';
import StackUtils from 'stack-utils';
import {AssertionError} from './assert.js';
import concordanceOptions from './concordance-options.js';
function isAvaAssertionError(source) {
return source instanceof AssertionError;
}
function filter(propertyName, isRoot) {
return !isRoot || (propertyName !== 'message' && propertyName !== 'name' && propertyName !== 'stack');
}
function normalizeFile(file, ...base) {
return file.startsWith('file://') ? file : pathToFileURL(path.resolve(...base, file)).toString();
}
const stackUtils = new StackUtils();
function extractSource(stack, testFile) {
if (!stack || !testFile) {
return null;
}
testFile = normalizeFile(testFile);
for (const line of stack.split('\n')) {
const callSite = stackUtils.parseLine(line);
if (callSite && normalizeFile(callSite.file) === testFile) {
return {
isDependency: false,
isWithinProject: true,
file: testFile,
line: callSite.line,
};
}
}
return null;
}
function buildSource(source) {
if (!source) {
return null;
}
// Assume the CWD is the project directory. This holds since this function
// is only called in test workers, which are created with their working
// directory set to the project directory.
const projectDir = process.cwd();
const file = normalizeFile(source.file.trim(), projectDir);
const rel = path.relative(projectDir, fileURLToPath(file));
const [segment] = rel.split(path.sep);
const isWithinProject = segment !== '..' && (process.platform !== 'win32' || !segment.includes(':'));
const isDependency = isWithinProject && path.dirname(rel).split(path.sep).includes('node_modules');
return {
isDependency,
isWithinProject,
file,
line: source.line,
};
}
function trySerializeError(error, shouldBeautifyStack, testFile) {
const stack = error.savedError ? error.savedError.stack : error.stack;
const retval = {
avaAssertionError: isAvaAssertionError(error),
nonErrorObject: false,
source: extractSource(stack, testFile),
stack,
shouldBeautifyStack,
};
if (error.actualStack) {
retval.stack = error.actualStack;
}
if (retval.avaAssertionError) {
retval.improperUsage = error.improperUsage;
retval.message = error.message;
retval.name = error.name;
retval.values = error.values;
if (error.fixedSource) {
const source = buildSource(error.fixedSource);
if (source) {
retval.source = source;
}
}
if (error.assertion) {
retval.assertion = error.assertion;
}
if (error.operator) {
retval.operator = error.operator;
}
} else {
retval.object = cleanYamlObject(error, filter); // Cleanly copy non-standard properties
if (typeof error.message === 'string') {
retval.message = error.message;
}
if (typeof error.name === 'string') {
retval.name = error.name;
}
}
if (typeof error.stack === 'string') {
const lines = error.stack.split('\n');
if (error.name === 'SyntaxError' && !lines[0].startsWith('SyntaxError')) {
retval.summary = '';
for (const line of lines) {
retval.summary += line + '\n';
if (line.startsWith('SyntaxError')) {
break;
}
}
retval.summary = retval.summary.trim();
} else {
retval.summary = '';
for (let index = 0; index < lines.length; index++) {
if (lines[index].startsWith(' at')) {
break;
}
const next = index + 1;
const end = next === lines.length || lines[next].startsWith(' at');
retval.summary += end ? lines[index] : lines[index] + '\n';
}
}
}
return retval;
}
export default function serializeError(origin, shouldBeautifyStack, error, testFile) {
if (!isError(error)) {
return {
avaAssertionError: false,
nonErrorObject: true,
formatted: concordance.formatDescriptor(concordance.describe(error, concordanceOptions), concordanceOptions),
};
}
try {
return trySerializeError(error, shouldBeautifyStack, testFile);
} catch {
const replacement = new Error(`${origin}: Could not serialize error`);
return {
avaAssertionError: false,
nonErrorObject: false,
name: replacement.name,
message: replacement.message,
stack: replacement.stack,
summary: replacement.message,
};
}
}