Simon Priet 9e2991e668 init
2021-09-05 22:53:58 +02:00

181 lines
4.6 KiB
JavaScript

// use a already existing formater or fallback to the default v8 formater
var defaultFormater = require('./format.js');
var originalCallSiteSymbol = Symbol('originalCallSite');
var mutatedCallSiteSymbol = Symbol('mutatedCallSite');
// public define API
function stackChain() {
this.extend = new TraceModifier();
this.filter = new TraceModifier();
this.format = new StackFormater();
this.version = require('./package.json').version;
}
var SHORTCIRCUIT_CALLSITE = false;
stackChain.prototype.callSite = function collectCallSites(options) {
if (!options) options = {};
// Get CallSites
SHORTCIRCUIT_CALLSITE = true;
var obj = {};
Error.captureStackTrace(obj, collectCallSites);
var callSites = obj.stack;
SHORTCIRCUIT_CALLSITE = false;
// Slice
callSites = callSites.slice(options.slice || 0);
// Modify CallSites
if (options.extend) callSites = this.extend._modify(obj, callSites);
if (options.filter) callSites = this.filter._modify(obj, callSites);
// Done
return callSites;
};
stackChain.prototype.originalCallSite = function (error) {
error.stack;
return error[originalCallSiteSymbol];
};
stackChain.prototype.mutatedCallSite = function (error) {
error.stack;
return error[mutatedCallSiteSymbol];
};
var chain = new stackChain();
function TraceModifier() {
this._modifiers = [];
}
TraceModifier.prototype._modify = function (error, frames) {
for (var i = 0, l = this._modifiers.length; i < l; i++) {
frames = this._modifiers[i](error, frames);
}
return frames;
};
TraceModifier.prototype.attach = function (modifier) {
this._modifiers.push(modifier);
};
TraceModifier.prototype.deattach = function (modifier) {
var index = this._modifiers.indexOf(modifier);
if (index === -1) return false;
this._modifiers.splice(index, 1);
return true;
};
function StackFormater() {
this._formater = defaultFormater;
this._previous = undefined;
}
StackFormater.prototype.replace = function (formater) {
if (formater) {
this._formater = formater;
} else {
this.restore();
}
};
StackFormater.prototype.restore = function () {
this._formater = defaultFormater;
this._previous = undefined;
};
StackFormater.prototype._backup = function () {
this._previous = this._formater;
};
StackFormater.prototype._roolback = function () {
if (this._previous === defaultFormater) {
this.replace(undefined);
} else {
this.replace(this._previous);
}
this._previous = undefined;
};
//
// Set Error.prepareStackTrace thus allowing stack-chain
// to take control of the Error().stack formating.
//
// If there already is a custom stack formater, then set
// that as the stack-chain formater.
if (Error.prepareStackTrace) {
chain.format.replace(Error.prepareStackTrace);
}
var SHORTCIRCUIT_FORMATER = false;
function prepareStackTrace(error, originalFrames) {
if (SHORTCIRCUIT_CALLSITE) return originalFrames;
if (SHORTCIRCUIT_FORMATER) return defaultFormater(error, originalFrames);
// Make a loss copy of originalFrames
var frames = originalFrames.concat();
// extend frames
frames = chain.extend._modify(error, frames);
// filter frames
frames = chain.filter._modify(error, frames);
// reduce frames to match Error.stackTraceLimit
frames = frames.slice(0, Error.stackTraceLimit);
// Set the callSite property
// But only if it hasn't been explicitly set, otherwise
// error.stack would have unintended side effects. Check also for
// non-extensible/sealed objects, such as those from Google's Closure Library
if (Object.isExtensible(error)) {
error[originalCallSiteSymbol] = originalFrames;
error[mutatedCallSiteSymbol] = frames;
}
// format frames
SHORTCIRCUIT_FORMATER = true;
var format = chain.format._formater(error, frames);
SHORTCIRCUIT_FORMATER = false;
return format;
}
// Replace the v8 stack trace creator
Object.defineProperty(Error, 'prepareStackTrace', {
'get': function () {
return prepareStackTrace;
},
'set': function (formater) {
// If formater is prepareStackTrace it means that someone ran
// var old = Error.prepareStackTrace;
// Error.prepareStackTrace = custom
// new Error().stack
// Error.prepareStackTrace = old;
// The effect of this, should be that the old behaviour is restored.
if (formater === prepareStackTrace) {
chain.format._roolback();
}
// Error.prepareStackTrace was set, this means that someone is
// trying to take control of the Error().stack format. Make
// them belive they succeeded by setting them up as the stack-chain
// formater.
else {
chain.format._backup();
chain.format.replace(formater);
}
}
});
module.exports = chain;