From 1d674cf3c84dc6fb5e672cb0e03de7662fffad48 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 8 May 2022 12:46:24 -0400 Subject: [PATCH 1/6] fix --- source-map-support.js | 44 ++++++++++++++++++++++++++++++------------- test.js | 26 ++++++++++++++++++++----- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/source-map-support.js b/source-map-support.js index ad830b6..ed0fe77 100644 --- a/source-map-support.js +++ b/source-map-support.js @@ -554,6 +554,14 @@ function cloneCallSite(frame) { return object; } +// Fix position in Node where some (internal) code is prepended. +// See https://github.com/evanw/node-source-map-support/issues/36 +// Header removed in node at ^10.16 || >=11.11.0 +// v11 is not an LTS candidate, we can just test the one version with it. +// Test node versions for: 10.16-19, 10.20+, 12-19, 20-99, 100+, or 11.11 +const noHeader = /^v(10\.1[6-9]|10\.[2-9][0-9]|10\.[0-9]{3,}|1[2-9]\d*|[2-9]\d|\d{3,}|11\.11)/; +const headerLength = isInBrowser() ? 0 : (noHeader.test(process.version) ? 0 : 62); + function wrapCallSite(frame, state) { // provides interface backward compatibility if (state === undefined) { @@ -577,31 +585,41 @@ function wrapCallSite(frame, state) { var line = frame.getLineNumber(); var column = frame.getColumnNumber() - 1; - - // Fix position in Node where some (internal) code is prepended. - // See https://github.com/evanw/node-source-map-support/issues/36 - // Header removed in node at ^10.16 || >=11.11.0 - // v11 is not an LTS candidate, we can just test the one version with it. - // Test node versions for: 10.16-19, 10.20+, 12-19, 20-99, 100+, or 11.11 - var noHeader = /^v(10\.1[6-9]|10\.[2-9][0-9]|10\.[0-9]{3,}|1[2-9]\d*|[2-9]\d|\d{3,}|11\.11)/; - var headerLength = noHeader.test(process.version) ? 0 : 62; - if (line === 1 && column > headerLength && !isInBrowser() && !frame.isEval()) { + if (line === 1 && column > headerLength && !frame.isEval()) { column -= headerLength; } - var position = mapSourcePosition({ source: source, line: line, column: column }); + + var enclosingLine = frame.getEnclosingLineNumber(); + var enclosingColumn = frame.getEnclosingColumnNumber() - 1; + var enclosingPosition = mapSourcePosition({ + source: source, + line: enclosingLine, + column: enclosingColumn + }); + state.curPosition = position; + const nextPosition = state.nextPosition; + frame = cloneCallSite(frame); + + // Check for function name in this order: + // enclosing position, then runtime function name, finally fallback to callsite (subsequent stack frame's position) var originalFunctionName = frame.getFunctionName; frame.getFunctionName = function() { - if (state.nextPosition == null) { - return originalFunctionName(); + const enclosingName = enclosingPosition.name; + if(enclosingName != null) return enclosingName; + const originalName = originalFunctionName(); + if(originalName != null) return originalName; + if (nextPosition != null) { + const nextPositionName = nextPosition.name; + if(nextPositionName != null) return nextPositionName; } - return state.nextPosition.name || originalFunctionName(); + return null; }; frame.getFileName = function() { return position.source; }; frame.getLineNumber = function() { return position.line; }; diff --git a/test.js b/test.js index 7dabc6c..2897813 100644 --- a/test.js +++ b/test.js @@ -597,18 +597,34 @@ it('finds the last sourceMappingURL', async function() { it('maps original name from source', async function() { var sourceMap = createEmptySourceMap(); + + // Note: related to discussion here: https://github.com/facebook/jest/pull/12786#issuecomment-1116380918 + // Mapping is at start of the `function` keyword because that is the "enclosing" position of the first stack frame. + // This is technically wrong; enclosing names should not use the sourcemap "names"; they should use the new type of + // "names" mapping being proposed here: https://github.com/source-map/source-map-rfc/issues/12 + + // Position of `function foo` + sourceMap.addMapping({ + generated: { line: 2, column: 0 }, + original: { line: 1002, column: 1 }, + source: `.original-${id}.js`, + name: "myOriginalName" + }); + // Position of `new Error` sourceMap.addMapping({ - generated: { line: 2, column: 8 }, + generated: { line: 3, column: 8 }, original: { line: 1000, column: 10 }, source: `.original-${id}.js`, }); + // Position of `foo();` sourceMap.addMapping({ - generated: { line: 4, column: 0 }, - original: { line: 1002, column: 1 }, + generated: { line: 5, column: 0 }, + original: { line: 1003, column: 1 }, source: `.original-${id}.js`, - name: "myOriginalName" + name: "myOriginalCallsiteName" }); await compareStackTrace(sourceMap, [ + '', 'function foo() {', ' throw new Error("test");', '}', @@ -616,7 +632,7 @@ it('maps original name from source', async function() { ], [ 'Error: test', re`^ at myOriginalName \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1000:11\)$`, - re`^ at ${stackFrameAtTest()} \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1002:2\)$` + re`^ at ${stackFrameAtTest()} \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1003:2\)$` ]); }); From f0279cd8b4529f412d5a1d53ececab4cd5bf06e4 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 8 May 2022 13:00:59 -0400 Subject: [PATCH 2/6] fix support on old node versions --- source-map-support.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/source-map-support.js b/source-map-support.js index ed0fe77..256a7de 100644 --- a/source-map-support.js +++ b/source-map-support.js @@ -594,13 +594,17 @@ function wrapCallSite(frame, state) { column: column }); - var enclosingLine = frame.getEnclosingLineNumber(); - var enclosingColumn = frame.getEnclosingColumnNumber() - 1; - var enclosingPosition = mapSourcePosition({ - source: source, - line: enclosingLine, - column: enclosingColumn - }); + let enclosingPosition = null; + // Was added in node 14.17 + if(frame.getEnclosingLineNumber) { + var enclosingLine = frame.getEnclosingLineNumber(); + var enclosingColumn = frame.getEnclosingColumnNumber() - 1; + enclosingPosition = mapSourcePosition({ + source: source, + line: enclosingLine, + column: enclosingColumn + }); + } state.curPosition = position; const nextPosition = state.nextPosition; From e7819238ab11e038854028df3b45d2846a73afae Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 8 May 2022 13:07:45 -0400 Subject: [PATCH 3/6] fix --- source-map-support.js | 10 +++++----- test.js | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/source-map-support.js b/source-map-support.js index 256a7de..2fe83e8 100644 --- a/source-map-support.js +++ b/source-map-support.js @@ -594,16 +594,17 @@ function wrapCallSite(frame, state) { column: column }); - let enclosingPosition = null; + let enclosingName = null; // Was added in node 14.17 if(frame.getEnclosingLineNumber) { - var enclosingLine = frame.getEnclosingLineNumber(); - var enclosingColumn = frame.getEnclosingColumnNumber() - 1; - enclosingPosition = mapSourcePosition({ + const enclosingLine = frame.getEnclosingLineNumber(); + const enclosingColumn = frame.getEnclosingColumnNumber() - 1; + const enclosingPosition = mapSourcePosition({ source: source, line: enclosingLine, column: enclosingColumn }); + if(enclosingPosition) enclosingName = enclosingPosition.name; } state.curPosition = position; @@ -615,7 +616,6 @@ function wrapCallSite(frame, state) { // enclosing position, then runtime function name, finally fallback to callsite (subsequent stack frame's position) var originalFunctionName = frame.getFunctionName; frame.getFunctionName = function() { - const enclosingName = enclosingPosition.name; if(enclosingName != null) return enclosingName; const originalName = originalFunctionName(); if(originalName != null) return originalName; diff --git a/test.js b/test.js index 2897813..6aef456 100644 --- a/test.js +++ b/test.js @@ -623,6 +623,7 @@ it('maps original name from source', async function() { source: `.original-${id}.js`, name: "myOriginalCallsiteName" }); + const expectedFunctionName = semver.gte(process.version, '14.17.0') ? 'myOriginalName' : 'foo'; await compareStackTrace(sourceMap, [ '', 'function foo() {', @@ -631,7 +632,7 @@ it('maps original name from source', async function() { 'foo();' ], [ 'Error: test', - re`^ at myOriginalName \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1000:11\)$`, + re`^ at ${expectedFunctionName} \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1000:11\)$`, re`^ at ${stackFrameAtTest()} \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1003:2\)$` ]); }); From 4bfe5cbbb90b012eace6147a9de5f1a587b36ac5 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 8 May 2022 13:34:58 -0400 Subject: [PATCH 4/6] make callsite fallback configurable --- source-map-support.d.ts | 7 +++++++ source-map-support.js | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/source-map-support.d.ts b/source-map-support.d.ts index d8cb9d8..21c1055 100755 --- a/source-map-support.d.ts +++ b/source-map-support.d.ts @@ -50,6 +50,13 @@ export interface Options { * @param options options object internally passed to node's `_resolveFilename` hook */ onConflictingLibraryRedirect?: (request: string, parent: any, isMain: boolean, options: any, redirectedRequest: string) => void; + + /** + * If enabled, will fallback to getting mapped function name from callsite (from the following stack frame) when neither + * enclosing position nor runtime have a function name. + * Default: true + */ + callsiteFallback?: boolean; } export interface Position { diff --git a/source-map-support.js b/source-map-support.js index 2fe83e8..46f91e0 100644 --- a/source-map-support.js +++ b/source-map-support.js @@ -92,6 +92,10 @@ var sharedData = initializeSharedData({ // If true, the caches are reset before a stack trace formatting operation emptyCacheBetweenOperations: false, + // If true, will fallback to getting mapped function name from callsite (from the following stack frame) when neither + // enclosing position nor runtime have a function name. + callsiteFallback: true, + // Maps a file path to a string containing the file contents fileContentsCache: Object.create(null), @@ -619,7 +623,7 @@ function wrapCallSite(frame, state) { if(enclosingName != null) return enclosingName; const originalName = originalFunctionName(); if(originalName != null) return originalName; - if (nextPosition != null) { + if (sharedData.callsiteFallback && nextPosition != null) { const nextPositionName = nextPosition.name; if(nextPositionName != null) return nextPositionName; } @@ -917,6 +921,10 @@ exports.install = function(options) { shimEmitUncaughtException(); } } + + if (typeof options.callsiteFallback === 'boolean') { + sharedData.callsiteFallback = options.callsiteFallback; + } }; exports.uninstall = function() { From c7e5dc809d1b36dc3d010a5563e107acfd111888 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 8 May 2022 15:29:25 -0400 Subject: [PATCH 5/6] remove enclosing position mapping, but keep the callsiteFallback option --- source-map-support.js | 16 +--------------- test.js | 29 ++++++----------------------- 2 files changed, 7 insertions(+), 38 deletions(-) diff --git a/source-map-support.js b/source-map-support.js index 46f91e0..5c48b36 100644 --- a/source-map-support.js +++ b/source-map-support.js @@ -598,29 +598,15 @@ function wrapCallSite(frame, state) { column: column }); - let enclosingName = null; - // Was added in node 14.17 - if(frame.getEnclosingLineNumber) { - const enclosingLine = frame.getEnclosingLineNumber(); - const enclosingColumn = frame.getEnclosingColumnNumber() - 1; - const enclosingPosition = mapSourcePosition({ - source: source, - line: enclosingLine, - column: enclosingColumn - }); - if(enclosingPosition) enclosingName = enclosingPosition.name; - } - state.curPosition = position; const nextPosition = state.nextPosition; frame = cloneCallSite(frame); // Check for function name in this order: - // enclosing position, then runtime function name, finally fallback to callsite (subsequent stack frame's position) + // runtime function name, then fallback to callsite (subsequent stack frame's position) var originalFunctionName = frame.getFunctionName; frame.getFunctionName = function() { - if(enclosingName != null) return enclosingName; const originalName = originalFunctionName(); if(originalName != null) return originalName; if (sharedData.callsiteFallback && nextPosition != null) { diff --git a/test.js b/test.js index 6aef456..7dabc6c 100644 --- a/test.js +++ b/test.js @@ -597,43 +597,26 @@ it('finds the last sourceMappingURL', async function() { it('maps original name from source', async function() { var sourceMap = createEmptySourceMap(); - - // Note: related to discussion here: https://github.com/facebook/jest/pull/12786#issuecomment-1116380918 - // Mapping is at start of the `function` keyword because that is the "enclosing" position of the first stack frame. - // This is technically wrong; enclosing names should not use the sourcemap "names"; they should use the new type of - // "names" mapping being proposed here: https://github.com/source-map/source-map-rfc/issues/12 - - // Position of `function foo` - sourceMap.addMapping({ - generated: { line: 2, column: 0 }, - original: { line: 1002, column: 1 }, - source: `.original-${id}.js`, - name: "myOriginalName" - }); - // Position of `new Error` sourceMap.addMapping({ - generated: { line: 3, column: 8 }, + generated: { line: 2, column: 8 }, original: { line: 1000, column: 10 }, source: `.original-${id}.js`, }); - // Position of `foo();` sourceMap.addMapping({ - generated: { line: 5, column: 0 }, - original: { line: 1003, column: 1 }, + generated: { line: 4, column: 0 }, + original: { line: 1002, column: 1 }, source: `.original-${id}.js`, - name: "myOriginalCallsiteName" + name: "myOriginalName" }); - const expectedFunctionName = semver.gte(process.version, '14.17.0') ? 'myOriginalName' : 'foo'; await compareStackTrace(sourceMap, [ - '', 'function foo() {', ' throw new Error("test");', '}', 'foo();' ], [ 'Error: test', - re`^ at ${expectedFunctionName} \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1000:11\)$`, - re`^ at ${stackFrameAtTest()} \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1003:2\)$` + re`^ at myOriginalName \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1000:11\)$`, + re`^ at ${stackFrameAtTest()} \(${stackFramePathStartsWith()}(?:.*[/\\])?\.original-${id}.js:1002:2\)$` ]); }); From 2bf19fc732cae7a42116a764ffce087b9dda471a Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 8 May 2022 15:32:17 -0400 Subject: [PATCH 6/6] fix test --- test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test.js b/test.js index 7dabc6c..7c870ae 100644 --- a/test.js +++ b/test.js @@ -598,20 +598,21 @@ it('finds the last sourceMappingURL', async function() { it('maps original name from source', async function() { var sourceMap = createEmptySourceMap(); sourceMap.addMapping({ - generated: { line: 2, column: 8 }, + generated: { line: 3, column: 8 }, original: { line: 1000, column: 10 }, source: `.original-${id}.js`, }); sourceMap.addMapping({ - generated: { line: 4, column: 0 }, + generated: { line: 5, column: 0 }, original: { line: 1002, column: 1 }, source: `.original-${id}.js`, name: "myOriginalName" }); await compareStackTrace(sourceMap, [ - 'function foo() {', + 'function identity(v) { return v }', + 'const foo = identity(function () {', ' throw new Error("test");', - '}', + '})', 'foo();' ], [ 'Error: test',