| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 08f9130 commit b01c496
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,9 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | + ArrayPrototypeIndexOf, | ||
| 4 | 5 | Error, | |
| 6 | + StringPrototypeStartsWith, | ||
| 5 | 7 | } = primordials; | |
| 6 | 8 | ||
| 7 | 9 | let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => { | |
@@ -15,6 +17,7 @@ const { | |||
| 15 | 17 | overrideStackTrace, | |
| 16 | 18 | maybeOverridePrepareStackTrace | |
| 17 | 19 | } = require('internal/errors'); | |
| 20 | + const { fileURLToPath } = require('internal/url'); | ||
| 18 | 21 | ||
| 19 | 22 | // Create a prettified stacktrace, inserting context from source maps | |
| 20 | 23 | // if possible. | |
@@ -40,14 +43,12 @@ const prepareStackTrace = (globalThis, error, trace) => { | |||
| 40 | 43 | } | |
| 41 | 44 | ||
| 42 | 45 | let errorSource = ''; | |
| 43 | - let firstSource; | ||
| 44 | 46 | let firstLine; | |
| 45 | 47 | let firstColumn; | |
| 46 | 48 | const preparedTrace = trace.map((t, i) => { | |
| 47 | 49 | if (i === 0) { | |
| 48 | 50 | firstLine = t.getLineNumber(); | |
| 49 | 51 | firstColumn = t.getColumnNumber(); | |
| 50 | - firstSource = t.getFileName(); | ||
| 51 | 52 | } | |
| 52 | 53 | let str = i !== 0 ? '\n at ' : ''; | |
| 53 | 54 | str = `${str}${t}`; | |
@@ -63,16 +64,22 @@ const prepareStackTrace = (globalThis, error, trace) => { | |||
| 63 | 64 | } = sm.findEntry(t.getLineNumber() - 1, t.getColumnNumber() - 1); | |
| 64 | 65 | if (originalSource && originalLine !== undefined && | |
| 65 | 66 | originalColumn !== undefined) { | |
| 66 | - const originalSourceNoScheme = originalSource | ||
| 67 | - .replace(/^file:\/\//, ''); | ||
| 68 | 67 | if (i === 0) { | |
| 69 | 68 | firstLine = originalLine + 1; | |
| 70 | 69 | firstColumn = originalColumn + 1; | |
| 71 | - firstSource = originalSourceNoScheme; | ||
| 70 | + | ||
| 72 | 71 | // Show error in original source context to help user pinpoint it: | |
| 73 | - errorSource = getErrorSource(firstSource, firstLine, firstColumn); | ||
| 72 | + errorSource = getErrorSource( | ||
| 73 | + sm.payload, | ||
| 74 | + originalSource, | ||
| 75 | + firstLine, | ||
| 76 | + firstColumn | ||
| 77 | + ); | ||
| 74 | 78 | } | |
| 75 | 79 | // Show both original and transpiled stack trace information: | |
| 80 | + const originalSourceNoScheme = | ||
| 81 | + StringPrototypeStartsWith(originalSource, 'file://') ? | ||
| 82 | + fileURLToPath(originalSource) : originalSource; | ||
| 76 | 83 | str += `\n -> ${originalSourceNoScheme}:${originalLine + 1}:` + | |
| 77 | 84 | `${originalColumn + 1}`; | |
| 78 | 85 | } | |
@@ -88,15 +95,29 @@ const prepareStackTrace = (globalThis, error, trace) => { | |||
| 88 | 95 | // Places a snippet of code from where the exception was originally thrown | |
| 89 | 96 | // above the stack trace. This logic is modeled after GetErrorSource in | |
| 90 | 97 | // node_errors.cc. | |
| 91 | - function getErrorSource(firstSource, firstLine, firstColumn) { | ||
| 98 | + function getErrorSource(payload, originalSource, firstLine, firstColumn) { | ||
| 92 | 99 | let exceptionLine = ''; | |
| 100 | + const originalSourceNoScheme = | ||
| 101 | + StringPrototypeStartsWith(originalSource, 'file://') ? | ||
| 102 | + fileURLToPath(originalSource) : originalSource; | ||
| 103 | + | ||
| 93 | 104 | let source; | |
| 94 | - try { | ||
| 95 | - source = readFileSync(firstSource, 'utf8'); | ||
| 96 | - } catch (err) { | ||
| 97 | - debug(err); | ||
| 98 | - return exceptionLine; | ||
| 105 | + const sourceContentIndex = | ||
| 106 | + ArrayPrototypeIndexOf(payload.sources, originalSource); | ||
| 107 | + if (payload.sourcesContent?.[sourceContentIndex]) { | ||
| 108 | + // First we check if the original source content was provided in the | ||
| 109 | + // source map itself: | ||
| 110 | + source = payload.sourcesContent[sourceContentIndex]; | ||
| 111 | + } else { | ||
| 112 | + // If no sourcesContent was found, attempt to load the original source | ||
| 113 | + // from disk: | ||
| 114 | + try { | ||
| 115 | + source = readFileSync(originalSourceNoScheme, 'utf8'); | ||
| 116 | + } catch (err) { | ||
| 117 | + debug(err); | ||
| 118 | + } | ||
| 99 | 119 | } | |
| 120 | + | ||
| 100 | 121 | const lines = source.split(/\r?\n/, firstLine); | |
| 101 | 122 | const line = lines[firstLine - 1]; | |
| 102 | 123 | if (!line) return exceptionLine; | |
@@ -110,7 +131,8 @@ function getErrorSource(firstSource, firstLine, firstColumn) { | |||
| 110 | 131 | } | |
| 111 | 132 | prefix = prefix.slice(0, -1); // The last character is the '^'. | |
| 112 | 133 | ||
| 113 | - exceptionLine = `${firstSource}:${firstLine}\n${line}\n${prefix}^\n\n`; | ||
| 134 | + exceptionLine = | ||
| 135 | + `${originalSourceNoScheme}:${firstLine}\n${line}\n${prefix}^\n\n`; | ||
| 114 | 136 | return exceptionLine; | |
| 115 | 137 | } | |
| 116 | 138 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,7 +25,6 @@ const { Buffer } = require('buffer'); | |||
| 25 | 25 | let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => { | |
| 26 | 26 | debug = fn; | |
| 27 | 27 | }); | |
| 28 | - const { dirname, resolve } = require('path'); | ||
| 29 | 28 | const fs = require('fs'); | |
| 30 | 29 | const { getOptionValue } = require('internal/options'); | |
| 31 | 30 | const { | |
@@ -63,10 +62,8 @@ function getSourceMapsEnabled() { | |||
| 63 | 62 | function maybeCacheSourceMap(filename, content, cjsModuleInstance) { | |
| 64 | 63 | const sourceMapsEnabled = getSourceMapsEnabled(); | |
| 65 | 64 | if (!(process.env.NODE_V8_COVERAGE || sourceMapsEnabled)) return; | |
| 66 | - let basePath; | ||
| 67 | 65 | try { | |
| 68 | 66 | filename = normalizeReferrerURL(filename); | |
| 69 | - basePath = dirname(fileURLToPath(filename)); | ||
| 70 | 67 | } catch (err) { | |
| 71 | 68 | // This is most likely an [eval]-wrapper, which is currently not | |
| 72 | 69 | // supported. | |
@@ -76,7 +73,7 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance) { | |||
| 76 | 73 | ||
| 77 | 74 | const match = content.match(/\/[*/]#\s+sourceMappingURL=(?<sourceMappingURL>[^\s]+)/); | |
| 78 | 75 | if (match) { | |
| 79 | - const data = dataFromUrl(basePath, match.groups.sourceMappingURL); | ||
| 76 | + const data = dataFromUrl(filename, match.groups.sourceMappingURL); | ||
| 80 | 77 | const url = data ? null : match.groups.sourceMappingURL; | |
| 81 | 78 | if (cjsModuleInstance) { | |
| 82 | 79 | if (!Module) Module = require('internal/modules/cjs/loader').Module; | |
@@ -98,21 +95,21 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance) { | |||
| 98 | 95 | } | |
| 99 | 96 | } | |
| 100 | 97 | ||
| 101 | - function dataFromUrl(basePath, sourceMappingURL) { | ||
| 98 | + function dataFromUrl(sourceURL, sourceMappingURL) { | ||
| 102 | 99 | try { | |
| 103 | 100 | const url = new URL(sourceMappingURL); | |
| 104 | 101 | switch (url.protocol) { | |
| 105 | 102 | case 'data:': | |
| 106 | - return sourceMapFromDataUrl(basePath, url.pathname); | ||
| 103 | + return sourceMapFromDataUrl(sourceURL, url.pathname); | ||
| 107 | 104 | default: | |
| 108 | 105 | debug(`unknown protocol ${url.protocol}`); | |
| 109 | 106 | return null; | |
| 110 | 107 | } | |
| 111 | 108 | } catch (err) { | |
| 112 | 109 | debug(err.stack); | |
| 113 | 110 | // If no scheme is present, we assume we are dealing with a file path. | |
| 114 | - const sourceMapFile = resolve(basePath, sourceMappingURL); | ||
| 115 | - return sourceMapFromFile(sourceMapFile); | ||
| 111 | + const mapURL = new URL(sourceMappingURL, sourceURL).href; | ||
| 112 | + return sourceMapFromFile(mapURL); | ||
| 116 | 113 | } | |
| 117 | 114 | } | |
| 118 | 115 | ||
@@ -128,11 +125,11 @@ function lineLengths(content) { | |||
| 128 | 125 | }); | |
| 129 | 126 | } | |
| 130 | 127 | ||
| 131 | - function sourceMapFromFile(sourceMapFile) { | ||
| 128 | + function sourceMapFromFile(mapURL) { | ||
| 132 | 129 | try { | |
| 133 | - const content = fs.readFileSync(sourceMapFile, 'utf8'); | ||
| 130 | + const content = fs.readFileSync(fileURLToPath(mapURL), 'utf8'); | ||
| 134 | 131 | const data = JSONParse(content); | |
| 135 | - return sourcesToAbsolute(dirname(sourceMapFile), data); | ||
| 132 | + return sourcesToAbsolute(mapURL, data); | ||
| 136 | 133 | } catch (err) { | |
| 137 | 134 | debug(err.stack); | |
| 138 | 135 | return null; | |
@@ -141,7 +138,7 @@ function sourceMapFromFile(sourceMapFile) { | |||
| 141 | 138 | ||
| 142 | 139 | // data:[<mediatype>][;base64],<data> see: | |
| 143 | 140 | // https://tools.ietf.org/html/rfc2397#section-2 | |
| 144 | - function sourceMapFromDataUrl(basePath, url) { | ||
| 141 | + function sourceMapFromDataUrl(sourceURL, url) { | ||
| 145 | 142 | const [format, data] = url.split(','); | |
| 146 | 143 | const splitFormat = format.split(';'); | |
| 147 | 144 | const contentType = splitFormat[0]; | |
@@ -151,7 +148,7 @@ function sourceMapFromDataUrl(basePath, url) { | |||
| 151 | 148 | Buffer.from(data, 'base64').toString('utf8') : data; | |
| 152 | 149 | try { | |
| 153 | 150 | const parsedData = JSONParse(decodedData); | |
| 154 | - return sourcesToAbsolute(basePath, parsedData); | ||
| 151 | + return sourcesToAbsolute(sourceURL, parsedData); | ||
| 155 | 152 | } catch (err) { | |
| 156 | 153 | debug(err.stack); | |
| 157 | 154 | return null; | |
@@ -165,14 +162,10 @@ function sourceMapFromDataUrl(basePath, url) { | |||
| 165 | 162 | // If the sources are not absolute URLs after prepending of the "sourceRoot", | |
| 166 | 163 | // the sources are resolved relative to the SourceMap (like resolving script | |
| 167 | 164 | // src in a html document). | |
| 168 | - function sourcesToAbsolute(base, data) { | ||
| 165 | + function sourcesToAbsolute(baseURL, data) { | ||
| 169 | 166 | data.sources = data.sources.map((source) => { | |
| 170 | 167 | source = (data.sourceRoot || '') + source; | |
| 171 | - if (!/^[\\/]/.test(source[0])) { | ||
| 172 | - source = resolve(base, source); | ||
| 173 | - } | ||
| 174 | - if (!source.startsWith('file://')) source = `file://${source}`; | ||
| 175 | - return source; | ||
| 168 | + return new URL(source, baseURL).href; | ||
| 176 | 169 | }); | |
| 177 | 170 | // The sources array is now resolved to absolute URLs, sourceRoot should | |
| 178 | 171 | // be updated to noop. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ const { dirname } = require('path'); | |||
| 8 | 8 | const fs = require('fs'); | |
| 9 | 9 | const path = require('path'); | |
| 10 | 10 | const { spawnSync } = require('child_process'); | |
| 11 | + const { pathToFileURL } = require('url'); | ||
| 11 | 12 | ||
| 12 | 13 | const tmpdir = require('../common/tmpdir'); | |
| 13 | 14 | tmpdir.refresh(); | |
@@ -88,8 +89,8 @@ function nextdir() { | |||
| 88 | 89 | // Source-map should have been loaded from disk and sources should have been | |
| 89 | 90 | // rewritten, such that they're absolute paths. | |
| 90 | 91 | assert.strictEqual( | |
| 91 | - dirname( | ||
| 92 | - `file://${require.resolve('../fixtures/source-map/disk-relative-path')}`), | ||
| 92 | + dirname(pathToFileURL( | ||
| 93 | + require.resolve('../fixtures/source-map/disk-relative-path')).href), | ||
| 93 | 94 | dirname(sourceMap.data.sources[0]) | |
| 94 | 95 | ); | |
| 95 | 96 | } | |
@@ -109,8 +110,8 @@ function nextdir() { | |||
| 109 | 110 | // base64 JSON should have been decoded, and paths to sources should have | |
| 110 | 111 | // been rewritten such that they're absolute: | |
| 111 | 112 | assert.strictEqual( | |
| 112 | - dirname( | ||
| 113 | - `file://${require.resolve('../fixtures/source-map/inline-base64')}`), | ||
| 113 | + dirname(pathToFileURL( | ||
| 114 | + require.resolve('../fixtures/source-map/inline-base64')).href), | ||
| 114 | 115 | dirname(sourceMap.data.sources[0]) | |
| 115 | 116 | ); | |
| 116 | 117 | } | |
@@ -265,6 +266,23 @@ function nextdir() { | |||
| 265 | 266 | ); | |
| 266 | 267 | } | |
| 267 | 268 | ||
| 269 | + // Does not attempt to apply path resolution logic to absolute URLs | ||
| 270 | + // with schemes. | ||
| 271 | + // Refs: https://github.com/webpack/webpack/issues/9601 | ||
| 272 | + // Refs: https://sourcemaps.info/spec.html#h.75yo6yoyk7x5 | ||
| 273 | + { | ||
| 274 | + const output = spawnSync(process.execPath, [ | ||
| 275 | + '--enable-source-maps', | ||
| 276 | + require.resolve('../fixtures/source-map/webpack.js') | ||
| 277 | + ]); | ||
| 278 | + // Error in original context of source content: | ||
| 279 | + assert.ok( | ||
| 280 | + output.stderr.toString().match(/throw new Error\('oh no!'\)\r?\n.*\^/) | ||
| 281 | + ); | ||
| 282 | + // Rewritten stack trace: | ||
| 283 | + assert.ok(output.stderr.toString().includes('webpack:///webpack.js:14:9')); | ||
| 284 | + } | ||
| 285 | + | ||
| 268 | 286 | function getSourceMapFromCache(fixtureFile, coverageDirectory) { | |
| 269 | 287 | const jsonFiles = fs.readdirSync(coverageDirectory); | |
| 270 | 288 | for (const jsonFile of jsonFiles) { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments