diff options
author | bcoe <bencoe@google.com> | 2020-05-25 02:00:46 +0300 |
---|---|---|
committer | Benjamin Coe <bencoe@google.com> | 2020-05-25 08:03:34 +0300 |
commit | 458677f5ef2bd35da920246bb266502ea76bb66c (patch) | |
tree | e1809f38c6bbbf898287513a062ad588ba32e43d /lib/internal/source_map | |
parent | 8f10bb2da5bcf166fa1b414055f03352bbdb8126 (diff) |
errors: print original exception context
When --enable-source-maps is set, the error context displayed
above the stack trace now shows original source rather than
transpiled.
PR-URL: https://github.com/nodejs/node/pull/33491
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Diffstat (limited to 'lib/internal/source_map')
-rw-r--r-- | lib/internal/source_map/prepare_stack_trace.js | 57 | ||||
-rw-r--r-- | lib/internal/source_map/source_map_cache.js | 27 |
2 files changed, 76 insertions, 8 deletions
diff --git a/lib/internal/source_map/prepare_stack_trace.js b/lib/internal/source_map/prepare_stack_trace.js index 037a8dc53e0..df0d79a204b 100644 --- a/lib/internal/source_map/prepare_stack_trace.js +++ b/lib/internal/source_map/prepare_stack_trace.js @@ -5,6 +5,8 @@ const { } = primordials; const debug = require('internal/util/debuglog').debuglog('source_map'); +const { getStringWidth } = require('internal/util/inspect'); +const { readFileSync } = require('fs'); const { findSourceMap } = require('internal/source_map/source_map_cache'); const { kNoOverride, @@ -34,7 +36,17 @@ const prepareStackTrace = (globalThis, error, trace) => { if (trace.length === 0) { return errorString; } + + let errorSource = ''; + let firstSource; + let firstLine; + let firstColumn; const preparedTrace = trace.map((t, i) => { + if (i === 0) { + firstLine = t.getLineNumber(); + firstColumn = t.getColumnNumber(); + firstSource = t.getFileName(); + } let str = i !== 0 ? '\n at ' : ''; str = `${str}${t}`; try { @@ -49,8 +61,18 @@ const prepareStackTrace = (globalThis, error, trace) => { } = sm.findEntry(t.getLineNumber() - 1, t.getColumnNumber() - 1); if (originalSource && originalLine !== undefined && originalColumn !== undefined) { - str += -`\n -> ${originalSource.replace('file://', '')}:${originalLine + 1}:${originalColumn + 1}`; + const originalSourceNoScheme = originalSource + .replace(/^file:\/\//, ''); + if (i === 0) { + firstLine = originalLine + 1; + firstColumn = originalColumn + 1; + firstSource = originalSourceNoScheme; + // Show error in original source context to help user pinpoint it: + errorSource = getErrorSource(firstSource, firstLine, firstColumn); + } + // Show both original and transpiled stack trace information: + str += `\n -> ${originalSourceNoScheme}:${originalLine + 1}:` + + `${originalColumn + 1}`; } } } catch (err) { @@ -58,9 +80,38 @@ const prepareStackTrace = (globalThis, error, trace) => { } return str; }); - return `${errorString}\n at ${preparedTrace.join('')}`; + return `${errorSource}${errorString}\n at ${preparedTrace.join('')}`; }; +// Places a snippet of code from where the exception was originally thrown +// above the stack trace. This logic is modeled after GetErrorSource in +// node_errors.cc. +function getErrorSource(firstSource, firstLine, firstColumn) { + let exceptionLine = ''; + let source; + try { + source = readFileSync(firstSource, 'utf8'); + } catch (err) { + debug(err); + return exceptionLine; + } + const lines = source.split(/\r?\n/, firstLine); + const line = lines[firstLine - 1]; + if (!line) return exceptionLine; + + // Display ^ in appropriate position, regardless of whether tabs or + // spaces are used: + let prefix = ''; + for (const character of line.slice(0, firstColumn)) { + prefix += (character === '\t') ? '\t' : + ' '.repeat(getStringWidth(character)); + } + prefix = prefix.slice(0, -1); // The last character is the '^'. + + exceptionLine = `${firstSource}:${firstLine}\n${line}\n${prefix}^\n\n`; + return exceptionLine; +} + module.exports = { prepareStackTrace, }; diff --git a/lib/internal/source_map/source_map_cache.js b/lib/internal/source_map/source_map_cache.js index 06b1a2a5f52..04926acfc1b 100644 --- a/lib/internal/source_map/source_map_cache.js +++ b/lib/internal/source_map/source_map_cache.js @@ -39,12 +39,28 @@ const { fileURLToPath, URL } = require('url'); let Module; let SourceMap; -let experimentalSourceMaps; -function maybeCacheSourceMap(filename, content, cjsModuleInstance) { - if (experimentalSourceMaps === undefined) { - experimentalSourceMaps = getOptionValue('--enable-source-maps'); +let sourceMapsEnabled; +function getSourceMapsEnabled() { + if (sourceMapsEnabled === undefined) { + sourceMapsEnabled = getOptionValue('--enable-source-maps'); + if (sourceMapsEnabled) { + const { + enableSourceMaps, + setPrepareStackTraceCallback + } = internalBinding('errors'); + const { + prepareStackTrace + } = require('internal/source_map/prepare_stack_trace'); + setPrepareStackTraceCallback(prepareStackTrace); + enableSourceMaps(); + } } - if (!(process.env.NODE_V8_COVERAGE || experimentalSourceMaps)) return; + return sourceMapsEnabled; +} + +function maybeCacheSourceMap(filename, content, cjsModuleInstance) { + const sourceMapsEnabled = getSourceMapsEnabled(); + if (!(process.env.NODE_V8_COVERAGE || sourceMapsEnabled)) return; let basePath; try { filename = normalizeReferrerURL(filename); @@ -248,6 +264,7 @@ function findSourceMap(uri, error) { module.exports = { findSourceMap, + getSourceMapsEnabled, maybeCacheSourceMap, rekeySourceMap, sourceMapCacheToObject, |