Can we find out what user function called our function (in a library)?
For example, imagine two files, one exporting a function foo
and another
one calling it foo()
. Can we accurately know inside function foo
who called it?
1 | module.exports = function foo () { |
1 | const foo = require('./foo') |
1 | $ node call-foo.js |
Turns out we can! Node's V8 engine has an
stack trace API for getting
callsite information. We can easily get this information using
callsites.
Inside our function foo
we can get the file and the line number of the
caller.
1 | const callsites = require('callsites') |
1 | $ node call-foo.js |
Which tells the right information; foo()
call was from line 4 (line numbers
are reported starting with line 1) of the file call-foo.js
.
Transpiled code
Yet, there is a problem with using the above call sites if the code has
been modified by the loader. For example, we can insert a line at the
beginning of call-foo.js
before Node evaluates it. I will use my
node-hook to register
a loading callback. It will only modify call-fool.js
for simplicity.
1 | const hook = require('node-hook') |
We can register the hook when running Node
1 | $ node -r ./add-log-line.js call-foo.js |
Hmm, the call site now reports that the call foo()
happened at line 5!
The call site logic does NOT take into account the modifications done by
the code loading hooks, any code transpiled by Babel for example will have
incorrect source line and column numbers.
ES6 modules
Let us change our foo.js
to export the function "foo" using ES6 module
syntax instead of following CommonJS standard.
1 |
|
We cannot run this function directly, thus we need to transpile it on the fly, for example by using babel-node command.
1 | { |
1 | $ npm run es6 |
Due to source on the fly transpile, the foo()
call inside "call-foo.js"
file is reported incorrectly at line 6, while in reality it happens at line 5.
Accurate numbers in the error stack
Surprisingly if you throw an exception from the transpiled code, the stack
shows the correct original numbers. Modify foo.js
to throw an Error after
printing the call site.
1 |
|
1 | $ npm run es6 |
Weird, but Node environment has the right line information for the original
untranspiled location at callFoo (call-foo.js:5:3)
, probably there is a
source map somewhere. Can we verify this?
Transforming source code
We can see the loaded code after all transformations. We can do this by loading the code and running it through the loading hook callback function ourselves.
You can see the code has been transformed by inspecting the exception call
stack. See the line Object.require.extensions [as .js]
below.
1 | caller Error: on purpose |
Let us see how the "call-foo.js" looks transformed. We can just grab the current transform function and give it a fake module to grab the transformed code
1 | // ... |
Which prints almost the same source code as the original one, except it
has a source map at the end and an empty line after use strict
line.
1 | transformed code |
That blank like after 'use strict' is the source of the confusion! The call site V8 API could not use the included source map to report accurate line numbers, but the exception stack could.
How is this useful?
Knowing the accurate caller location can be useful for creating simple and powerful utilities. For example the framework-agnostic snap-shot library uses the above approach to get the caller information to find which test callback function called it. This allows "magic" zero-configuration use without relying on runner context or additional arguments.
1 | const snapshot = require('snap-shot') |
I have found the difference between locations reported using call sites and exception stack for ES6 modules during testing for that project. Luckily it is simple to grab the line numbers and filenames from the exception stack.
1 | export function foo () { |