summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/cli.md10
-rw-r--r--lib/internal/bootstrap/pre_execution.js12
-rw-r--r--lib/internal/modules/cjs/loader.js27
-rw-r--r--lib/internal/modules/esm/translators.js2
-rw-r--r--lib/internal/source_map/source_map.js301
-rw-r--r--lib/internal/source_map/source_map_cache.js (renamed from lib/internal/source_map.js)93
-rw-r--r--node.gyp3
-rw-r--r--src/node_options.cc4
-rw-r--r--src/node_options.h1
-rw-r--r--test/fixtures/source-map/babel-esm-original.mjs9
-rw-r--r--test/fixtures/source-map/babel-esm.mjs10
-rw-r--r--test/fixtures/source-map/babel-throw-original.js19
-rw-r--r--test/fixtures/source-map/babel-throw.js21
-rw-r--r--test/fixtures/source-map/basic.js2
-rw-r--r--test/fixtures/source-map/esm-basic.mjs2
-rw-r--r--test/fixtures/source-map/esm-dep.mjs2
-rw-r--r--test/fixtures/source-map/exit-1.js2
-rw-r--r--test/fixtures/source-map/istanbul-throw-original.js10
-rw-r--r--test/fixtures/source-map/istanbul-throw.js4
-rw-r--r--test/fixtures/source-map/sigint.js2
-rw-r--r--test/fixtures/source-map/typescript-throw.js27
-rw-r--r--test/fixtures/source-map/typescript-throw.js.map1
-rw-r--r--test/fixtures/source-map/typescript-throw.ts24
-rw-r--r--test/fixtures/source-map/uglify-throw-original.js10
-rw-r--r--test/fixtures/source-map/uglify-throw.js2
-rw-r--r--test/message/source_map_throw_catch.js11
-rw-r--r--test/message/source_map_throw_catch.out14
-rw-r--r--test/message/source_map_throw_first_tick.js5
-rw-r--r--test/message/source_map_throw_first_tick.out14
-rw-r--r--test/message/source_map_throw_set_immediate.js5
-rw-r--r--test/message/source_map_throw_set_immediate.out10
-rw-r--r--test/parallel/test-bootstrap-modules.js2
-rw-r--r--test/parallel/test-source-map.js97
33 files changed, 732 insertions, 26 deletions
diff --git a/doc/api/cli.md b/doc/api/cli.md
index c196adf038..e0c2573882 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -135,6 +135,15 @@ added: v6.0.0
Enable FIPS-compliant crypto at startup. (Requires Node.js to be built with
`./configure --openssl-fips`.)
+### `--enable-source-maps`
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 1 - Experimental
+
+Enable experimental Source Map V3 support for stack traces.
+
### `--es-module-specifier-resolution=mode`
<!-- YAML
added: v12.0.0
@@ -980,6 +989,7 @@ node --require "./a.js" --require "./b.js"
Node.js options that are allowed are:
<!-- node-options-node start -->
* `--enable-fips`
+* `--enable-source-maps`
* `--es-module-specifier-resolution`
* `--experimental-exports`
* `--experimental-loader`
diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js
index 174ffcd018..c1636d87f4 100644
--- a/lib/internal/bootstrap/pre_execution.js
+++ b/lib/internal/bootstrap/pre_execution.js
@@ -21,6 +21,15 @@ function prepareMainThreadExecution(expandArgv1 = false) {
setupCoverageHooks(process.env.NODE_V8_COVERAGE);
}
+ // If source-map support has been enabled, we substitute in a new
+ // prepareStackTrace method, replacing the default in errors.js.
+ if (getOptionValue('--enable-source-maps')) {
+ const { prepareStackTrace } =
+ require('internal/source_map/source_map_cache');
+ const { setPrepareStackTraceCallback } = internalBinding('errors');
+ setPrepareStackTraceCallback(prepareStackTrace);
+ }
+
setupDebugEnv();
// Only main thread receives signals.
@@ -119,7 +128,8 @@ function setupCoverageHooks(dir) {
const cwd = require('internal/process/execution').tryGetCwd();
const { resolve } = require('path');
const coverageDirectory = resolve(cwd, dir);
- const { sourceMapCacheToObject } = require('internal/source_map');
+ const { sourceMapCacheToObject } =
+ require('internal/source_map/source_map_cache');
if (process.features.inspector) {
internalBinding('profiler').setCoverageDirectory(coverageDirectory);
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index 6f727b4510..9bee9130d2 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -31,7 +31,10 @@ const {
} = primordials;
const { NativeModule } = require('internal/bootstrap/loaders');
-const { maybeCacheSourceMap } = require('internal/source_map');
+const {
+ maybeCacheSourceMap,
+ rekeySourceMap
+} = require('internal/source_map/source_map_cache');
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
const { deprecate } = require('internal/util');
const vm = require('vm');
@@ -51,6 +54,7 @@ const {
loadNativeModule
} = require('internal/modules/cjs/helpers');
const { getOptionValue } = require('internal/options');
+const enableSourceMaps = getOptionValue('--enable-source-maps');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalModules = getOptionValue('--experimental-modules');
@@ -707,7 +711,19 @@ Module._load = function(request, parent, isMain) {
let threw = true;
try {
- module.load(filename);
+ // Intercept exceptions that occur during the first tick and rekey them
+ // on error instance rather than module instance (which will immediately be
+ // garbage collected).
+ if (enableSourceMaps) {
+ try {
+ module.load(filename);
+ } catch (err) {
+ rekeySourceMap(Module._cache[filename], err);
+ throw err; /* node-do-not-add-exception-line */
+ }
+ } else {
+ module.load(filename);
+ }
threw = false;
} finally {
if (threw) {
@@ -846,9 +862,7 @@ Module.prototype.require = function(id) {
var resolvedArgv;
let hasPausedEntry = false;
-function wrapSafe(filename, content, cjsModuleInstance) {
- maybeCacheSourceMap(filename, content, cjsModuleInstance);
-
+function wrapSafe(filename, content) {
if (patched) {
const wrapper = Module.wrap(content);
return vm.runInThisContext(wrapper, {
@@ -913,7 +927,8 @@ Module.prototype._compile = function(content, filename) {
manifest.assertIntegrity(moduleURL, content);
}
- const compiledWrapper = wrapSafe(filename, content, this);
+ maybeCacheSourceMap(filename, content, this);
+ const compiledWrapper = wrapSafe(filename, content);
var inspectorWrapper = null;
if (getOptionValue('--inspect-brk') && process._eval == null) {
diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js
index 352b937766..056bf64bf5 100644
--- a/lib/internal/modules/esm/translators.js
+++ b/lib/internal/modules/esm/translators.js
@@ -31,7 +31,7 @@ const {
} = require('internal/errors').codes;
const readFileAsync = promisify(fs.readFile);
const JsonParse = JSON.parse;
-const { maybeCacheSourceMap } = require('internal/source_map');
+const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
const debug = debuglog('esm');
diff --git a/lib/internal/source_map/source_map.js b/lib/internal/source_map/source_map.js
new file mode 100644
index 0000000000..9044521b6d
--- /dev/null
+++ b/lib/internal/source_map/source_map.js
@@ -0,0 +1,301 @@
+// This file is a modified version of:
+// https://cs.chromium.org/chromium/src/v8/tools/SourceMap.js?rcl=dd10454c1d
+// from the V8 codebase. Logic specific to WebInspector is removed and linting
+// is made to match the Node.js style guide.
+
+// Copyright 2013 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This is a copy from blink dev tools, see:
+// http://src.chromium.org/viewvc/blink/trunk/Source/devtools/front_end/SourceMap.js
+// revision: 153407
+
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+'use strict';
+
+let base64Map;
+
+const VLQ_BASE_SHIFT = 5;
+const VLQ_BASE_MASK = (1 << 5) - 1;
+const VLQ_CONTINUATION_MASK = 1 << 5;
+
+class StringCharIterator {
+ /**
+ * @constructor
+ * @param {string} string
+ */
+ constructor(string) {
+ this._string = string;
+ this._position = 0;
+ }
+
+ /**
+ * @return {string}
+ */
+ next() {
+ return this._string.charAt(this._position++);
+ }
+
+ /**
+ * @return {string}
+ */
+ peek() {
+ return this._string.charAt(this._position);
+ }
+
+ /**
+ * @return {boolean}
+ */
+ hasNext() {
+ return this._position < this._string.length;
+ }
+}
+
+/**
+ * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps
+ * for format description.
+ * @constructor
+ * @param {string} sourceMappingURL
+ * @param {SourceMapV3} payload
+ */
+class SourceMap {
+ #reverseMappingsBySourceURL = [];
+ #mappings = [];
+ #sources = {};
+ #sourceContentByURL = {};
+
+ /**
+ * @constructor
+ * @param {SourceMapV3} payload
+ */
+ constructor(payload) {
+ if (!base64Map) {
+ const base64Digits =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+ base64Map = {};
+ for (let i = 0; i < base64Digits.length; ++i)
+ base64Map[base64Digits[i]] = i;
+ }
+ this.#parseMappingPayload(payload);
+ }
+
+ /**
+ * @param {SourceMapV3} mappingPayload
+ */
+ #parseMappingPayload = (mappingPayload) => {
+ if (mappingPayload.sections)
+ this.#parseSections(mappingPayload.sections);
+ else
+ this.#parseMap(mappingPayload, 0, 0);
+ }
+
+ /**
+ * @param {Array.<SourceMapV3.Section>} sections
+ */
+ #parseSections = (sections) => {
+ for (let i = 0; i < sections.length; ++i) {
+ const section = sections[i];
+ this.#parseMap(section.map, section.offset.line, section.offset.column);
+ }
+ }
+
+ /**
+ * @param {number} lineNumber in compiled resource
+ * @param {number} columnNumber in compiled resource
+ * @return {?Array}
+ */
+ findEntry(lineNumber, columnNumber) {
+ let first = 0;
+ let count = this.#mappings.length;
+ while (count > 1) {
+ const step = count >> 1;
+ const middle = first + step;
+ const mapping = this.#mappings[middle];
+ if (lineNumber < mapping[0] ||
+ (lineNumber === mapping[0] && columnNumber < mapping[1])) {
+ count = step;
+ } else {
+ first = middle;
+ count -= step;
+ }
+ }
+ const entry = this.#mappings[first];
+ if (!first && entry && (lineNumber < entry[0] ||
+ (lineNumber === entry[0] && columnNumber < entry[1]))) {
+ return null;
+ }
+ return entry;
+ }
+
+ /**
+ * @param {string} sourceURL of the originating resource
+ * @param {number} lineNumber in the originating resource
+ * @return {Array}
+ */
+ findEntryReversed(sourceURL, lineNumber) {
+ const mappings = this.#reverseMappingsBySourceURL[sourceURL];
+ for (; lineNumber < mappings.length; ++lineNumber) {
+ const mapping = mappings[lineNumber];
+ if (mapping)
+ return mapping;
+ }
+ return this.#mappings[0];
+ }
+
+ /**
+ * @override
+ */
+ #parseMap = (map, lineNumber, columnNumber) => {
+ let sourceIndex = 0;
+ let sourceLineNumber = 0;
+ let sourceColumnNumber = 0;
+
+ const sources = [];
+ const originalToCanonicalURLMap = {};
+ for (let i = 0; i < map.sources.length; ++i) {
+ const url = map.sources[i];
+ originalToCanonicalURLMap[url] = url;
+ sources.push(url);
+ this.#sources[url] = true;
+
+ if (map.sourcesContent && map.sourcesContent[i])
+ this.#sourceContentByURL[url] = map.sourcesContent[i];
+ }
+
+ const stringCharIterator = new StringCharIterator(map.mappings);
+ let sourceURL = sources[sourceIndex];
+
+ while (true) {
+ if (stringCharIterator.peek() === ',')
+ stringCharIterator.next();
+ else {
+ while (stringCharIterator.peek() === ';') {
+ lineNumber += 1;
+ columnNumber = 0;
+ stringCharIterator.next();
+ }
+ if (!stringCharIterator.hasNext())
+ break;
+ }
+
+ columnNumber += decodeVLQ(stringCharIterator);
+ if (isSeparator(stringCharIterator.peek())) {
+ this.#mappings.push([lineNumber, columnNumber]);
+ continue;
+ }
+
+ const sourceIndexDelta = decodeVLQ(stringCharIterator);
+ if (sourceIndexDelta) {
+ sourceIndex += sourceIndexDelta;
+ sourceURL = sources[sourceIndex];
+ }
+ sourceLineNumber += decodeVLQ(stringCharIterator);
+ sourceColumnNumber += decodeVLQ(stringCharIterator);
+ if (!isSeparator(stringCharIterator.peek()))
+ // Unused index into the names list.
+ decodeVLQ(stringCharIterator);
+
+ this.#mappings.push([lineNumber, columnNumber, sourceURL,
+ sourceLineNumber, sourceColumnNumber]);
+ }
+
+ for (let i = 0; i < this.#mappings.length; ++i) {
+ const mapping = this.#mappings[i];
+ const url = mapping[2];
+ if (!url)
+ continue;
+ if (!this.#reverseMappingsBySourceURL[url])
+ this.#reverseMappingsBySourceURL[url] = [];
+ const reverseMappings = this.#reverseMappingsBySourceURL[url];
+ const sourceLine = mapping[3];
+ if (!reverseMappings[sourceLine])
+ reverseMappings[sourceLine] = [mapping[0], mapping[1]];
+ }
+ };
+}
+
+/**
+ * @param {string} char
+ * @return {boolean}
+ */
+function isSeparator(char) {
+ return char === ',' || char === ';';
+}
+
+/**
+ * @param {SourceMap.StringCharIterator} stringCharIterator
+ * @return {number}
+ */
+function decodeVLQ(stringCharIterator) {
+ // Read unsigned value.
+ let result = 0;
+ let shift = 0;
+ let digit;
+ do {
+ digit = base64Map[stringCharIterator.next()];
+ result += (digit & VLQ_BASE_MASK) << shift;
+ shift += VLQ_BASE_SHIFT;
+ } while (digit & VLQ_CONTINUATION_MASK);
+
+ // Fix the sign.
+ const negative = result & 1;
+ result >>= 1;
+ return negative ? -result : result;
+}
+
+module.exports = {
+ SourceMap
+};
diff --git a/lib/internal/source_map.js b/lib/internal/source_map/source_map_cache.js
index 4b198ff598..94a4165546 100644
--- a/lib/internal/source_map.js
+++ b/lib/internal/source_map/source_map_cache.js
@@ -5,6 +5,7 @@ const { Buffer } = require('buffer');
const debug = require('internal/util/debuglog').debuglog('source_map');
const { dirname, resolve } = require('path');
const fs = require('fs');
+const { getOptionValue } = require('internal/options');
const {
normalizeReferrerURL,
} = require('internal/modules/cjs/helpers');
@@ -16,10 +17,14 @@ const cjsSourceMapCache = new WeakMap();
// on filenames.
const esmSourceMapCache = new Map();
const { fileURLToPath, URL } = require('url');
+const { overrideStackTrace } = require('internal/errors');
+let experimentalSourceMaps;
function maybeCacheSourceMap(filename, content, cjsModuleInstance) {
- if (!process.env.NODE_V8_COVERAGE) return;
-
+ if (experimentalSourceMaps === undefined) {
+ experimentalSourceMaps = getOptionValue('--enable-source-maps');
+ }
+ if (!(process.env.NODE_V8_COVERAGE || experimentalSourceMaps)) return;
let basePath;
try {
filename = normalizeReferrerURL(filename);
@@ -35,6 +40,7 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance) {
if (match) {
if (cjsModuleInstance) {
cjsSourceMapCache.set(cjsModuleInstance, {
+ filename,
url: match.groups.sourceMappingURL,
data: dataFromUrl(basePath, match.groups.sourceMappingURL)
});
@@ -119,6 +125,16 @@ function sourcesToAbsolute(base, data) {
return data;
}
+// Move source map from garbage collected module to alternate key.
+function rekeySourceMap(cjsModuleInstance, newInstance) {
+ const sourceMap = cjsSourceMapCache.get(cjsModuleInstance);
+ if (sourceMap) {
+ cjsSourceMapCache.set(newInstance, sourceMap);
+ }
+}
+
+// Get serialized representation of source-map cache, this is used
+// to persist a cache of source-maps to disk when NODE_V8_COVERAGE is enabled.
function sourceMapCacheToObject() {
const obj = Object.create(null);
@@ -136,17 +152,86 @@ function sourceMapCacheToObject() {
// Since WeakMap can't be iterated over, we use Module._cache's
// keys to facilitate Source Map serialization.
+//
+// TODO(bcoe): this means we don't currently serialize source-maps attached
+// to error instances, only module instances.
function appendCJSCache(obj) {
const { Module } = require('internal/modules/cjs/loader');
Object.keys(Module._cache).forEach((key) => {
const value = cjsSourceMapCache.get(Module._cache[key]);
if (value) {
- obj[`file://${key}`] = value;
+ obj[`file://${key}`] = {
+ url: value.url,
+ data: value.data
+ };
+ }
+ });
+}
+
+// Create a prettified stacktrace, inserting context from source maps
+// if possible.
+const ErrorToString = Error.prototype.toString; // Capture original toString.
+const prepareStackTrace = (globalThis, error, trace) => {
+ // API for node internals to override error stack formatting
+ // without interfering with userland code.
+ // TODO(bcoe): add support for source-maps to repl.
+ if (overrideStackTrace.has(error)) {
+ const f = overrideStackTrace.get(error);
+ overrideStackTrace.delete(error);
+ return f(error, trace);
+ }
+
+ const { SourceMap } = require('internal/source_map/source_map');
+ const errorString = ErrorToString.call(error);
+
+ if (trace.length === 0) {
+ return errorString;
+ }
+ const preparedTrace = trace.map((t, i) => {
+ let str = i !== 0 ? '\n at ' : '';
+ str = `${str}${t}`;
+ try {
+ const sourceMap = findSourceMap(t.getFileName(), error);
+ if (sourceMap && sourceMap.data) {
+ const sm = new SourceMap(sourceMap.data);
+ // Source Map V3 lines/columns use zero-based offsets whereas, in
+ // stack traces, they start at 1/1.
+ const [, , url, line, col] =
+ sm.findEntry(t.getLineNumber() - 1, t.getColumnNumber() - 1);
+ if (url && line !== undefined && col !== undefined) {
+ str +=
+ `\n -> ${url.replace('file://', '')}:${line + 1}:${col + 1}`;
+ }
+ }
+ } catch (err) {
+ debug(err.stack);
}
+ return str;
});
+ return `${errorString}\n at ${preparedTrace.join('')}`;
+};
+
+// Attempt to lookup a source map, which is either attached to a file URI, or
+// keyed on an error instance.
+function findSourceMap(uri, error) {
+ const { Module } = require('internal/modules/cjs/loader');
+ let sourceMap = cjsSourceMapCache.get(Module._cache[uri]);
+ if (!uri.startsWith('file://')) uri = normalizeReferrerURL(uri);
+ if (sourceMap === undefined) {
+ sourceMap = esmSourceMapCache.get(uri);
+ }
+ if (sourceMap === undefined) {
+ const candidateSourceMap = cjsSourceMapCache.get(error);
+ if (candidateSourceMap && uri === candidateSourceMap.filename) {
+ sourceMap = candidateSourceMap;
+ }
+ }
+ return sourceMap;
}
module.exports = {
+ maybeCacheSourceMap,
+ prepareStackTrace,
+ rekeySourceMap,
sourceMapCacheToObject,
- maybeCacheSourceMap
};
diff --git a/node.gyp b/node.gyp
index dcd8904151..31d10b7444 100644
--- a/node.gyp
+++ b/node.gyp
@@ -175,7 +175,8 @@
'lib/internal/repl/history.js',
'lib/internal/repl/utils.js',
'lib/internal/socket_list.js',
- 'lib/internal/source_map.js',
+ 'lib/internal/source_map/source_map.js',
+ 'lib/internal/source_map/source_map_cache.js',
'lib/internal/test/binding.js',
'lib/internal/timers.js',
'lib/internal/tls.js',
diff --git a/src/node_options.cc b/src/node_options.cc
index 34efe6336a..dabd03376c 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -308,6 +308,10 @@ DebugOptionsParser::DebugOptionsParser() {
}
EnvironmentOptionsParser::EnvironmentOptionsParser() {
+ AddOption("--enable-source-maps",
+ "experimental Source Map V3 support",
+ &EnvironmentOptions::enable_source_maps,
+ kAllowedInEnvironment);
AddOption("--experimental-exports",
"experimental support for exports in package.json",
&EnvironmentOptions::experimental_exports,
diff --git a/src/node_options.h b/src/node_options.h
index 780e669eb6..ea0efbc9e8 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -100,6 +100,7 @@ class DebugOptions : public Options {
class EnvironmentOptions : public Options {
public:
bool abort_on_uncaught_exception = false;
+ bool enable_source_maps = false;
bool experimental_exports = false;
bool experimental_modules = false;
std::string es_module_specifier_resolution;
diff --git a/test/fixtures/source-map/babel-esm-original.mjs b/test/fixtures/source-map/babel-esm-original.mjs
new file mode 100644
index 0000000000..70ae479452
--- /dev/null
+++ b/test/fixtures/source-map/babel-esm-original.mjs
@@ -0,0 +1,9 @@
+import {foo} from './esm-dep.mjs';
+
+const obj = {
+ a: {
+ b: 22
+ }
+};
+
+if (obj?.a?.b === 22) throw Error('an exception');
diff --git a/test/fixtures/source-map/babel-esm.mjs b/test/fixtures/source-map/babel-esm.mjs
new file mode 100644
index 0000000000..9ad84663a3
--- /dev/null
+++ b/test/fixtures/source-map/babel-esm.mjs
@@ -0,0 +1,10 @@
+var _obj$a;
+
+import { foo } from './esm-dep.mjs';
+const obj = {
+ a: {
+ b: 22
+ }
+};
+if ((obj === null || obj === void 0 ? void 0 : (_obj$a = obj.a) === null || _obj$a === void 0 ? void 0 : _obj$a.b) === 22) throw Error('an exception');
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhYmVsLWVzbS1vcmlnaW5hbC5tanMiXSwibmFtZXMiOlsiZm9vIiwib2JqIiwiYSIsImIiLCJFcnJvciJdLCJtYXBwaW5ncyI6Ijs7QUFBQSxTQUFRQSxHQUFSLFFBQWtCLGVBQWxCO0FBRUEsTUFBTUMsR0FBRyxHQUFHO0FBQ1ZDLEVBQUFBLENBQUMsRUFBRTtBQUNEQyxJQUFBQSxDQUFDLEVBQUU7QUFERjtBQURPLENBQVo7QUFNQSxJQUFJLENBQUFGLEdBQUcsU0FBSCxJQUFBQSxHQUFHLFdBQUgsc0JBQUFBLEdBQUcsQ0FBRUMsQ0FBTCxrREFBUUMsQ0FBUixNQUFjLEVBQWxCLEVBQXNCLE1BQU1DLEtBQUssQ0FBQyxjQUFELENBQVgiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge2Zvb30gZnJvbSAnLi9lc20tZGVwLm1qcyc7XG5cbmNvbnN0IG9iaiA9IHtcbiAgYToge1xuICAgIGI6IDIyXG4gIH1cbn07XG5cbmlmIChvYmo/LmE/LmIgPT09IDIyKSB0aHJvdyBFcnJvcignYW4gZXhjZXB0aW9uJyk7XG5cbiJdfQ==
diff --git a/test/fixtures/source-map/babel-throw-original.js b/test/fixtures/source-map/babel-throw-original.js
new file mode 100644
index 0000000000..779bd16fd4
--- /dev/null
+++ b/test/fixtures/source-map/babel-throw-original.js
@@ -0,0 +1,19 @@
+/*---
+esid: prod-OptionalExpression
+features: [optional-chaining]
+---*/
+
+const obj = {
+ a: {
+ b: 22
+ }
+};
+
+function fn () {
+ return {};
+}
+
+setTimeout((err) => {
+ // OptionalExpression (MemberExpression OptionalChain) OptionalChain
+ if (obj?.a?.b === 22) throw Error('an exception');
+}, 5);
diff --git a/test/fixtures/source-map/babel-throw.js b/test/fixtures/source-map/babel-throw.js
new file mode 100644
index 0000000000..3cef68136c
--- /dev/null
+++ b/test/fixtures/source-map/babel-throw.js
@@ -0,0 +1,21 @@
+/*---
+esid: prod-OptionalExpression
+features: [optional-chaining]
+---*/
+const obj = {
+ a: {
+ b: 22
+ }
+};
+
+function fn() {
+ return {};
+}
+
+setTimeout(err => {
+ var _obj$a;
+
+ // OptionalExpression (MemberExpression OptionalChain) OptionalChain
+ if ((obj === null || obj === void 0 ? void 0 : (_obj$a = obj.a) === null || _obj$a === void 0 ? void 0 : _obj$a.b) === 22) throw Error('an exception');
+}, 5);
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhYmVsLXRocm93LW9yaWdpbmFsLmpzIl0sIm5hbWVzIjpbIm9iaiIsImEiLCJiIiwiZm4iLCJzZXRUaW1lb3V0IiwiZXJyIiwiRXJyb3IiXSwibWFwcGluZ3MiOiJBQUFBOzs7O0FBS0EsTUFBTUEsR0FBRyxHQUFHO0FBQ1ZDLEVBQUFBLENBQUMsRUFBRTtBQUNEQyxJQUFBQSxDQUFDLEVBQUU7QUFERjtBQURPLENBQVo7O0FBTUEsU0FBU0MsRUFBVCxHQUFlO0FBQ2IsU0FBTyxFQUFQO0FBQ0Q7O0FBRURDLFVBQVUsQ0FBRUMsR0FBRCxJQUFTO0FBQUE7O0FBQ2xCO0FBQ0EsTUFBSSxDQUFBTCxHQUFHLFNBQUgsSUFBQUEsR0FBRyxXQUFILHNCQUFBQSxHQUFHLENBQUVDLENBQUwsa0RBQVFDLENBQVIsTUFBYyxFQUFsQixFQUFzQixNQUFNSSxLQUFLLENBQUMsY0FBRCxDQUFYO0FBQ3ZCLENBSFMsRUFHUCxDQUhPLENBQVYiLCJzb3VyY2VzQ29udGVudCI6WyIvKi0tLVxuZXNpZDogcHJvZC1PcHRpb25hbEV4cHJlc3Npb25cbmZlYXR1cmVzOiBbb3B0aW9uYWwtY2hhaW5pbmddXG4tLS0qL1xuXG5jb25zdCBvYmogPSB7XG4gIGE6IHtcbiAgICBiOiAyMlxuICB9XG59O1xuXG5mdW5jdGlvbiBmbiAoKSB7XG4gIHJldHVybiB7fTtcbn1cblxuc2V0VGltZW91dCgoZXJyKSA9PiB7XG4gIC8vIE9wdGlvbmFsRXhwcmVzc2lvbiAoTWVtYmVyRXhwcmVzc2lvbiBPcHRpb25hbENoYWluKSBPcHRpb25hbENoYWluXG4gIGlmIChvYmo/LmE/LmIgPT09IDIyKSB0aHJvdyBFcnJvcignYW4gZXhjZXB0aW9uJyk7XG59LCA1KTtcblxuIl19
diff --git a/test/fixtures/source-map/basic.js b/test/fixtures/source-map/basic.js
index a483ffb105..5d1420360f 100644
--- a/test/fixtures/source-map/basic.js
+++ b/test/fixtures/source-map/basic.js
@@ -4,4 +4,4 @@ if (true) {
} else {
const c = 102;
}
-//# sourceMappingURL=https://http.cat/418
+//# sourceMappingURL=https://ci.nodejs.org/418
diff --git a/test/fixtures/source-map/esm-basic.mjs b/test/fixtures/source-map/esm-basic.mjs
index 55747d3870..03222b244b 100644
--- a/test/fixtures/source-map/esm-basic.mjs
+++ b/test/fixtures/source-map/esm-basic.mjs
@@ -1,4 +1,4 @@
import {foo} from './esm-dep.mjs';
import {strictEqual} from 'assert';
strictEqual(foo(), 'foo');
-//# sourceMappingURL=https://http.cat/405
+//# sourceMappingURL=https://ci.nodejs.org/405
diff --git a/test/fixtures/source-map/esm-dep.mjs b/test/fixtures/source-map/esm-dep.mjs
index 00805894af..5e86405751 100644
--- a/test/fixtures/source-map/esm-dep.mjs
+++ b/test/fixtures/source-map/esm-dep.mjs
@@ -1,4 +1,4 @@
export function foo () {
return 'foo';
};
-//# sourceMappingURL=https://http.cat/422
+//# sourceMappingURL=https://ci.nodejs.org/422
diff --git a/test/fixtures/source-map/exit-1.js b/test/fixtures/source-map/exit-1.js
index 9734649a77..d8b5626445 100644
--- a/test/fixtures/source-map/exit-1.js
+++ b/test/fixtures/source-map/exit-1.js
@@ -5,4 +5,4 @@ if (true) {
const c = 102;
}
process.exit(1);
-//# sourceMappingURL=https://http.cat/404
+//# sourceMappingURL=https://ci.nodejs.org/404
diff --git a/test/fixtures/source-map/istanbul-throw-original.js b/test/fixtures/source-map/istanbul-throw-original.js
new file mode 100644
index 0000000000..099faa175d
--- /dev/null
+++ b/test/fixtures/source-map/istanbul-throw-original.js
@@ -0,0 +1,10 @@
+/*
+ * comments dropped by uglify.
+ */
+function Hello() {
+ throw Error('goodbye');
+}
+
+setImmediate(function() {
+ Hello();
+});
diff --git a/test/fixtures/source-map/istanbul-throw.js b/test/fixtures/source-map/istanbul-throw.js
new file mode 100644
index 0000000000..4f719a1c7a
--- /dev/null
+++ b/test/fixtures/source-map/istanbul-throw.js
@@ -0,0 +1,4 @@
+var cov_ono70fls3=function(){var path="/Users/bencoe/oss/source-map-testing/istanbul-throw-original.js";var hash="4302fcea4eb0ea4d9af6e63a478f214aa61f9dd8";var global=new Function("return this")();var gcv="__coverage__";var coverageData={path:"/Users/bencoe/oss/source-map-testing/istanbul-throw-original.js",statementMap:{"0":{start:{line:5,column:2},end:{line:5,column:25}},"1":{start:{line:8,column:0},end:{line:10,column:3}},"2":{start:{line:9,column:2},end:{line:9,column:10}}},fnMap:{"0":{name:"Hello",decl:{start:{line:4,column:9},end:{line:4,column:14}},loc:{start:{line:4,column:17},end:{line:6,column:1}},line:4},"1":{name:"(anonymous_1)",decl:{start:{line:8,column:13},end:{line:8,column:14}},loc:{start:{line:8,column:24},end:{line:10,column:1}},line:8}},branchMap:{},s:{"0":0,"1":0,"2":0},f:{"0":0,"1":0},b:{},_coverageSchema:"43e27e138ebf9cfc5966b082cf9a028302ed4184",hash:"4302fcea4eb0ea4d9af6e63a478f214aa61f9dd8"};var coverage=global[gcv]||(global[gcv]={});if(coverage[path]&&coverage[path].hash===hash){return coverage[path];}return coverage[path]=coverageData;}();/*
+ * comments dropped by uglify.
+ */function Hello(){cov_ono70fls3.f[0]++;cov_ono70fls3.s[0]++;throw Error('goodbye');}cov_ono70fls3.s[1]++;setImmediate(function(){cov_ono70fls3.f[1]++;cov_ono70fls3.s[2]++;Hello();});
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9iZW5jb2Uvb3NzL3NvdXJjZS1tYXAtdGVzdGluZy9pc3RhbmJ1bC10aHJvdy1vcmlnaW5hbC5qcyJdLCJuYW1lcyI6WyJIZWxsbyIsIkVycm9yIiwic2V0SW1tZWRpYXRlIl0sIm1hcHBpbmdzIjoiMmpDQUFBOztHQUdBLFFBQVNBLENBQUFBLEtBQVQsRUFBaUIsMkNBQ2YsS0FBTUMsQ0FBQUEsS0FBSyxDQUFDLFNBQUQsQ0FBWCxDQUNELEMscUJBRURDLFlBQVksQ0FBQyxVQUFXLDJDQUN0QkYsS0FBSyxHQUNOLENBRlcsQ0FBWiIsInNvdXJjZXNDb250ZW50IjpbIi8qXG4gKiBjb21tZW50cyBkcm9wcGVkIGJ5IHVnbGlmeS5cbiAqL1xuZnVuY3Rpb24gSGVsbG8oKSB7XG4gIHRocm93IEVycm9yKCdnb29kYnllJyk7XG59XG5cbnNldEltbWVkaWF0ZShmdW5jdGlvbigpIHtcbiAgSGVsbG8oKTtcbn0pO1xuXG4iXX0=
diff --git a/test/fixtures/source-map/sigint.js b/test/fixtures/source-map/sigint.js
index 11df66645f..f9ffaa8666 100644
--- a/test/fixtures/source-map/sigint.js
+++ b/test/fixtures/source-map/sigint.js
@@ -5,4 +5,4 @@ if (true) {
const c = 102;
}
process.kill(process.pid, "SIGINT");
-//# sourceMappingURL=https://http.cat/402
+//# sourceMappingURL=https://ci.nodejs.org/402
diff --git a/test/fixtures/source-map/typescript-throw.js b/test/fixtures/source-map/typescript-throw.js
new file mode 100644
index 0000000000..0c4e57756f
--- /dev/null
+++ b/test/fixtures/source-map/typescript-throw.js
@@ -0,0 +1,27 @@
+var ATrue;
+(function (ATrue) {
+ ATrue[ATrue["IsTrue"] = 1] = "IsTrue";
+ ATrue[ATrue["IsFalse"] = 0] = "IsFalse";
+})(ATrue || (ATrue = {}));
+if (false) {
+ console.info('unreachable');
+}
+else if (true) {
+ console.info('reachable');
+}
+else {
+ console.info('unreachable');
+}
+function branch(a) {
+ if (a === ATrue.IsFalse) {
+ console.info('a = false');
+ }
+ else if (a === ATrue.IsTrue) {
+ throw Error('an exception');
+ }
+ else {
+ console.info('a = ???');
+ }
+}
+branch(ATrue.IsTrue);
+//# sourceMappingURL=typescript-throw.js.map
diff --git a/test/fixtures/source-map/typescript-throw.js.map b/test/fixtures/source-map/typescript-throw.js.map
new file mode 100644
index 0000000000..f1f55af1a9
--- /dev/null
+++ b/test/fixtures/source-map/typescript-throw.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"typescript-throw.js","sourceRoot":"","sources":["typescript-throw.ts"],"names":[],"mappings":"AAAA,IAAK,KAGJ;AAHD,WAAK,KAAK;IACR,qCAAU,CAAA;IACV,uCAAW,CAAA;AACb,CAAC,EAHI,KAAK,KAAL,KAAK,QAGT;AAED,IAAI,KAAK,EAAE;IACT,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;CAC5B;KAAM,IAAI,IAAI,EAAE;IACf,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;CAC1B;KAAM;IACL,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;CAC5B;AAED,SAAS,MAAM,CAAE,CAAQ;IACvB,IAAI,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE;QACvB,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;KAC1B;SAAM,IAAI,CAAC,KAAK,KAAK,CAAC,MAAM,EAAE;QAC7B,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;KAC7B;SAAM;QACL,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;KACxB;AACH,CAAC;AAED,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA"}
diff --git a/test/fixtures/source-map/typescript-throw.ts b/test/fixtures/source-map/typescript-throw.ts
new file mode 100644
index 0000000000..befb58fe0a
--- /dev/null
+++ b/test/fixtures/source-map/typescript-throw.ts
@@ -0,0 +1,24 @@
+enum ATrue {
+ IsTrue = 1,
+ IsFalse = 0
+}
+
+if (false) {
+ console.info('unreachable')
+} else if (true) {
+ console.info('reachable')
+} else {
+ console.info('unreachable')
+}
+
+function branch (a: ATrue) {
+ if (a === ATrue.IsFalse) {
+ console.info('a = false')
+ } else if (a === ATrue.IsTrue) {
+ throw Error('an exception');
+ } else {
+ console.info('a = ???')
+ }
+}
+
+branch(ATrue.IsTrue)
diff --git a/test/fixtures/source-map/uglify-throw-original.js b/test/fixtures/source-map/uglify-throw-original.js
new file mode 100644
index 0000000000..099faa175d
--- /dev/null
+++ b/test/fixtures/source-map/uglify-throw-original.js
@@ -0,0 +1,10 @@
+/*
+ * comments dropped by uglify.
+ */
+function Hello() {
+ throw Error('goodbye');
+}
+
+setImmediate(function() {
+ Hello();
+});
diff --git a/test/fixtures/source-map/uglify-throw.js b/test/fixtures/source-map/uglify-throw.js
new file mode 100644
index 0000000000..ea6201e59d
--- /dev/null
+++ b/test/fixtures/source-map/uglify-throw.js
@@ -0,0 +1,2 @@
+setImmediate(function(){!function(){throw Error("goodbye")}()});
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInVnbGlmeS10aHJvdy1vcmlnaW5hbC5qcyJdLCJuYW1lcyI6WyJzZXRJbW1lZGlhdGUiLCJFcnJvciIsIkhlbGxvIl0sIm1hcHBpbmdzIjoiQUFPQUEsYUFBYSxZQUpiLFdBQ0UsTUFBTUMsTUFBTSxXQUlaQyIsInNvdXJjZXNDb250ZW50IjpbIi8qXG4gKiBjb21tZW50cyBkcm9wcGVkIGJ5IHVnbGlmeS5cbiAqL1xuZnVuY3Rpb24gSGVsbG8oKSB7XG4gIHRocm93IEVycm9yKCdnb29kYnllJyk7XG59XG5cbnNldEltbWVkaWF0ZShmdW5jdGlvbigpIHtcbiAgSGVsbG8oKTtcbn0pO1xuXG4iXX0=
diff --git a/test/message/source_map_throw_catch.js b/test/message/source_map_throw_catch.js
new file mode 100644
index 0000000000..f3a887474b
--- /dev/null
+++ b/test/message/source_map_throw_catch.js
@@ -0,0 +1,11 @@
+// Flags: --enable-source-maps
+
+'use strict';
+require('../common');
+try {
+ require('../fixtures/source-map/typescript-throw');
+} catch (err) {
+ setTimeout(() => {
+ console.info(err);
+ }, 10);
+}
diff --git a/test/message/source_map_throw_catch.out b/test/message/source_map_throw_catch.out
new file mode 100644
index 0000000000..63e3eca3ef
--- /dev/null
+++ b/test/message/source_map_throw_catch.out
@@ -0,0 +1,14 @@
+reachable
+Error: an exception
+ at branch (*typescript-throw.js:20:15)
+ -> *typescript-throw.ts:18:11
+ at Object.<anonymous> (*typescript-throw.js:26:1)
+ -> *typescript-throw.ts:24:1
+ at Module._compile (internal/modules/cjs/loader.js:*)
+ at Object.Module._extensions..js (internal/modules/cjs/loader.js:*)
+ at Module.load (internal/modules/cjs/loader.js:*)
+ at Function.Module._load (internal/modules/cjs/loader.js:*)
+ at Module.require (internal/modules/cjs/loader.js:*)
+ at require (internal/modules/cjs/helpers.js:*)
+ at Object.<anonymous> (*source_map_throw_catch.js:6:3)
+ at Module._compile (internal/modules/cjs/loader.js:*)
diff --git a/test/message/source_map_throw_first_tick.js b/test/message/source_map_throw_first_tick.js
new file mode 100644
index 0000000000..b691b51f35
--- /dev/null
+++ b/test/message/source_map_throw_first_tick.js
@@ -0,0 +1,5 @@
+// Flags: --enable-source-maps
+
+'use strict';
+require('../common');
+require('../fixtures/source-map/typescript-throw');
diff --git a/test/message/source_map_throw_first_tick.out b/test/message/source_map_throw_first_tick.out
new file mode 100644
index 0000000000..7f11d9fbd9
--- /dev/null
+++ b/test/message/source_map_throw_first_tick.out
@@ -0,0 +1,14 @@
+reachable
+Error: an exception
+ at branch (*typescript-throw.js:20:15)
+ -> *typescript-throw.ts:18:11
+ at Object.<anonymous> (*typescript-throw.js:26:1)
+ -> *typescript-throw.ts:24:1
+ at Module._compile (internal/modules/cjs/loader.js:*)
+ at Object.Module._extensions..js (internal/modules/cjs/loader.js:*)
+ at Module.load (internal/modules/cjs/loader.js:*)
+ at Function.Module._load (internal/modules/cjs/loader.js:*)
+ at Module.require (internal/modules/cjs/loader.js:*)
+ at require (internal/modules/cjs/helpers.js:*)
+ at Object.<anonymous> (*source_map_throw_first_tick.js:5:1)
+ at Module._compile (internal/modules/cjs/loader.js:*)
diff --git a/test/message/source_map_throw_set_immediate.js b/test/message/source_map_throw_set_immediate.js
new file mode 100644
index 0000000000..17da1bd7ac
--- /dev/null
+++ b/test/message/source_map_throw_set_immediate.js
@@ -0,0 +1,5 @@
+// Flags: --enable-source-maps
+
+'use strict';
+require('../common');
+require('../fixtures/source-map/uglify-throw');
diff --git a/test/message/source_map_throw_set_immediate.out b/test/message/source_map_throw_set_immediate.out
new file mode 100644
index 0000000000..6b169d7e02
--- /dev/null
+++ b/test/message/source_map_throw_set_immediate.out
@@ -0,0 +1,10 @@
+*uglify-throw.js:1
+setImmediate(function(){!function(){throw Error("goodbye")}()});
+ ^
+
+Error: goodbye
+ at *uglify-throw.js:1:43
+ -> *uglify-throw-original.js:5:9
+ at Immediate.<anonymous> (*uglify-throw.js:1:60)
+ -> *uglify-throw-original.js:9:3
+ at processImmediate (internal/timers.js:*)
diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js
index 36c323b977..ae69f5aef0 100644
--- a/test/parallel/test-bootstrap-modules.js
+++ b/test/parallel/test-bootstrap-modules.js
@@ -55,7 +55,7 @@ const expectedModules = new Set([
'NativeModule internal/process/task_queues',
'NativeModule internal/process/warning',
'NativeModule internal/querystring',
- 'NativeModule internal/source_map',
+ 'NativeModule internal/source_map/source_map_cache',
'NativeModule internal/timers',
'NativeModule internal/url',
'NativeModule internal/util',
diff --git a/test/parallel/test-source-map.js b/test/parallel/test-source-map.js
index de728c05bf..41a315e3f8 100644
--- a/test/parallel/test-source-map.js
+++ b/test/parallel/test-source-map.js
@@ -30,7 +30,7 @@ function nextdir() {
assert.strictEqual(output.status, 0);
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache('basic.js', coverageDirectory);
- assert.strictEqual(sourceMap.url, 'https://http.cat/418');
+ assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/418');
}
// Outputs source maps when process.kill(process.pid, "SIGINT"); exits process.
@@ -47,7 +47,7 @@ function nextdir() {
}
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache('sigint.js', coverageDirectory);
- assert.strictEqual(sourceMap.url, 'https://http.cat/402');
+ assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/402');
}
// Outputs source maps when source-file calls process.exit(1).
@@ -58,7 +58,7 @@ function nextdir() {
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache('exit-1.js', coverageDirectory);
- assert.strictEqual(sourceMap.url, 'https://http.cat/404');
+ assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/404');
}
// Outputs source-maps for esm module.
@@ -71,7 +71,7 @@ function nextdir() {
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache('esm-basic.mjs', coverageDirectory);
- assert.strictEqual(sourceMap.url, 'https://http.cat/405');
+ assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/405');
}
// Loads source-maps with relative path from .map file on disk.
@@ -116,12 +116,95 @@ function nextdir() {
);
}
+// Does not apply source-map to stack trace if --experimental-modules
+// is not set.
+{
+ const output = spawnSync(process.execPath, [
+ require.resolve('../fixtures/source-map/uglify-throw.js')
+ ]);
+ assert.strictEqual(
+ output.stderr.toString().match(/->.*uglify-throw-original\.js:5:9/),
+ null
+ );
+ assert.strictEqual(
+ output.stderr.toString().match(/->.*uglify-throw-original\.js:9:3/),
+ null
+ );
+}
+
+// Applies source-maps generated by uglifyjs to stack trace.
+{
+ const output = spawnSync(process.execPath, [
+ '--enable-source-maps',
+ require.resolve('../fixtures/source-map/uglify-throw.js')
+ ]);
+ assert.ok(
+ output.stderr.toString().match(/->.*uglify-throw-original\.js:5:9/)
+ );
+ assert.ok(
+ output.stderr.toString().match(/->.*uglify-throw-original\.js:9:3/)
+ );
+}
+
+// Applies source-maps generated by tsc to stack trace.
+{
+ const output = spawnSync(process.execPath, [
+ '--enable-source-maps',
+ require.resolve('../fixtures/source-map/typescript-throw.js')
+ ]);
+ assert.ok(output.stderr.toString().match(/->.*typescript-throw\.ts:18:11/));
+ assert.ok(output.stderr.toString().match(/->.*typescript-throw\.ts:24:1/));
+}
+
+// Applies source-maps generated by babel to stack trace.
+{
+ const output = spawnSync(process.execPath, [
+ '--enable-source-maps',
+ require.resolve('../fixtures/source-map/babel-throw.js')
+ ]);
+ assert.ok(
+ output.stderr.toString().match(/->.*babel-throw-original\.js:18:31/)
+ );
+}
+
+// Applies source-maps generated by nyc to stack trace.
+{
+ const output = spawnSync(process.execPath, [
+ '--enable-source-maps',
+ require.resolve('../fixtures/source-map/istanbul-throw.js')
+ ]);
+ assert.ok(
+ output.stderr.toString().match(/->.*istanbul-throw-original\.js:5:9/)
+ );
+ assert.ok(
+ output.stderr.toString().match(/->.*istanbul-throw-original\.js:9:3/)
+ );
+}
+
+// Applies source-maps in esm modules to stack trace.
+{
+ const output = spawnSync(process.execPath, [
+ '--enable-source-maps',
+ '--experimental-modules',
+ require.resolve('../fixtures/source-map/babel-esm.mjs')
+ ]);
+ assert.ok(
+ output.stderr.toString().match(/->.*babel-esm-original\.mjs:9:29/)
+ );
+}
+
function getSourceMapFromCache(fixtureFile, coverageDirectory) {
const jsonFiles = fs.readdirSync(coverageDirectory);
for (const jsonFile of jsonFiles) {
- const maybeSourceMapCache = require(
- path.join(coverageDirectory, jsonFile)
- )['source-map-cache'] || {};
+ let maybeSourceMapCache;
+ try {
+ maybeSourceMapCache = require(
+ path.join(coverageDirectory, jsonFile)
+ )['source-map-cache'] || {};
+ } catch (err) {
+ console.warn(err);
+ maybeSourceMapCache = {};
+ }
const keys = Object.keys(maybeSourceMapCache);
for (const key of keys) {
if (key.includes(fixtureFile)) {