Skip to content

Commit ee663cc

Browse files
Ugaitz UrienCarlesDD
andauthored
Fix stack traces when --enable-source-maps is enabled (#140)
--------- Co-authored-by: Carles Capell <107924659+CarlesDD@users.noreply.github.com>
1 parent 99bd06c commit ee663cc

8 files changed

Lines changed: 141 additions & 63 deletions

File tree

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ samples
22
test/resources/**
33
index.d.ts
44
wasm
5+
integration-test/requires/error-typescript.js
6+
integration-test/requires/error-typescript.ts

integration-test/init-rewriter.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use strict'
2+
3+
const rewriterPackage = process.env.NPM_REWRITER === 'true' ? '@datadog/wasm-js-rewriter' : '../'
4+
const { addEditedFile } = require('./edited-files-cache')
5+
const { Rewriter, getPrepareStackTrace } = require(rewriterPackage)
6+
const path = require('path')
7+
const Module = require('module')
8+
9+
let rewriter
10+
let originalPrepareStackTrace = Error.prepareStackTrace
11+
12+
const CSI_METHODS = [
13+
{ src: 'plusOperator', operator: true },
14+
{ src: 'substring' },
15+
{ src: 'trim' },
16+
{ src: 'trimStart' },
17+
{ src: 'trimEnd' },
18+
{ src: 'trimLeft' },
19+
{ src: 'trimRight' },
20+
{ src: 'toLowerCase' },
21+
{ src: 'toLocaleLowerCase' },
22+
{ src: 'toUpperCase' },
23+
{ src: 'toLocaleUpperCase' },
24+
{ src: 'replace' },
25+
{ src: 'replaceAll' },
26+
{ src: 'slice' },
27+
{ src: 'concat' }
28+
]
29+
30+
function isFlagPresent (flag) {
31+
return process.env.NODE_OPTIONS?.includes(flag) ||
32+
process.execArgv?.some(arg => arg.includes(flag))
33+
}
34+
35+
function initRewriter () {
36+
rewriter = new Rewriter({
37+
csiMethods: CSI_METHODS,
38+
telemetryVerbosity: 'Debug',
39+
chainSourceMap: isFlagPresent('--enable-source-maps')
40+
})
41+
42+
if (rewriter) {
43+
Object.defineProperty(global.Error, 'prepareStackTrace', getPrepareStackTraceAccessor())
44+
Module.prototype._compile = getCompileMethodFn(Module.prototype._compile)
45+
}
46+
}
47+
48+
function getPrepareStackTraceAccessor () {
49+
let actual = getPrepareStackTrace(originalPrepareStackTrace)
50+
return {
51+
get () {
52+
return actual
53+
},
54+
set (value) {
55+
actual = getPrepareStackTrace(value)
56+
originalPrepareStackTrace = value
57+
}
58+
}
59+
}
60+
61+
function getCompileMethodFn (compileMethod) {
62+
return function (content, filename) {
63+
try {
64+
if (filename.indexOf(path.join('integration-test', 'requires')) > -1) {
65+
const response = rewriter.rewrite(content, filename, ['iast'])
66+
content = response.content
67+
addEditedFile(filename)
68+
}
69+
} catch (e) {
70+
// eslint-disable-next-line no-console
71+
console.error(e)
72+
}
73+
return compileMethod.apply(this, [content, filename])
74+
}
75+
}
76+
77+
initRewriter()

integration-test/requires/error-typescript.js

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integration-test/requires/error-typescript.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function main (arg: string) {
2+
console.log(new Error('Error: ' + arg))
3+
}
4+
5+
main('arg')

integration-test/setup.js

Lines changed: 2 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,74 +5,13 @@
55

66
'use strict'
77

8+
require('./init-rewriter')
9+
810
const chai = require('chai')
911
const sinon = require('sinon')
1012
const sinonChai = require('sinon-chai')
11-
const rewriterPackage = process.env.NPM_REWRITER === 'true' ? '@datadog/wasm-js-rewriter' : '../'
12-
const { Rewriter, getPrepareStackTrace } = require(rewriterPackage)
13-
const path = require('path')
14-
const Module = require('module')
15-
const { addEditedFile } = require('./edited-files-cache')
16-
let rewriter
17-
let originalPrepareStackTrace = Error.prepareStackTrace
1813

1914
chai.use(sinonChai)
2015

2116
global.expect = chai.expect
2217
global.sinon = sinon
23-
24-
const CSI_METHODS = [
25-
{ src: 'plusOperator', operator: true },
26-
{ src: 'substring' },
27-
{ src: 'trim' },
28-
{ src: 'trimStart' },
29-
{ src: 'trimEnd' },
30-
{ src: 'trimLeft' },
31-
{ src: 'trimRight' },
32-
{ src: 'toLowerCase' },
33-
{ src: 'toLocaleLowerCase' },
34-
{ src: 'toUpperCase' },
35-
{ src: 'toLocaleUpperCase' },
36-
{ src: 'replace' },
37-
{ src: 'replaceAll' },
38-
{ src: 'slice' },
39-
{ src: 'concat' }
40-
]
41-
42-
initRewriter()
43-
44-
function initRewriter () {
45-
rewriter = new Rewriter({ csiMethods: CSI_METHODS, telemetryVerbosity: 'Debug' })
46-
if (rewriter) {
47-
Object.defineProperty(global.Error, 'prepareStackTrace', getPrepareStackTraceAccessor())
48-
Module.prototype._compile = getCompileMethodFn(Module.prototype._compile)
49-
}
50-
}
51-
function getPrepareStackTraceAccessor () {
52-
let actual = getPrepareStackTrace(originalPrepareStackTrace)
53-
return {
54-
get () {
55-
return actual
56-
},
57-
set (value) {
58-
actual = getPrepareStackTrace(value)
59-
originalPrepareStackTrace = value
60-
}
61-
}
62-
}
63-
64-
function getCompileMethodFn (compileMethod) {
65-
return function (content, filename) {
66-
try {
67-
if (filename.indexOf(path.join('integration-test', 'requires')) > -1) {
68-
const response = rewriter.rewrite(content, filename, ['iast'])
69-
content = response.content
70-
addEditedFile(filename)
71-
}
72-
} catch (e) {
73-
// eslint-disable-next-line no-console
74-
console.error(e)
75-
}
76-
return compileMethod.apply(this, [content, filename])
77-
}
78-
}

integration-test/stack-traces.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
/* eslint no-unused-expressions: 0 */
2+
'use strict'
3+
4+
const childProcess = require('child_process')
5+
const path = require('path')
26
const { isFileEdited } = require('./edited-files-cache')
37

48
describe('Test stack traces', () => {
@@ -33,4 +37,15 @@ describe('Test stack traces', () => {
3337
expect(isFileEdited('errors.js')).to.be.true
3438
})
3539
})
40+
41+
describe('When sourcemaps are enabled', () => {
42+
it('should calculate stack traces correctly', () => {
43+
const result = childProcess.execSync(
44+
'node --enable-source-maps --require ./init-rewriter.js ./requires/error-typescript.js',
45+
{
46+
cwd: __dirname
47+
}).toString()
48+
expect(result).to.contain(`${path.join('requires', 'error-typescript.ts')}:2:15`)
49+
})
50+
})
3651
})

