mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 00:29:09 -05:00
Remap stack traces of unthrown errors. (#2693)
This commit is contained in:
parent
ff96e3dc63
commit
5083f5fd90
13 changed files with 517 additions and 3 deletions
|
@ -83,6 +83,7 @@ ts_sources = [
|
||||||
"../js/dispatch_minimal.ts",
|
"../js/dispatch_minimal.ts",
|
||||||
"../js/dom_types.ts",
|
"../js/dom_types.ts",
|
||||||
"../js/dom_util.ts",
|
"../js/dom_util.ts",
|
||||||
|
"../js/error_stack.ts",
|
||||||
"../js/errors.ts",
|
"../js/errors.ts",
|
||||||
"../js/event.ts",
|
"../js/event.ts",
|
||||||
"../js/event_target.ts",
|
"../js/event_target.ts",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
union Any {
|
union Any {
|
||||||
Accept,
|
Accept,
|
||||||
|
ApplySourceMap,
|
||||||
Cache,
|
Cache,
|
||||||
Chdir,
|
Chdir,
|
||||||
Chmod,
|
Chmod,
|
||||||
|
@ -257,6 +258,12 @@ table FetchSourceFileRes {
|
||||||
data: [ubyte];
|
data: [ubyte];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table ApplySourceMap {
|
||||||
|
filename: string;
|
||||||
|
line: int;
|
||||||
|
column: int;
|
||||||
|
}
|
||||||
|
|
||||||
table Cache {
|
table Cache {
|
||||||
extension: string;
|
extension: string;
|
||||||
module_id: string;
|
module_id: string;
|
||||||
|
|
46
cli/ops.rs
46
cli/ops.rs
|
@ -20,6 +20,8 @@ use crate::resources;
|
||||||
use crate::resources::table_entries;
|
use crate::resources::table_entries;
|
||||||
use crate::resources::Resource;
|
use crate::resources::Resource;
|
||||||
use crate::signal::kill;
|
use crate::signal::kill;
|
||||||
|
use crate::source_maps::get_orig_position;
|
||||||
|
use crate::source_maps::CachedMaps;
|
||||||
use crate::startup_data;
|
use crate::startup_data;
|
||||||
use crate::state::ThreadSafeState;
|
use crate::state::ThreadSafeState;
|
||||||
use crate::tokio_util;
|
use crate::tokio_util;
|
||||||
|
@ -46,6 +48,7 @@ use log;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use remove_dir_all::remove_dir_all;
|
use remove_dir_all::remove_dir_all;
|
||||||
use std;
|
use std;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::net::Shutdown;
|
use std::net::Shutdown;
|
||||||
|
@ -194,6 +197,7 @@ pub fn dispatch_all_legacy(
|
||||||
pub fn op_selector_std(inner_type: msg::Any) -> Option<CliDispatchFn> {
|
pub fn op_selector_std(inner_type: msg::Any) -> Option<CliDispatchFn> {
|
||||||
match inner_type {
|
match inner_type {
|
||||||
msg::Any::Accept => Some(op_accept),
|
msg::Any::Accept => Some(op_accept),
|
||||||
|
msg::Any::ApplySourceMap => Some(op_apply_source_map),
|
||||||
msg::Any::Cache => Some(op_cache),
|
msg::Any::Cache => Some(op_cache),
|
||||||
msg::Any::Chdir => Some(op_chdir),
|
msg::Any::Chdir => Some(op_chdir),
|
||||||
msg::Any::Chmod => Some(op_chmod),
|
msg::Any::Chmod => Some(op_chmod),
|
||||||
|
@ -532,6 +536,48 @@ fn op_fetch_source_file(
|
||||||
Ok(Op::Sync(result_buf))
|
Ok(Op::Sync(result_buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn op_apply_source_map(
|
||||||
|
state: &ThreadSafeState,
|
||||||
|
base: &msg::Base<'_>,
|
||||||
|
data: Option<PinnedBuf>,
|
||||||
|
) -> CliOpResult {
|
||||||
|
if !base.sync() {
|
||||||
|
return Err(deno_error::no_async_support());
|
||||||
|
}
|
||||||
|
assert!(data.is_none());
|
||||||
|
let inner = base.inner_as_apply_source_map().unwrap();
|
||||||
|
let cmd_id = base.cmd_id();
|
||||||
|
let filename = inner.filename().unwrap();
|
||||||
|
let line = inner.line();
|
||||||
|
let column = inner.column();
|
||||||
|
|
||||||
|
let mut mappings_map: CachedMaps = HashMap::new();
|
||||||
|
let (orig_filename, orig_line, orig_column) = get_orig_position(
|
||||||
|
filename.to_owned(),
|
||||||
|
line.into(),
|
||||||
|
column.into(),
|
||||||
|
&mut mappings_map,
|
||||||
|
&state.ts_compiler,
|
||||||
|
);
|
||||||
|
|
||||||
|
let builder = &mut FlatBufferBuilder::new();
|
||||||
|
let msg_args = msg::ApplySourceMapArgs {
|
||||||
|
filename: Some(builder.create_string(&orig_filename)),
|
||||||
|
line: orig_line as i32,
|
||||||
|
column: orig_column as i32,
|
||||||
|
};
|
||||||
|
let res_inner = msg::ApplySourceMap::create(builder, &msg_args);
|
||||||
|
ok_buf(serialize_response(
|
||||||
|
cmd_id,
|
||||||
|
builder,
|
||||||
|
msg::BaseArgs {
|
||||||
|
inner: Some(res_inner.as_union_value()),
|
||||||
|
inner_type: msg::Any::ApplySourceMap,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn op_chdir(
|
fn op_chdir(
|
||||||
_state: &ThreadSafeState,
|
_state: &ThreadSafeState,
|
||||||
base: &msg::Base<'_>,
|
base: &msg::Base<'_>,
|
||||||
|
|
|
@ -17,9 +17,9 @@ pub trait SourceMapGetter {
|
||||||
|
|
||||||
/// Cached filename lookups. The key can be None if a previous lookup failed to
|
/// Cached filename lookups. The key can be None if a previous lookup failed to
|
||||||
/// find a SourceMap.
|
/// find a SourceMap.
|
||||||
type CachedMaps = HashMap<String, Option<SourceMap>>;
|
pub type CachedMaps = HashMap<String, Option<SourceMap>>;
|
||||||
|
|
||||||
struct SourceMap {
|
pub struct SourceMap {
|
||||||
mappings: Mappings,
|
mappings: Mappings,
|
||||||
sources: Vec<String>,
|
sources: Vec<String>,
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ fn get_maybe_orig_position<G: SourceMapGetter>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_orig_position<G: SourceMapGetter>(
|
pub fn get_orig_position<G: SourceMapGetter>(
|
||||||
script_name: String,
|
script_name: String,
|
||||||
line: i64,
|
line: i64,
|
||||||
column: i64,
|
column: i64,
|
||||||
|
|
|
@ -58,6 +58,7 @@ export { statSync, lstatSync, stat, lstat } from "./stat";
|
||||||
export { linkSync, link } from "./link";
|
export { linkSync, link } from "./link";
|
||||||
export { symlinkSync, symlink } from "./symlink";
|
export { symlinkSync, symlink } from "./symlink";
|
||||||
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file";
|
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file";
|
||||||
|
export { applySourceMap } from "./error_stack";
|
||||||
export { ErrorKind, DenoError } from "./errors";
|
export { ErrorKind, DenoError } from "./errors";
|
||||||
export {
|
export {
|
||||||
permissions,
|
permissions,
|
||||||
|
@ -88,6 +89,9 @@ export const args: string[] = [];
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export { core } from "./core";
|
export { core } from "./core";
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export { setPrepareStackTrace } from "./error_stack";
|
||||||
|
|
||||||
// TODO Don't expose Console nor stringifyArgs.
|
// TODO Don't expose Console nor stringifyArgs.
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export { Console, stringifyArgs } from "./console";
|
export { Console, stringifyArgs } from "./console";
|
||||||
|
|
298
js/error_stack.ts
Normal file
298
js/error_stack.ts
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
// Copyright 2018-2019 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 msg from "gen/cli/msg_generated";
|
||||||
|
import * as flatbuffers from "./flatbuffers";
|
||||||
|
import * as dispatch from "./dispatch";
|
||||||
|
import { assert } from "./util";
|
||||||
|
|
||||||
|
export interface Location {
|
||||||
|
/** The full url for the module, e.g. `file://some/file.ts` or
|
||||||
|
* `https://some/file.ts`. */
|
||||||
|
filename: string;
|
||||||
|
|
||||||
|
/** The line number in the file. It is assumed to be 1-indexed. */
|
||||||
|
line: number;
|
||||||
|
|
||||||
|
/** The column number in the file. It is assumed to be 1-indexed. */
|
||||||
|
column: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function req(
|
||||||
|
filename: string,
|
||||||
|
line: number,
|
||||||
|
column: number
|
||||||
|
): [flatbuffers.Builder, msg.Any.ApplySourceMap, flatbuffers.Offset] {
|
||||||
|
const builder = flatbuffers.createBuilder();
|
||||||
|
const filename_ = builder.createString(filename);
|
||||||
|
const inner = msg.ApplySourceMap.createApplySourceMap(
|
||||||
|
builder,
|
||||||
|
filename_,
|
||||||
|
// On this side, line/column are 1 based, but in the source maps, they are
|
||||||
|
// 0 based, so we have to convert back and forth
|
||||||
|
line - 1,
|
||||||
|
column - 1
|
||||||
|
);
|
||||||
|
return [builder, msg.Any.ApplySourceMap, inner];
|
||||||
|
}
|
||||||
|
|
||||||
|
function res(baseRes: msg.Base | null): Location {
|
||||||
|
assert(baseRes != null);
|
||||||
|
assert(baseRes!.innerType() === msg.Any.ApplySourceMap);
|
||||||
|
const res = new msg.ApplySourceMap();
|
||||||
|
assert(baseRes!.inner(res) != null);
|
||||||
|
const filename = res.filename()!;
|
||||||
|
assert(filename != null);
|
||||||
|
return {
|
||||||
|
filename,
|
||||||
|
// On this side, line/column are 1 based, but in the source maps, they are
|
||||||
|
// 0 based, so we have to convert back and forth
|
||||||
|
line: res.line() + 1,
|
||||||
|
column: res.column() + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Given a current location in a module, lookup the source location and
|
||||||
|
* return it.
|
||||||
|
*
|
||||||
|
* When Deno transpiles code, it keep source maps of the transpiled code. This
|
||||||
|
* function can be used to lookup the original location. This is automatically
|
||||||
|
* done when accessing the `.stack` of an error, or when an uncaught error is
|
||||||
|
* logged. This function can be used to perform the lookup for creating better
|
||||||
|
* error handling.
|
||||||
|
*
|
||||||
|
* **Note:** `line` and `column` are 1 indexed, which matches display
|
||||||
|
* expectations, but is not typical of most index numbers in Deno.
|
||||||
|
*
|
||||||
|
* An example:
|
||||||
|
*
|
||||||
|
* const orig = Deno.applySourceMap({
|
||||||
|
* location: "file://my/module.ts",
|
||||||
|
* line: 5,
|
||||||
|
* column: 15
|
||||||
|
* });
|
||||||
|
* console.log(`${orig.filename}:${orig.line}:${orig.column}`);
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function applySourceMap(location: Location): Location {
|
||||||
|
const { filename, line, column } = location;
|
||||||
|
return res(dispatch.sendSync(...req(filename, line, column)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mutate the call site so that it returns the location, instead of its
|
||||||
|
* original location.
|
||||||
|
*/
|
||||||
|
function patchCallSite(callSite: CallSite, location: Location): CallSite {
|
||||||
|
return {
|
||||||
|
getThis(): unknown {
|
||||||
|
return callSite.getThis();
|
||||||
|
},
|
||||||
|
getTypeName(): string {
|
||||||
|
return callSite.getTypeName();
|
||||||
|
},
|
||||||
|
getFunction(): Function {
|
||||||
|
return callSite.getFunction();
|
||||||
|
},
|
||||||
|
getFunctionName(): string {
|
||||||
|
return callSite.getFunctionName();
|
||||||
|
},
|
||||||
|
getMethodName(): string {
|
||||||
|
return callSite.getMethodName();
|
||||||
|
},
|
||||||
|
getFileName(): string {
|
||||||
|
return location.filename;
|
||||||
|
},
|
||||||
|
getLineNumber(): number {
|
||||||
|
return location.line;
|
||||||
|
},
|
||||||
|
getColumnNumber(): number {
|
||||||
|
return location.column;
|
||||||
|
},
|
||||||
|
getEvalOrigin(): string | null {
|
||||||
|
return callSite.getEvalOrigin();
|
||||||
|
},
|
||||||
|
isToplevel(): boolean {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return a string representations of a CallSite's method call name
|
||||||
|
*
|
||||||
|
* This is adapted directly from V8.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return a string representations of a CallSite's file location
|
||||||
|
*
|
||||||
|
* This is adapted directly from V8.
|
||||||
|
*/
|
||||||
|
function getFileLocation(callSite: CallSite): string {
|
||||||
|
if (callSite.isNative()) {
|
||||||
|
return "native";
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
const fileName = callSite.getFileName();
|
||||||
|
if (!fileName && callSite.isEval()) {
|
||||||
|
const evalOrigin = callSite.getEvalOrigin();
|
||||||
|
assert(evalOrigin != null);
|
||||||
|
result += `${evalOrigin}, `;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName) {
|
||||||
|
result += fileName;
|
||||||
|
} else {
|
||||||
|
result += "<anonymous>";
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineNumber = callSite.getLineNumber();
|
||||||
|
if (lineNumber != null) {
|
||||||
|
result += `:${lineNumber}`;
|
||||||
|
|
||||||
|
const columnNumber = callSite.getColumnNumber();
|
||||||
|
if (columnNumber != null) {
|
||||||
|
result += `:${columnNumber}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert a CallSite to a string.
|
||||||
|
*
|
||||||
|
* This is adapted directly from V8.
|
||||||
|
*/
|
||||||
|
function callSiteToString(callSite: CallSite): string {
|
||||||
|
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 += "async ";
|
||||||
|
}
|
||||||
|
if (isPromiseAll) {
|
||||||
|
result += `Promise.all (index ${callSite.getPromiseIndex})`;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (isMethodCall) {
|
||||||
|
result += getMethodCall(callSite);
|
||||||
|
} else if (isConstructor) {
|
||||||
|
result += "new ";
|
||||||
|
if (functionName) {
|
||||||
|
result += functionName;
|
||||||
|
} else {
|
||||||
|
result += "<anonymous>";
|
||||||
|
}
|
||||||
|
} else if (functionName) {
|
||||||
|
result += functionName;
|
||||||
|
} else {
|
||||||
|
result += getFileLocation(callSite);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += ` (${getFileLocation(callSite)})`;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A replacement for the default stack trace preparer which will op into Rust
|
||||||
|
* to apply source maps to individual sites
|
||||||
|
*/
|
||||||
|
function prepareStackTrace(
|
||||||
|
error: Error,
|
||||||
|
structuredStackTrace: CallSite[]
|
||||||
|
): string {
|
||||||
|
return (
|
||||||
|
`${error.name}: ${error.message}\n` +
|
||||||
|
structuredStackTrace
|
||||||
|
.map(
|
||||||
|
(callSite): CallSite => {
|
||||||
|
const filename = callSite.getFileName();
|
||||||
|
const line = callSite.getLineNumber();
|
||||||
|
const column = callSite.getColumnNumber();
|
||||||
|
if (filename && line != null && column != null) {
|
||||||
|
return patchCallSite(
|
||||||
|
callSite,
|
||||||
|
applySourceMap({
|
||||||
|
filename,
|
||||||
|
line,
|
||||||
|
column
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return callSite;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.map((callSite): string => ` at ${callSiteToString(callSite)}`)
|
||||||
|
.join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the `prepareStackTrace` method on the Error constructor which will
|
||||||
|
* op into Rust to remap source code for caught errors where the `.stack` is
|
||||||
|
* being accessed.
|
||||||
|
*
|
||||||
|
* See: https://v8.dev/docs/stack-trace-api
|
||||||
|
*/
|
||||||
|
// @internal
|
||||||
|
export function setPrepareStackTrace(ErrorConstructor: typeof Error): void {
|
||||||
|
ErrorConstructor.prepareStackTrace = prepareStackTrace;
|
||||||
|
}
|
111
js/error_stack_test.ts
Normal file
111
js/error_stack_test.ts
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import { test, assert } from "./test_util.ts";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const { setPrepareStackTrace } = Deno as any;
|
||||||
|
|
||||||
|
interface CallSite {
|
||||||
|
getThis(): unknown;
|
||||||
|
getTypeName(): string;
|
||||||
|
getFunction(): Function;
|
||||||
|
getFunctionName(): string;
|
||||||
|
getMethodName(): string;
|
||||||
|
getFileName(): string;
|
||||||
|
getLineNumber(): number | null;
|
||||||
|
getColumnNumber(): number | null;
|
||||||
|
getEvalOrigin(): string | null;
|
||||||
|
isToplevel(): boolean;
|
||||||
|
isEval(): boolean;
|
||||||
|
isNative(): boolean;
|
||||||
|
isConstructor(): boolean;
|
||||||
|
isAsync(): boolean;
|
||||||
|
isPromiseAll(): boolean;
|
||||||
|
getPromiseIndex(): number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMockCallSite(
|
||||||
|
filename: string,
|
||||||
|
line: number | null,
|
||||||
|
column: number | null
|
||||||
|
): CallSite {
|
||||||
|
return {
|
||||||
|
getThis(): unknown {
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
getTypeName(): string {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
getFunction(): Function {
|
||||||
|
return (): void => {};
|
||||||
|
},
|
||||||
|
getFunctionName(): string {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
getMethodName(): string {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
getFileName(): string {
|
||||||
|
return filename;
|
||||||
|
},
|
||||||
|
getLineNumber(): number | null {
|
||||||
|
return line;
|
||||||
|
},
|
||||||
|
getColumnNumber(): number | null {
|
||||||
|
return column;
|
||||||
|
},
|
||||||
|
getEvalOrigin(): null {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
isToplevel(): false {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isEval(): false {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isNative(): false {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isConstructor(): false {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isAsync(): false {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isPromiseAll(): false {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getPromiseIndex(): null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test(function prepareStackTrace(): void {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const MockError = {} as any;
|
||||||
|
setPrepareStackTrace(MockError);
|
||||||
|
assert(typeof MockError.prepareStackTrace === "function");
|
||||||
|
const prepareStackTrace: (
|
||||||
|
error: Error,
|
||||||
|
structuredStackTrace: CallSite[]
|
||||||
|
) => string = MockError.prepareStackTrace;
|
||||||
|
const result = prepareStackTrace(new Error("foo"), [
|
||||||
|
getMockCallSite("gen/cli/bundle/main.js", 23, 0)
|
||||||
|
]);
|
||||||
|
assert(result.startsWith("Error: foo\n"));
|
||||||
|
assert(
|
||||||
|
result.includes("<anonymous> (js/"),
|
||||||
|
"should remap to something in 'js/'"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(function applySourceMap(): void {
|
||||||
|
const result = Deno.applySourceMap({
|
||||||
|
filename: "gen/cli/bundle/main.js",
|
||||||
|
line: 23,
|
||||||
|
column: 0
|
||||||
|
});
|
||||||
|
assert(result.filename.startsWith("js/"));
|
||||||
|
assert(result.line != null);
|
||||||
|
assert(result.column != null);
|
||||||
|
});
|
|
@ -40,6 +40,29 @@ import { immutableDefine } from "./util";
|
||||||
declare global {
|
declare global {
|
||||||
const console: consoleTypes.Console;
|
const console: consoleTypes.Console;
|
||||||
const setTimeout: typeof timers.setTimeout;
|
const setTimeout: typeof timers.setTimeout;
|
||||||
|
|
||||||
|
interface CallSite {
|
||||||
|
getThis(): unknown;
|
||||||
|
getTypeName(): string;
|
||||||
|
getFunction(): Function;
|
||||||
|
getFunctionName(): string;
|
||||||
|
getMethodName(): string;
|
||||||
|
getFileName(): string;
|
||||||
|
getLineNumber(): number | null;
|
||||||
|
getColumnNumber(): number | null;
|
||||||
|
getEvalOrigin(): string | null;
|
||||||
|
isToplevel(): boolean;
|
||||||
|
isEval(): boolean;
|
||||||
|
isNative(): boolean;
|
||||||
|
isConstructor(): boolean;
|
||||||
|
isAsync(): boolean;
|
||||||
|
isPromiseAll(): boolean;
|
||||||
|
getPromiseIndex(): number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorConstructor {
|
||||||
|
prepareStackTrace(error: Error, structuredStackTrace: CallSite[]): string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A self reference to the global object.
|
// A self reference to the global object.
|
||||||
|
|
|
@ -8,6 +8,7 @@ import "./globals";
|
||||||
import { assert, log } from "./util";
|
import { assert, log } from "./util";
|
||||||
import * as os from "./os";
|
import * as os from "./os";
|
||||||
import { args } from "./deno";
|
import { args } from "./deno";
|
||||||
|
import { setPrepareStackTrace } from "./error_stack";
|
||||||
import { replLoop } from "./repl";
|
import { replLoop } from "./repl";
|
||||||
import { xevalMain, XevalFunc } from "./xeval";
|
import { xevalMain, XevalFunc } from "./xeval";
|
||||||
import { setVersions } from "./version";
|
import { setVersions } from "./version";
|
||||||
|
@ -30,6 +31,8 @@ export default function denoMain(name?: string): void {
|
||||||
os.exit(0);
|
os.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPrepareStackTrace(Error);
|
||||||
|
|
||||||
const mainModule = startResMsg.mainModule();
|
const mainModule = startResMsg.mainModule();
|
||||||
if (mainModule) {
|
if (mainModule) {
|
||||||
assert(mainModule.length > 0);
|
assert(mainModule.length > 0);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import "./console_test.ts";
|
||||||
import "./copy_file_test.ts";
|
import "./copy_file_test.ts";
|
||||||
import "./custom_event_test.ts";
|
import "./custom_event_test.ts";
|
||||||
import "./dir_test.ts";
|
import "./dir_test.ts";
|
||||||
|
import "./error_stack_test.ts";
|
||||||
import "./event_test.ts";
|
import "./event_test.ts";
|
||||||
import "./event_target_test.ts";
|
import "./event_target_test.ts";
|
||||||
import "./fetch_test.ts";
|
import "./fetch_test.ts";
|
||||||
|
|
4
tests/error_stack.test
Normal file
4
tests/error_stack.test
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
args: run --reload tests/error_stack.ts
|
||||||
|
check_stderr: true
|
||||||
|
exit_code: 1
|
||||||
|
output: tests/error_stack.ts.out
|
10
tests/error_stack.ts
Normal file
10
tests/error_stack.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
function foo(): never {
|
||||||
|
throw new Error("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
foo();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
throw e;
|
||||||
|
}
|
6
tests/error_stack.ts.out
Normal file
6
tests/error_stack.ts.out
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[WILDCARD]Error: foo
|
||||||
|
at foo ([WILDCARD]tests/error_stack.ts:2:9)
|
||||||
|
at [WILDCARD]tests/error_stack.ts:6:3
|
||||||
|
error: Uncaught Error: foo
|
||||||
|
at foo ([WILDCARD]tests/error_stack.ts:2:9)
|
||||||
|
at [WILDCARD]tests/error_stack.ts:6:3
|
Loading…
Reference in a new issue