mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 08:09:08 -05:00
0ea6eb83a9
Renames and adds missing fields to JSStackFrame from CallSite. Fixes #4705. Cleans up base changes for line and column numbers.
269 lines
7.4 KiB
TypeScript
269 lines
7.4 KiB
TypeScript
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
|
// Some of the code here is adapted directly from V8 and licensed under a BSD
|
|
// style license available here: https://github.com/v8/v8/blob/24886f2d1c565287d33d71e4109a53bf0b54b75c/LICENSE.v8
|
|
import * as colors from "./colors.ts";
|
|
import { applySourceMap, Location } from "./ops/errors.ts";
|
|
import { assert } from "./util.ts";
|
|
import { exposeForTest } from "./internals.ts";
|
|
|
|
function patchCallSite(callSite: CallSite, location: Location): CallSite {
|
|
return {
|
|
getThis(): unknown {
|
|
return callSite.getThis();
|
|
},
|
|
getTypeName(): string | null {
|
|
return callSite.getTypeName();
|
|
},
|
|
getFunction(): Function | null {
|
|
return callSite.getFunction();
|
|
},
|
|
getFunctionName(): string | null {
|
|
return callSite.getFunctionName();
|
|
},
|
|
getMethodName(): string | null {
|
|
return callSite.getMethodName();
|
|
},
|
|
getFileName(): string | null {
|
|
return location.fileName;
|
|
},
|
|
getLineNumber(): number {
|
|
return location.lineNumber;
|
|
},
|
|
getColumnNumber(): number {
|
|
return location.columnNumber;
|
|
},
|
|
getEvalOrigin(): string | null {
|
|
return callSite.getEvalOrigin();
|
|
},
|
|
isToplevel(): boolean | null {
|
|
return callSite.isToplevel();
|
|
},
|
|
isEval(): boolean {
|
|
return callSite.isEval();
|
|
},
|
|
isNative(): boolean {
|
|
return callSite.isNative();
|
|
},
|
|
isConstructor(): boolean {
|
|
return callSite.isConstructor();
|
|
},
|
|
isAsync(): boolean {
|
|
return callSite.isAsync();
|
|
},
|
|
isPromiseAll(): boolean {
|
|
return callSite.isPromiseAll();
|
|
},
|
|
getPromiseIndex(): number | null {
|
|
return callSite.getPromiseIndex();
|
|
},
|
|
};
|
|
}
|
|
|
|
function getMethodCall(callSite: CallSite): string {
|
|
let result = "";
|
|
|
|
const typeName = callSite.getTypeName();
|
|
const methodName = callSite.getMethodName();
|
|
const functionName = callSite.getFunctionName();
|
|
|
|
if (functionName) {
|
|
if (typeName) {
|
|
const startsWithTypeName = functionName.startsWith(typeName);
|
|
if (!startsWithTypeName) {
|
|
result += `${typeName}.`;
|
|
}
|
|
}
|
|
result += functionName;
|
|
|
|
if (methodName) {
|
|
if (!functionName.endsWith(methodName)) {
|
|
result += ` [as ${methodName}]`;
|
|
}
|
|
}
|
|
} else {
|
|
if (typeName) {
|
|
result += `${typeName}.`;
|
|
}
|
|
if (methodName) {
|
|
result += methodName;
|
|
} else {
|
|
result += "<anonymous>";
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function getFileLocation(callSite: CallSite, isInternal = false): string {
|
|
const cyan = isInternal ? colors.gray : colors.cyan;
|
|
const yellow = isInternal ? colors.gray : colors.yellow;
|
|
const black = isInternal ? colors.gray : (s: string): string => s;
|
|
if (callSite.isNative()) {
|
|
return cyan("native");
|
|
}
|
|
|
|
let result = "";
|
|
|
|
const fileName = callSite.getFileName();
|
|
if (!fileName && callSite.isEval()) {
|
|
const evalOrigin = callSite.getEvalOrigin();
|
|
assert(evalOrigin != null);
|
|
result += cyan(`${evalOrigin}, `);
|
|
}
|
|
|
|
if (fileName) {
|
|
result += cyan(fileName);
|
|
} else {
|
|
result += cyan("<anonymous>");
|
|
}
|
|
|
|
const lineNumber = callSite.getLineNumber();
|
|
if (lineNumber != null) {
|
|
result += `${black(":")}${yellow(lineNumber.toString())}`;
|
|
|
|
const columnNumber = callSite.getColumnNumber();
|
|
if (columnNumber != null) {
|
|
result += `${black(":")}${yellow(columnNumber.toString())}`;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function callSiteToString(callSite: CallSite, isInternal = false): string {
|
|
const cyan = isInternal ? colors.gray : colors.cyan;
|
|
const black = isInternal ? colors.gray : (s: string): string => s;
|
|
|
|
let result = "";
|
|
const functionName = callSite.getFunctionName();
|
|
|
|
const isTopLevel = callSite.isToplevel();
|
|
const isAsync = callSite.isAsync();
|
|
const isPromiseAll = callSite.isPromiseAll();
|
|
const isConstructor = callSite.isConstructor();
|
|
const isMethodCall = !(isTopLevel || isConstructor);
|
|
|
|
if (isAsync) {
|
|
result += colors.gray("async ");
|
|
}
|
|
if (isPromiseAll) {
|
|
result += colors.bold(
|
|
colors.italic(black(`Promise.all (index ${callSite.getPromiseIndex()})`))
|
|
);
|
|
return result;
|
|
}
|
|
if (isMethodCall) {
|
|
result += colors.bold(colors.italic(black(getMethodCall(callSite))));
|
|
} else if (isConstructor) {
|
|
result += colors.gray("new ");
|
|
if (functionName) {
|
|
result += colors.bold(colors.italic(black(functionName)));
|
|
} else {
|
|
result += cyan("<anonymous>");
|
|
}
|
|
} else if (functionName) {
|
|
result += colors.bold(colors.italic(black(functionName)));
|
|
} else {
|
|
result += getFileLocation(callSite, isInternal);
|
|
return result;
|
|
}
|
|
|
|
result += ` ${black("(")}${getFileLocation(callSite, isInternal)}${black(
|
|
")"
|
|
)}`;
|
|
return result;
|
|
}
|
|
|
|
interface CallSiteEval {
|
|
this: unknown;
|
|
typeName: string | null;
|
|
function: Function | null;
|
|
functionName: string | null;
|
|
methodName: string | null;
|
|
fileName: string | null;
|
|
lineNumber: number | null;
|
|
columnNumber: number | null;
|
|
evalOrigin: string | null;
|
|
isToplevel: boolean | null;
|
|
isEval: boolean;
|
|
isNative: boolean;
|
|
isConstructor: boolean;
|
|
isAsync: boolean;
|
|
isPromiseAll: boolean;
|
|
promiseIndex: number | null;
|
|
}
|
|
|
|
function evaluateCallSite(callSite: CallSite): CallSiteEval {
|
|
return {
|
|
this: callSite.getThis(),
|
|
typeName: callSite.getTypeName(),
|
|
function: callSite.getFunction(),
|
|
functionName: callSite.getFunctionName(),
|
|
methodName: callSite.getMethodName(),
|
|
fileName: callSite.getFileName(),
|
|
lineNumber: callSite.getLineNumber(),
|
|
columnNumber: callSite.getColumnNumber(),
|
|
evalOrigin: callSite.getEvalOrigin(),
|
|
isToplevel: callSite.isToplevel(),
|
|
isEval: callSite.isEval(),
|
|
isNative: callSite.isNative(),
|
|
isConstructor: callSite.isConstructor(),
|
|
isAsync: callSite.isAsync(),
|
|
isPromiseAll: callSite.isPromiseAll(),
|
|
promiseIndex: callSite.getPromiseIndex(),
|
|
};
|
|
}
|
|
|
|
function prepareStackTrace(
|
|
error: Error,
|
|
structuredStackTrace: CallSite[]
|
|
): string {
|
|
Object.defineProperties(error, {
|
|
__callSiteEvals: { value: [] },
|
|
__formattedFrames: { value: [] },
|
|
});
|
|
const errorString =
|
|
`${error.name}: ${error.message}\n` +
|
|
structuredStackTrace
|
|
.map(
|
|
(callSite): CallSite => {
|
|
const fileName = callSite.getFileName();
|
|
const lineNumber = callSite.getLineNumber();
|
|
const columnNumber = callSite.getColumnNumber();
|
|
if (fileName && lineNumber != null && columnNumber != null) {
|
|
return patchCallSite(
|
|
callSite,
|
|
applySourceMap({
|
|
fileName,
|
|
lineNumber,
|
|
columnNumber,
|
|
})
|
|
);
|
|
}
|
|
return callSite;
|
|
}
|
|
)
|
|
.map((callSite): string => {
|
|
// @ts-ignore
|
|
error.__callSiteEvals.push(Object.freeze(evaluateCallSite(callSite)));
|
|
const isInternal =
|
|
callSite.getFileName()?.startsWith("$deno$") ?? false;
|
|
const string = callSiteToString(callSite, isInternal);
|
|
// @ts-ignore
|
|
error.__formattedFrames.push(string);
|
|
return ` at ${colors.stripColor(string)}`;
|
|
})
|
|
.join("\n");
|
|
// @ts-ignore
|
|
Object.freeze(error.__callSiteEvals);
|
|
// @ts-ignore
|
|
Object.freeze(error.__formattedFrames);
|
|
return errorString;
|
|
}
|
|
|
|
// @internal
|
|
export function setPrepareStackTrace(ErrorConstructor: typeof Error): void {
|
|
ErrorConstructor.prepareStackTrace = prepareStackTrace;
|
|
}
|
|
|
|
exposeForTest("setPrepareStackTrace", setPrepareStackTrace);
|