p5
Version:
[](https://www.npmjs.com/package/p5)
251 lines (222 loc) • 8.62 kB
JavaScript
/**
* @for p5
* @requires core
*/
// Borrow from stacktracejs https://github.com/stacktracejs/stacktrace.js with
// minor modifications. The license for the same and the code is included below
// Copyright (c) 2017 Eric Wendelin and other contributors
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
function ErrorStackParser() {
let FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/;
let CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
let SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/;
return {
/**
* Given an Error object, extract the most information from it.
* @private
* @param {Error} error object
* @return {Array} of stack frames
*/
parse: function ErrorStackParser$$parse(error) {
if (
typeof error.stacktrace !== 'undefined' ||
typeof error['opera#sourceloc'] !== 'undefined'
) {
return this.parseOpera(error);
} else if (error.stack && error.stack.match(CHROME_IE_STACK_REGEXP)) {
return this.parseV8OrIE(error);
} else if (error.stack) {
return this.parseFFOrSafari(error);
} else ;
},
// Separate line and column numbers from a string of the form: (URI:Line:Column)
extractLocation: function ErrorStackParser$$extractLocation(urlLike) {
// Fail-fast but return locations like "(native)"
if (urlLike.indexOf(':') === -1) {
return [urlLike];
}
let regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
let parts = regExp.exec(urlLike.replace(/[()]/g, ''));
return [parts[1], parts[2] || undefined, parts[3] || undefined];
},
parseV8OrIE: function ErrorStackParser$$parseV8OrIE(error) {
let filtered = error.stack.split('\n').filter(function(line) {
return !!line.match(CHROME_IE_STACK_REGEXP);
}, this);
return filtered.map(function(line) {
if (line.indexOf('(eval ') > -1) {
// Throw away eval information until we implement stacktrace.js/stackframe#8
line = line
.replace(/eval code/g, 'eval')
.replace(/(\(eval at [^()]*)|(\),.*$)/g, '');
}
let sanitizedLine = line
.replace(/^\s+/, '')
.replace(/\(eval code/g, '(');
// capture and preseve the parenthesized location "(/foo/my bar.js:12:87)" in
// case it has spaces in it, as the string is split on \s+ later on
let location = sanitizedLine.match(/ (\((.+):(\d+):(\d+)\)$)/);
// remove the parenthesized location from the line, if it was matched
sanitizedLine = location
? sanitizedLine.replace(location[0], '')
: sanitizedLine;
let tokens = sanitizedLine.split(/\s+/).slice(1);
// if a location was matched, pass it to extractLocation() otherwise pop the last token
let locationParts = this.extractLocation(
location ? location[1] : tokens.pop()
);
let functionName = tokens.join(' ') || undefined;
let fileName =
['eval', '<anonymous>'].indexOf(locationParts[0]) > -1
? undefined
: locationParts[0];
return {
functionName,
fileName,
lineNumber: locationParts[1],
columnNumber: locationParts[2],
source: line
};
}, this);
},
parseFFOrSafari: function ErrorStackParser$$parseFFOrSafari(error) {
let filtered = error.stack.split('\n').filter(function(line) {
return !line.match(SAFARI_NATIVE_CODE_REGEXP);
}, this);
return filtered.map(function(line) {
// Throw away eval information until we implement stacktrace.js/stackframe#8
if (line.indexOf(' > eval') > -1) {
line = line.replace(
/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g,
':$1'
);
}
if (line.indexOf('@') === -1 && line.indexOf(':') === -1) {
// Safari eval frames only have function names and nothing else
return {
functionName: line
};
} else {
let functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/;
let matches = line.match(functionNameRegex);
let functionName = matches && matches[1] ? matches[1] : undefined;
let locationParts = this.extractLocation(
line.replace(functionNameRegex, '')
);
return {
functionName,
fileName: locationParts[0],
lineNumber: locationParts[1],
columnNumber: locationParts[2],
source: line
};
}
}, this);
},
parseOpera: function ErrorStackParser$$parseOpera(e) {
if (
!e.stacktrace ||
(e.message.indexOf('\n') > -1 &&
e.message.split('\n').length > e.stacktrace.split('\n').length)
) {
return this.parseOpera9(e);
} else if (!e.stack) {
return this.parseOpera10(e);
} else {
return this.parseOpera11(e);
}
},
parseOpera9: function ErrorStackParser$$parseOpera9(e) {
let lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
let lines = e.message.split('\n');
let result = [];
for (let i = 2, len = lines.length; i < len; i += 2) {
let match = lineRE.exec(lines[i]);
if (match) {
result.push({
fileName: match[2],
lineNumber: match[1],
source: lines[i]
});
}
}
return result;
},
parseOpera10: function ErrorStackParser$$parseOpera10(e) {
let lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
let lines = e.stacktrace.split('\n');
let result = [];
for (let i = 0, len = lines.length; i < len; i += 2) {
let match = lineRE.exec(lines[i]);
if (match) {
result.push({
functionName: match[3] || undefined,
fileName: match[2],
lineNumber: match[1],
source: lines[i]
});
}
}
return result;
},
// Opera 10.65+ Error.stack very similar to FF/Safari
parseOpera11: function ErrorStackParser$$parseOpera11(error) {
let filtered = error.stack.split('\n').filter(function(line) {
return (
!!line.match(FIREFOX_SAFARI_STACK_REGEXP) &&
!line.match(/^Error created at/)
);
}, this);
return filtered.map(function(line) {
let tokens = line.split('@');
let locationParts = this.extractLocation(tokens.pop());
let functionCall = tokens.shift() || '';
let functionName =
functionCall
.replace(/<anonymous function(: (\w+))?>/, '$2')
.replace(/\([^)]*\)/g, '') || undefined;
let argsRaw;
if (functionCall.match(/\(([^)]*)\)/)) {
argsRaw = functionCall.replace(/^[^(]+\(([^)]*)\)$/, '$1');
}
let args =
argsRaw === undefined || argsRaw === '[arguments not available]'
? undefined
: argsRaw.split(',');
return {
functionName,
args,
fileName: locationParts[0],
lineNumber: locationParts[1],
columnNumber: locationParts[2],
source: line
};
}, this);
}
};
}
// End borrow
// wrapper exposing ErrorStackParser
function stacktrace(p5, fn){
p5._getErrorStackParser = function getErrorStackParser() {
return new ErrorStackParser();
};
}
if (typeof p5 !== 'undefined') {
stacktrace(p5, p5.prototype);
}
export { stacktrace as default };