You can trace function calls with help of putout code transformer. Plugin will look this way:
const {template, types, operator} = require('putout');
const {replaceWith} = operator;
const {BlockStatement} = types;
// create nodes
const buildLog = template(`console.log('TYPE' + ' ' + 'NAME')`);
const buildLogEnter = template(`console.log('enter' + ' ' + 'NAME' + '(' + JSON.stringify(Array.from(arguments)) + ')')`);
const buildLogException = template(`console.log('TYPE' + ' ' + 'NAME' + ': ' + trace$error.message); throw trace$error`);
const buildTryCatch = template(`try {
BLOCK;
} catch(trace$error) {
CATCH;
} finally {
FINALLY;
}
`);
const JSON = 'JSON';
// nodes we are searching for
module.exports.include = () => [
'Function',
];
module.exports.fix = (path) => {
const name = getName(path);
// create 3 types of events
const enterLog = buildLogEnter({
NAME: name,
JSON,
});
const exitLog = buildLogEvent(name, 'exit');
const errorLog = buildLogExceptionEvent(name);
// move function body into try-catch
const bodyPath = path.get('body');
replaceWith(bodyPath, BlockStatement([buildTryCatch({
BLOCK: path.node.body.body,
CATCH: errorLog,
FINALLY: exitLog,
})]));
// add into the beginning of function "console.log" with "enter" event
bodyPath.node.body.unshift(enterLog);
};
// get name of a function
function getName(path) {
if (path.isClassMethod())
return path.node.key.name;
if (path.isFunctionDeclaration())
return path.node.id.name;
const {line} = path.node.loc.start;
return `<anonymous:${line}>`;
}
// build logger
function buildLogEvent(name, type) {
return buildLog({
NAME: name,
TYPE: type,
});
}
// build logger that throws
function buildLogExceptionEvent(name) {
return buildLogException({
NAME: name,
TYPE: 'error',
});
}
Let's suppose that this is the code you want to trace:
const processFile = (a) => a;
process([]);
function process(runners) {
const files = getFiles(runners);
const linted = lintFiles(files);
return linted;
}
function getFiles(runners) {
const files = [];
for (const run of runners) {
files.push(...run());
}
return files;
}
function lintFiles(files) {
const linted = [];
for (const file of files) {
linted.push(processFile(file));
}
return linted;
}
Here is a full picture:

If you save processed source as trace.js and run it with node, you will have:
> node trace.js
enter process([[]])
enter getFiles([[]])
exit getFiles
enter lintFiles([[]])
exit lintFiles
exit process
There is putout issue related to tracing functions.
console.trace()in one place instead ofconsole.login many places. The stack will then appear in the developer tools. Is that an acceptable solution?