js/stack-trace/index.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ const kSymbolPrepareStackTrace = Symbol('_ddiastPrepareStackTrace')
44

55
const evalRegex = /.*\(((?:.:[/\\]?)?[/\\].*):(\d*):(\d*)\)/g
66

7+
function isFlagPresent (flag) {
8+
return process.env.NODE_OPTIONS?.includes(flag) ||
9+
process.execArgv?.some(arg => arg.includes(flag))
10+
}
11+
12+
const sourceMapsEnabled = isFlagPresent('--enable-source-maps')
713
class WrappedCallSite {
814
constructor (callSite) {
915
if (callSite.isEval()) {
@@ -33,6 +39,18 @@ class WrappedCallSite {
3339
this.lineNumber = line
3440
this.columnNumber = column
3541
this.callSite = callSite
42+
43+
// Automatically support methods that are added in newer versions of Node.js
44+
const callSiteProto = Object.getPrototypeOf(callSite)
45+
const originalCallSiteMethods = Object.getOwnPropertyNames(callSiteProto)
46+
47+
originalCallSiteMethods.forEach(method => {
48+
if (method === 'constructor') return
49+
if (this[method]) return
50+
if (typeof callSiteProto[method] !== 'function') return
51+
52+
this[method] = function (...args) { return this.callSite[method](...args) }
53+
})
3654
}
3755

3856
getThis () {
@@ -56,6 +74,10 @@ class WrappedCallSite {
5674
}
5775

5876
getFileName () {
77+
return sourceMapsEnabled ? this.callSite.getFileName() : this.source
78+
}
79+
80+
getTranslatedFileName () {
5981
return this.source
6082
}
6183

@@ -64,14 +86,26 @@ class WrappedCallSite {
6486
}
6587

6688
getLineNumber () {
89+
return sourceMapsEnabled ? this.callSite.getLineNumber() : this.lineNumber
90+
}
91+
92+
getTranslatedLineNumber () {
6793
return this.lineNumber
6894
}
6995

7096
getColumnNumber () {
97+
return sourceMapsEnabled ? this.callSite.getColumnNumber() : this.columnNumber
98+
}
99+
100+
getTranslatedColumnNumber () {
71101
return this.columnNumber
72102
}
73103

74104
getEvalOrigin () {
105+
return sourceMapsEnabled ? this.callSite.getEvalOrigin() : (this.evalOrigin || this.callSite.getEvalOrigin())
106+
}
107+
108+
getTranslatedEvalOrigin () {
75109
return this.evalOrigin || this.callSite.getEvalOrigin()
76110
}
77111

0 commit comments

Comments
 (0)