summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2018-11-06 15:08:25 +1100
committerRyan Dahl <ry@tinyclouds.org>2018-11-06 06:37:16 -0800
commitf477b45a0a398e379ecafd2525c460f2793a43c2 (patch)
tree5a1782f818ad11fe0aa09fcc0d904b3a8ef7549f
parent7a17e2aec6307d37b7fe3bd9c7af0beb15ec924b (diff)
Improve preparing stack traces
-rw-r--r--js/compiler.ts100
-rw-r--r--js/compiler_test.ts4
-rw-r--r--js/main.ts12
-rw-r--r--js/v8_source_maps.ts78
4 files changed, 97 insertions, 97 deletions
diff --git a/js/compiler.ts b/js/compiler.ts
index a6b6e6970..046233442 100644
--- a/js/compiler.ts
+++ b/js/compiler.ts
@@ -10,7 +10,6 @@ import { window } from "./globals";
import * as os from "./os";
import { RawSourceMap } from "./types";
import { assert, log, notImplemented } from "./util";
-import * as sourceMaps from "./v8_source_maps";
const EOL = "\n";
const ASSETS = "$asset$";
@@ -91,7 +90,7 @@ export class ModuleMetaData implements ts.IScriptSnapshot {
public readonly mediaType: MediaType,
public readonly sourceCode: SourceCode = "",
public outputCode: OutputCode = "",
- public sourceMap: SourceMap = ""
+ public sourceMap: SourceMap | RawSourceMap = ""
) {
if (outputCode !== "" || fileName.endsWith(".d.ts")) {
this.scriptVersion = "1";
@@ -153,6 +152,8 @@ export class DenoCompiler
>();
// A reference to global eval, so it can be monkey patched during testing
private _globalEval = globalEval;
+ // Keep track of state of the last module requested via `getGeneratedContents`
+ private _lastModule: ModuleMetaData | undefined;
// A reference to the log utility, so it can be monkey patched during testing
private _log = log;
// A map of module file names to module meta data
@@ -376,63 +377,11 @@ export class DenoCompiler
innerMap.set(moduleSpecifier, fileName);
}
- /** Setup being able to map back source references back to their source
- *
- * TODO is this the best place for this? It is tightly coupled to how the
- * compiler works, but it is also tightly coupled to how the whole runtime
- * environment is bootstrapped. It also needs efficient access to the
- * `outputCode` of the module information, which exists inside of the
- * compiler instance.
- */
- private _setupSourceMaps(): void {
- let lastModule: ModuleMetaData | undefined;
- sourceMaps.install({
- installPrepareStackTrace: true,
-
- getGeneratedContents: (fileName: string): string | RawSourceMap => {
- this._log("compiler.getGeneratedContents", fileName);
- if (fileName === "gen/bundle/main.js") {
- assert(libdeno.mainSource.length > 0);
- return libdeno.mainSource;
- } else if (fileName === "main.js.map") {
- return libdeno.mainSourceMap;
- } else if (fileName === "deno_main.js") {
- return "";
- } else if (!fileName.endsWith(".map")) {
- const moduleMetaData = this._moduleMetaDataMap.get(fileName);
- if (!moduleMetaData) {
- lastModule = undefined;
- return "";
- }
- lastModule = moduleMetaData;
- return moduleMetaData.outputCode;
- } else {
- if (lastModule && lastModule.sourceMap) {
- // Assuming the the map will always be asked for after the source
- // code.
- const { sourceMap } = lastModule;
- lastModule = undefined;
- return sourceMap;
- } else {
- // Errors thrown here are caught by source-map.
- throw new Error(`Unable to find source map: "${fileName}"`);
- }
- }
- }
- });
- // Pre-compute source maps for main.js.map. This will happen at compile-time
- // as long as Compiler is instanciated before the snapshot is created..
- const consumer = sourceMaps.loadConsumer("gen/bundle/main.js");
- assert(consumer != null);
- consumer!.computeColumnSpans();
- }
-
private constructor() {
if (DenoCompiler._instance) {
throw new TypeError("Attempt to create an additional compiler.");
}
this._service = this._ts.createLanguageService(this);
- this._setupSourceMaps();
}
// Deno specific compiler API
@@ -503,19 +452,55 @@ export class DenoCompiler
moduleMetaData.outputCode = `${
outputFile.text
}\n//# sourceURL=${fileName}`;
- moduleMetaData.sourceMap = sourceMapFile.text;
+ moduleMetaData.sourceMap = JSON.parse(sourceMapFile.text);
}
moduleMetaData.scriptVersion = "1";
+ const sourceMap =
+ moduleMetaData.sourceMap === "string"
+ ? moduleMetaData.sourceMap
+ : JSON.stringify(moduleMetaData.sourceMap);
this._os.codeCache(
fileName,
sourceCode,
moduleMetaData.outputCode,
- moduleMetaData.sourceMap
+ sourceMap
);
return moduleMetaData.outputCode;
}
+ /** Given a fileName, return what was generated by the compiler. */
+ getGeneratedContents = (fileName: string): string | RawSourceMap => {
+ this._log("compiler.getGeneratedContents", fileName);
+ if (fileName === "gen/bundle/main.js") {
+ assert(libdeno.mainSource.length > 0);
+ return libdeno.mainSource;
+ } else if (fileName === "main.js.map") {
+ return libdeno.mainSourceMap;
+ } else if (fileName === "deno_main.js") {
+ return "";
+ } else if (!fileName.endsWith(".map")) {
+ const moduleMetaData = this._moduleMetaDataMap.get(fileName);
+ if (!moduleMetaData) {
+ this._lastModule = undefined;
+ return "";
+ }
+ this._lastModule = moduleMetaData;
+ return moduleMetaData.outputCode;
+ } else {
+ if (this._lastModule && this._lastModule.sourceMap) {
+ // Assuming the the map will always be asked for after the source
+ // code.
+ const { sourceMap } = this._lastModule;
+ this._lastModule = undefined;
+ return sourceMap;
+ } else {
+ // Errors thrown here are caught by source-map.
+ throw new Error(`Unable to find source map: "${fileName}"`);
+ }
+ }
+ };
+
/** For a given module specifier and containing file, return a list of
* absolute identifiers for dependent modules that are required by this
* module.
@@ -586,7 +571,8 @@ export class DenoCompiler
mediaType = fetchResponse.mediaType;
sourceCode = fetchResponse.sourceCode;
outputCode = fetchResponse.outputCode;
- sourceMap = fetchResponse.sourceMap;
+ sourceMap =
+ fetchResponse.sourceMap && JSON.parse(fetchResponse.sourceMap);
}
assert(moduleId != null, "No module ID.");
assert(fileName != null, "No file name.");
diff --git a/js/compiler_test.ts b/js/compiler_test.ts
index 729d6b4a7..d2a3a2cf2 100644
--- a/js/compiler_test.ts
+++ b/js/compiler_test.ts
@@ -438,7 +438,7 @@ test(function compilerRun() {
assert(moduleMetaData.hasRun);
assertEqual(moduleMetaData.sourceCode, fooBarTsSource);
assertEqual(moduleMetaData.outputCode, fooBarTsOutput);
- assertEqual(moduleMetaData.sourceMap, fooBarTsSourcemap);
+ assertEqual(JSON.stringify(moduleMetaData.sourceMap), fooBarTsSourcemap);
assertEqual(moduleMetaData.exports, { foo: "bar" });
assertEqual(
@@ -550,7 +550,7 @@ test(function compilerResolveModule() {
);
assertEqual(moduleMetaData.sourceCode, fooBazTsSource);
assertEqual(moduleMetaData.outputCode, fooBazTsOutput);
- assertEqual(moduleMetaData.sourceMap, fooBazTsSourcemap);
+ assertEqual(JSON.stringify(moduleMetaData.sourceMap), fooBazTsSourcemap);
assert(!moduleMetaData.hasRun);
assert(!moduleMetaData.deps);
assertEqual(moduleMetaData.exports, {});
diff --git a/js/main.ts b/js/main.ts
index 02e5dcb4d..176f098c7 100644
--- a/js/main.ts
+++ b/js/main.ts
@@ -9,11 +9,19 @@ import { args } from "./deno";
import { sendSync, handleAsyncMsgFromRust } from "./dispatch";
import { promiseErrorExaminer, promiseRejectHandler } from "./promise_util";
import { replLoop } from "./repl";
+import * as sourceMaps from "./v8_source_maps";
import { version } from "typescript";
-// Instantiate compiler at the top-level so it decodes source maps for the main
-// bundle during snapshot.
+// Install the source maps handler and do some pre-calculations so all of it is
+// available in the snapshot
const compiler = DenoCompiler.instance();
+sourceMaps.install({
+ installPrepareStackTrace: true,
+ getGeneratedContents: compiler.getGeneratedContents
+});
+const consumer = sourceMaps.loadConsumer("gen/bundle/main.js");
+assert(consumer != null);
+consumer!.computeColumnSpans();
function sendStart(): msg.StartRes {
const builder = flatbuffers.createBuilder();
diff --git a/js/v8_source_maps.ts b/js/v8_source_maps.ts
index cd61955d2..dcdde16c7 100644
--- a/js/v8_source_maps.ts
+++ b/js/v8_source_maps.ts
@@ -3,9 +3,8 @@
// Originated from source-map-support but has been heavily modified for deno.
import { SourceMapConsumer, MappedPosition } from "source-map";
-import * as base64 from "base64-js";
-import { arrayToStr } from "./util";
import { CallSite, RawSourceMap } from "./types";
+import { atob } from "./text_encoding";
const consumers = new Map<string, SourceMapConsumer>();
@@ -53,9 +52,9 @@ export function prepareStackTraceWrapper(
// @internal
export function prepareStackTrace(error: Error, stack: CallSite[]): string {
const frames = stack.map(
- (frame: CallSite) => `\n at ${wrapCallSite(frame).toString()}`
+ frame => `\n at ${wrapCallSite(frame).toString()}`
);
- return error.toString() + frames.join("");
+ return `${error.toString()}${frames.join("")}`;
}
// @internal
@@ -74,11 +73,13 @@ export function wrapCallSite(frame: CallSite): CallSite {
const column = (frame.getColumnNumber() || 1) - 1;
const position = mapSourcePosition({ source, line, column });
frame = cloneCallSite(frame);
- frame.getFileName = () => position.source;
- frame.getLineNumber = () => position.line;
- frame.getColumnNumber = () => Number(position.column) + 1;
- frame.getScriptNameOrSourceURL = () => position.source;
- frame.toString = () => CallSiteToString(frame);
+ Object.assign(frame, {
+ getFileName: () => position.source,
+ getLineNumber: () => position.line,
+ getColumnNumber: () => Number(position.column) + 1,
+ getScriptNameOrSourceURL: () => position.source,
+ toString: () => CallSiteToString(frame)
+ });
return frame;
}
@@ -87,8 +88,10 @@ export function wrapCallSite(frame: CallSite): CallSite {
if (origin) {
origin = mapEvalOrigin(origin);
frame = cloneCallSite(frame);
- frame.getEvalOrigin = () => origin;
- frame.toString = () => CallSiteToString(frame);
+ Object.assign(frame, {
+ getEvalOrigin: () => origin,
+ toString: () => CallSiteToString(frame)
+ });
return frame;
}
@@ -96,29 +99,30 @@ export function wrapCallSite(frame: CallSite): CallSite {
return frame;
}
-function cloneCallSite(frame: CallSite): CallSite {
- // tslint:disable:no-any
- const obj: any = {};
- const frame_ = frame as any;
- const props = Object.getOwnPropertyNames(Object.getPrototypeOf(frame));
- props.forEach(name => {
+function cloneCallSite(
+ frame: CallSite
+ // mixin: Partial<CallSite> & { toString: () => string }
+): CallSite {
+ const obj = {} as CallSite;
+ const props = Object.getOwnPropertyNames(
+ Object.getPrototypeOf(frame)
+ ) as Array<keyof CallSite>;
+ for (const name of props) {
obj[name] = /^(?:is|get)/.test(name)
- ? () => frame_[name].call(frame)
- : frame_[name];
- });
- return (obj as any) as CallSite;
- // tslint:enable:no-any
+ ? () => frame[name].call(frame)
+ : frame[name];
+ }
+ return obj;
}
// Taken from source-map-support, original copied from V8's messages.js
// MIT License. Copyright (c) 2014 Evan Wallace
function CallSiteToString(frame: CallSite): string {
- let fileName;
let fileLocation = "";
if (frame.isNative()) {
fileLocation = "native";
} else {
- fileName = frame.getScriptNameOrSourceURL();
+ const fileName = frame.getScriptNameOrSourceURL();
if (!fileName && frame.isEval()) {
fileLocation = frame.getEvalOrigin() || "";
fileLocation += ", "; // Expecting source position to follow.
@@ -134,10 +138,10 @@ function CallSiteToString(frame: CallSite): string {
}
const lineNumber = frame.getLineNumber();
if (lineNumber != null) {
- fileLocation += ":" + String(lineNumber);
+ fileLocation += `:${lineNumber}`;
const columnNumber = frame.getColumnNumber();
if (columnNumber) {
- fileLocation += ":" + String(columnNumber);
+ fileLocation += `:${columnNumber}`;
}
}
}
@@ -156,7 +160,7 @@ function CallSiteToString(frame: CallSite): string {
const methodName = frame.getMethodName();
if (functionName) {
if (typeName && functionName.indexOf(typeName) !== 0) {
- line += typeName + ".";
+ line += `${typeName}.`;
}
line += functionName;
if (
@@ -206,8 +210,7 @@ export function loadConsumer(source: string): SourceMapConsumer | null {
if (reSourceMap.test(sourceMappingURL)) {
// Support source map URL as a data url
const rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(",") + 1);
- const ui8 = base64.toByteArray(rawData);
- sourceMapData = arrayToStr(ui8);
+ sourceMapData = atob(rawData);
sourceMappingURL = source;
} else {
// TODO Support source map URLs relative to the source URL
@@ -217,7 +220,7 @@ export function loadConsumer(source: string): SourceMapConsumer | null {
const rawSourceMap =
typeof sourceMapData === "string"
- ? JSON.parse(sourceMapData)
+ ? (JSON.parse(sourceMapData) as RawSourceMap)
: sourceMapData;
consumer = new SourceMapConsumer(rawSourceMap);
consumers.set(source, consumer);
@@ -225,14 +228,14 @@ export function loadConsumer(source: string): SourceMapConsumer | null {
return consumer;
}
+// tslint:disable-next-line:max-line-length
+const sourceMapUrlRe = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/)[ \t]*$)/gm;
+
function retrieveSourceMapURL(fileData: string): string | null {
- // Get the URL of the source map
- // tslint:disable-next-line:max-line-length
- const re = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/)[ \t]*$)/gm;
// Keep executing the search to find the *last* sourceMappingURL to avoid
// picking up sourceMappingURLs from comments, strings, etc.
let lastMatch, match;
- while ((match = re.exec(fileData))) {
+ while ((match = sourceMapUrlRe.exec(fileData))) {
lastMatch = match;
}
if (!lastMatch) {
@@ -249,11 +252,14 @@ export function mapSourcePosition(position: Position): MappedPosition {
return consumer.originalPositionFor(position);
}
+const stackEvalRe = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/;
+const nestedEvalRe = /^eval at ([^(]+) \((.+)\)$/;
+
// Parses code generated by FormatEvalOrigin(), a function inside V8:
// https://code.google.com/p/v8/source/browse/trunk/src/messages.js
function mapEvalOrigin(origin: string): string {
// Most eval() calls are in this format
- let match = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/.exec(origin);
+ let match = stackEvalRe.exec(origin);
if (match) {
const position = mapSourcePosition({
source: match[2],
@@ -269,7 +275,7 @@ function mapEvalOrigin(origin: string): string {
}
// Parse nested eval() calls using recursion
- match = /^eval at ([^(]+) \((.+)\)$/.exec(origin);
+ match = nestedEvalRe.exec(origin);
if (match) {
return `eval at ${match[1]} (${mapEvalOrigin(match[2])})`;
}