From ae8b2b4ab76c68d9911bca52f9b9559e37729bf7 Mon Sep 17 00:00:00 2001 From: bcoe Date: Sun, 29 Sep 2019 14:15:39 -0700 Subject: [PATCH] process: add source-map support to stack traces PR-URL: https://github.com/nodejs/node/pull/29564 Reviewed-By: Anna Henningsen Reviewed-By: Benjamin Gruenbaum Reviewed-By: Matteo Collina Reviewed-By: James M Snell --- doc/api/cli.md | 10 + lib/internal/bootstrap/pre_execution.js | 12 +- lib/internal/modules/cjs/loader.js | 20 +- lib/internal/modules/esm/translators.js | 2 +- lib/internal/source_map/source_map.js | 301 ++++++++++++++++++ .../source_map_cache.js} | 93 +++++- node.gyp | 3 +- src/node_options.cc | 4 + src/node_options.h | 1 + .../source-map/babel-esm-original.mjs | 9 + test/fixtures/source-map/babel-esm.mjs | 10 + .../source-map/babel-throw-original.js | 19 ++ test/fixtures/source-map/babel-throw.js | 21 ++ test/fixtures/source-map/basic.js | 2 +- test/fixtures/source-map/esm-basic.mjs | 2 +- test/fixtures/source-map/esm-dep.mjs | 2 +- test/fixtures/source-map/exit-1.js | 2 +- .../source-map/istanbul-throw-original.js | 10 + test/fixtures/source-map/istanbul-throw.js | 4 + test/fixtures/source-map/sigint.js | 2 +- test/fixtures/source-map/typescript-throw.js | 27 ++ .../source-map/typescript-throw.js.map | 1 + test/fixtures/source-map/typescript-throw.ts | 24 ++ .../source-map/uglify-throw-original.js | 10 + test/fixtures/source-map/uglify-throw.js | 2 + test/message/source_map_throw_catch.js | 11 + test/message/source_map_throw_catch.out | 14 + test/message/source_map_throw_first_tick.js | 5 + test/message/source_map_throw_first_tick.out | 14 + .../message/source_map_throw_set_immediate.js | 5 + .../source_map_throw_set_immediate.out | 10 + test/parallel/test-bootstrap-modules.js | 2 +- test/parallel/test-source-map.js | 97 +++++- 33 files changed, 729 insertions(+), 22 deletions(-) create mode 100644 lib/internal/source_map/source_map.js rename lib/internal/{source_map.js => source_map/source_map_cache.js} (58%) create mode 100644 test/fixtures/source-map/babel-esm-original.mjs create mode 100644 test/fixtures/source-map/babel-esm.mjs create mode 100644 test/fixtures/source-map/babel-throw-original.js create mode 100644 test/fixtures/source-map/babel-throw.js create mode 100644 test/fixtures/source-map/istanbul-throw-original.js create mode 100644 test/fixtures/source-map/istanbul-throw.js create mode 100644 test/fixtures/source-map/typescript-throw.js create mode 100644 test/fixtures/source-map/typescript-throw.js.map create mode 100644 test/fixtures/source-map/typescript-throw.ts create mode 100644 test/fixtures/source-map/uglify-throw-original.js create mode 100644 test/fixtures/source-map/uglify-throw.js create mode 100644 test/message/source_map_throw_catch.js create mode 100644 test/message/source_map_throw_catch.out create mode 100644 test/message/source_map_throw_first_tick.js create mode 100644 test/message/source_map_throw_first_tick.out create mode 100644 test/message/source_map_throw_set_immediate.js create mode 100644 test/message/source_map_throw_set_immediate.out diff --git a/doc/api/cli.md b/doc/api/cli.md index e6cb4b1952f02b..0ef08ed7e37fc1 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` + + +> Stability: 1 - Experimental + +Enable experimental Source Map V3 support for stack traces. + ### `--es-module-specifier-resolution=mode` * `--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 6fe9f36bc2c1e6..f9593101561416 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 9933839a055a47..e4c71f2edf44ef 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'); @@ -52,6 +55,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'); @@ -708,7 +712,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) { diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 19f434d44e68d4..7a84b51e2d0e05 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -32,7 +32,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 00000000000000..9044521b6d62d0 --- /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.} 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 similarity index 58% rename from lib/internal/source_map.js rename to lib/internal/source_map/source_map_cache.js index 4b198ff59871d1..94a4165546b77c 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 5bb6fd93845cd4..e7d0f27410bd73 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 a9425c4c0862ec..917de69fe8e875 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -312,6 +312,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 c4d33831e02bbe..89f6363f4a6e04 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 00000000000000..70ae479452a970 --- /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 00000000000000..9ad84663a32d35 --- /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 00000000000000..779bd16fd4611d --- /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 00000000000000..3cef68136ce186 --- /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 a483ffb105cfd7..5d1420360f466e 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 55747d3870bbe6..03222b244bae36 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 00805894afa6a2..5e86405751427c 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 9734649a773b42..d8b56264457593 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 00000000000000..099faa175dbfd8 --- /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 00000000000000..4f719a1c7a1ff0 --- /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 11df66645f07d7..f9ffaa8666229d 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 00000000000000..0c4e57756f708d --- /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 00000000000000..f1f55af1a92107 --- /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 00000000000000..befb58fe0a5185 --- /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 00000000000000..099faa175dbfd8 --- /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 00000000000000..ea6201e59d6c17 --- /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 00000000000000..f3a887474bfbac --- /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 00000000000000..63e3eca3eff0ce --- /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. (*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. (*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 00000000000000..b691b51f355194 --- /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 00000000000000..7f11d9fbd969be --- /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. (*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. (*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 00000000000000..17da1bd7acea66 --- /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 00000000000000..6b169d7e025f7e --- /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. (*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 36c323b97736a4..ae69f5aef01da9 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 de728c05bf8413..41a315e3f8184c 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)) {