p5
Version:
[](https://www.npmjs.com/package/p5)
1,124 lines (1,026 loc) • 40.8 kB
JavaScript
import { translator } from '../internationalization.js';
import strings from './browser_errors.js';
import { v as constants } from '../../constants-BRcElHU3.js';
import 'i18next';
import 'i18next-browser-languagedetector';
/**
* @for p5
* @requires core
*
* This is the main file for the Friendly Error System (FES), containing
* the core as well as miscellaneous functionality of the FES. Here is a
* brief outline of the functions called in this system.
*
* The FES may be invoked by a call to either
* (1) _validateParameters, (2) _friendlyFileLoadError, (3) _friendlyError,
* (4) helpForMisusedAtTopLevelCode, or (5) _fesErrorMonitor.
*
* _validateParameters is located in validate_params.js along with other code
* used for parameter validation.
* _friendlyFileLoadError is located in file_errors.js along with other code
* used for dealing with file load errors.
* Apart from this, there's also a file stacktrace.js, which contains the code
* to parse the error stack, borrowed from:
* https://github.com/stacktracejs/stacktrace.js
*
* For more detailed information on the FES functions, including the call
* sequence of each function, please look at the FES Reference + Dev Notes:
* https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md
*/
function fesCore(p5, fn){
// p5.js blue, p5.js orange, auto dark green; fallback p5.js darkened magenta
// See testColors below for all the color codes and names
const typeColors = ['#2D7BB6', '#EE9900', '#4DB200', '#C83C00'];
let misusedAtTopLevelCode = null;
let defineMisusedAtTopLevelCode = null;
// the threshold for the maximum allowed levenshtein distance
// used in misspelling detection
const EDIT_DIST_THRESHOLD = 2;
// Used for internally thrown errors that should not get wrapped by another
// friendly error handler
class FESError extends Error {}
if (typeof IS_MINIFIED !== 'undefined') {
p5._friendlyError =
p5._checkForUserDefinedFunctions =
p5._fesErrorMonitor =
() => {};
} else {
// const errorTable = require('./browser_errors').default;
// -- Borrowed from jQuery 1.11.3 --
const class2type = {};
const toString = class2type.toString;
const names = [
'Boolean',
'Number',
'String',
'Function',
'Array',
'Date',
'RegExp',
'Object',
'Error'
];
for (let n = 0; n < names.length; n++) {
class2type[`[object ${names[n]}]`] = names[n].toLowerCase();
}
const getType = obj => {
if (obj == null) {
return `${obj}`;
}
return typeof obj === 'object' || typeof obj === 'function'
? class2type[toString.call(obj)] || 'object'
: typeof obj;
};
// -- End borrow --
// entry points into user-defined code
const entryPoints = [
'setup',
'draw',
'deviceMoved',
'deviceTurned',
'deviceShaken',
'doubleClicked',
'mousePressed',
'mouseReleased',
'mouseMoved',
'mouseDragged',
'mouseClicked',
'mouseWheel',
'touchStarted',
'touchMoved',
'touchEnded',
'keyPressed',
'keyReleased',
'keyTyped',
'windowResized'
];
/**
* Takes a message and a p5 function func, and adds a link pointing to
* the reference documentation of func at the end of the message
*
* @method mapToReference
* @private
* @param {String} message the words to be said
* @param {String} [func] the name of function
*
* @returns {String}
*/
const mapToReference = (message, func) => {
let msgWithReference = '';
if (func == null || func.substring(0, 4) === 'load') {
msgWithReference = message;
} else {
const methodParts = func.split('.');
const referenceSection =
methodParts.length > 1 ? `${methodParts[0]}.${methodParts[1]}` : 'p5';
const funcName =
methodParts.length === 1 ? func : methodParts.slice(2).join('/');
//Whenever func having p5.[Class] is encountered, we need to have the error link as mentioned below else different link
funcName.startsWith('p5.') ?
msgWithReference = `${message} (https://p5js.org/reference/${referenceSection}.${funcName})` :
msgWithReference = `${message} (https://p5js.org/reference/${referenceSection}/${funcName})`;
}
return msgWithReference;
};
/**
* Prints out a fancy, colorful message to the console log
* Attaches Friendly Errors prefix [fes.pre] to the message.
*
* @method _report
* @private
* @param {String} message Message to be printed
* @param {String} [func] Name of function
* @param {Number|String} [color] CSS color code
*
* @return console logs
*/
p5._report = (message, func, color) => {
// if p5._fesLogger is set ( i.e we are running tests ), use that
// instead of console.log
const log =
p5._fesLogger == null ? console.log.bind(console) : p5._fesLogger;
if ('undefined' === getType(color)) {
color = '#B40033'; // dark magenta
} else if (getType(color) === 'number') {
// Type to color
color = typeColors[color];
}
// Add a link to the reference docs of func at the end of the message
message = mapToReference(message, func);
const prefixedMsg = translator('fes.pre', { message });
{
log(prefixedMsg);
}
};
/**
* Throws an error with helpful p5 context. Similar to _report, but
* this will stop other code execution to prevent downstream errors
* from being logged.
*
* @method _error
* @private
* @param context p5 instance the error is from
* @param {String} message Message to be printed
* @param {String} [func] Name of function
*/
p5._error = (context, message, func) => {
p5._report(message, func);
context.hitCriticalError = true;
// Throw an error to stop the current function (e.g. setup or draw) from
// running more code
throw new FESError('Stopping sketch to prevent more errors');
};
/**
* This is a generic method that can be called from anywhere in the p5
* library to alert users to a common error.
*
* @method _friendlyError
* @private
* @param {String} message Message to be printed
* @param {String} [func] Name of the function linked to error
* @param {Number|String} [color] CSS color code
*/
p5._friendlyError = function(message, func, color) {
p5._report(message, func, color);
};
/**
* This is called internally if there is an error with autoplay. Generates
* and prints a friendly error message [fes.autoplay].
*
* @method _friendlyAutoplayError
* @private
*/
p5._friendlyAutoplayError = function(src) {
const message = translator('fes.autoplay', {
src,
url: 'https://developer.mozilla.org/docs/Web/Media/Autoplay_guide'
});
console.log(translator('fes.pre', { message }));
};
/**
* Measures dissimilarity between two strings by calculating
* the Levenshtein distance.
*
* If the "distance" between them is small enough, it is
* reasonable to think that one is the misspelled version of the other.
*
* Specifically, this uses the Wagner–Fischer algorithm.
* @method computeEditDistance
* @private
* @param {String} w1 the first word
* @param {String} w2 the second word
*
* @returns {Number} the "distance" between the two words, a smaller value
* indicates that the words are similar
*/
const computeEditDistance = (w1, w2) => {
const l1 = w1.length,
l2 = w2.length;
if (l1 === 0) return w2;
if (l2 === 0) return w1;
let prev = [];
let cur = [];
for (let j = 0; j < l2 + 1; j++) {
cur[j] = j;
}
prev = cur;
for (let i = 1; i < l1 + 1; i++) {
cur = [];
for (let j = 0; j < l2 + 1; j++) {
if (j === 0) {
cur[j] = i;
} else {
let a1 = w1[i - 1],
a2 = w2[j - 1];
let temp = 999999;
let cost = a1.toLowerCase() === a2.toLowerCase() ? 0 : 1;
temp = temp > cost + prev[j - 1] ? cost + prev[j - 1] : temp;
temp = temp > 1 + cur[j - 1] ? 1 + cur[j - 1] : temp;
temp = temp > 1 + prev[j] ? 1 + prev[j] : temp;
cur[j] = temp;
}
}
prev = cur;
}
return cur[l2];
};
/**
* Whether or not p5.js is running in an environment where `preload` will be
* run before `setup`.
*
* This will return false for default builds >= 2.0, but backwards compatibility
* addons may set this to true.
*
* @private
*/
p5.isPreloadSupported = function() {
return false;
};
/**
* Checks capitalization for user defined functions.
*
* Generates and prints a friendly error message using key:
* "fes.checkUserDefinedFns".
*
* @method checkForUserDefinedFunctions
* @private
* @param {*} context Current default context. Set to window in
* "global mode" and to a p5 instance in "instance mode"
*/
const checkForUserDefinedFunctions = context => {
if (p5.disableFriendlyErrors) return;
// if using instance mode, this function would be called with the current
// instance as context
const instanceMode = context instanceof p5;
context = instanceMode ? context : window;
const fnNames = entryPoints;
if (context.preload && !p5.isPreloadSupported()) {
p5._error(context, translator('fes.preloadDisabled'));
}
const fxns = {};
// lowercasename -> actualName mapping
fnNames.forEach(symbol => {
fxns[symbol.toLowerCase()] = symbol;
});
for (const prop of Object.keys(context)) {
const lowercase = prop.toLowerCase();
// check if the lowercase property name has an entry in fxns, if the
// actual name with correct capitalization doesnt exist in context,
// and if the user-defined symbol is of the type function
if (
fxns.hasOwnProperty(lowercase) &&
!context[fxns[lowercase]] &&
typeof context[prop] === 'function'
) {
const msg = translator('fes.checkUserDefinedFns', {
name: prop,
actualName: fxns[lowercase]
});
p5._friendlyError(msg, fxns[lowercase]);
}
}
};
/**
* Compares the symbol caught in the ReferenceError to everything in
* misusedAtTopLevel ( all public p5 properties ).
*
* Generates and prints a friendly error message using key: "fes.misspelling".
*
* @method handleMisspelling
* @private
* @param {String} errSym Symbol to whose spelling to check
* @param {Error} error ReferenceError object
*
* @returns {Boolean} tell whether error was likely due to typo
*/
const handleMisspelling = (errSym, error) => {
if (!misusedAtTopLevelCode) {
defineMisusedAtTopLevelCode();
}
const distanceMap = {};
let min = 999999;
// compute the levenshtein distance for the symbol against all known
// public p5 properties. Find the property with the minimum distance
misusedAtTopLevelCode.forEach(symbol => {
let dist = computeEditDistance(errSym, symbol.name);
if (distanceMap[dist]) distanceMap[dist].push(symbol);
else distanceMap[dist] = [symbol];
if (dist < min) min = dist;
});
// if the closest match has more "distance" than the max allowed threshold
if (min > Math.min(EDIT_DIST_THRESHOLD, errSym.length)) return false;
// Show a message only if the caught symbol and the matched property name
// differ in their name ( either letter difference or difference of case )
const matchedSymbols = distanceMap[min].filter(
symbol => symbol.name !== errSym
);
if (matchedSymbols.length !== 0) {
const parsed = p5._getErrorStackParser().parse(error);
let locationObj;
if (
parsed &&
parsed[0] &&
parsed[0].fileName &&
parsed[0].lineNumber &&
parsed[0].columnNumber
) {
locationObj = {
location: `${parsed[0].fileName}:${parsed[0].lineNumber}:${
parsed[0].columnNumber
}`,
file: parsed[0].fileName.split('/').slice(-1),
line: parsed[0].lineNumber
};
}
let msg;
if (matchedSymbols.length === 1) {
// To be used when there is only one closest match. The count parameter
// allows i18n to pick between the keys "fes.misspelling" and
// "fes.misspelling_plural"
msg = translator('fes.misspelling', {
name: errSym,
actualName: matchedSymbols[0].name,
type: matchedSymbols[0].type,
location: locationObj ? translator('fes.location', locationObj) : '',
count: matchedSymbols.length
});
} else {
// To be used when there are multiple closest matches. Gives each
// suggestion on its own line, the function name followed by a link to
// reference documentation
const suggestions = matchedSymbols
.map(symbol => {
const message =
'▶️ ' + symbol.name + (symbol.type === 'function' ? '()' : '');
return mapToReference(message, symbol.name);
})
.join('\n');
msg = translator('fes.misspelling', {
name: errSym,
suggestions,
location: locationObj ? translator('fes.location', locationObj) : '',
count: matchedSymbols.length
});
}
// If there is only one closest match, tell _friendlyError to also add
// a link to the reference documentation. In case of multiple matches,
// this is already done in the suggestions variable, one link for each
// suggestion.
p5._friendlyError(
msg,
matchedSymbols.length === 1 ? matchedSymbols[0].name : undefined
);
return true;
}
return false;
};
/**
* Prints a friendly stacktrace for user-written functions for "global" errors
*
* Generates and prints a friendly error message using key:
* "fes.globalErrors.stackTop", "fes.globalErrors.stackSubseq".
*
* @method printFriendlyStack
* @private
* @param {Array} friendlyStack
*/
const printFriendlyStack = friendlyStack => {
const log =
p5._fesLogger && typeof p5._fesLogger === 'function'
? p5._fesLogger
: console.log.bind(console);
if (friendlyStack.length > 1) {
let stacktraceMsg = '';
friendlyStack.forEach((frame, idx) => {
const location = `${frame.fileName}:${frame.lineNumber}:${
frame.columnNumber
}`;
let frameMsg,
translationObj = {
func: frame.functionName,
line: frame.lineNumber,
location,
file: frame.fileName.split('/').slice(-1)
};
if (idx === 0) {
frameMsg = translator('fes.globalErrors.stackTop', translationObj);
} else {
frameMsg = translator('fes.globalErrors.stackSubseq', translationObj);
}
stacktraceMsg += frameMsg;
});
log(stacktraceMsg);
}
};
/**
* Takes a stacktrace array and filters out all frames that show internal p5
* details.
*
* Generates and prints a friendly error message using key:
* "fes.wrongPreload", "fes.libraryError".
*
* The processed stack is used to find whether the error happened internally
* within the library, and if the error was due to a non-loadX() method
* being used in preload.
*
* "Internally" here means that the exact location of the error (the top of
* the stack) is a piece of code written in the p5.js library (which may or
* may not have been called from the user's sketch).
*
* @method processStack
* @private
* @param {Error} error
* @param {Array} stacktrace
*
* @returns {Array} An array with two elements, [isInternal, friendlyStack]
* isInternal: a boolean value indicating whether the error
* happened internally
* friendlyStack: the filtered (simplified) stacktrace
*/
const processStack = (error, stacktrace) => {
// cannot process a stacktrace that doesn't exist
if (!stacktrace) return [false, null];
stacktrace.forEach(frame => {
frame.functionName = frame.functionName || '';
});
// isInternal - Did this error happen inside the library
let isInternal = false;
let p5FileName, friendlyStack, currentEntryPoint;
// Intentionally throw an error that we catch so that we can check the name
// of the current file. Any errors we see from this file, we treat as
// internal errors.
try {
throw new Error();
} catch (testError) {
const testStacktrace = p5._getErrorStackParser().parse(testError);
p5FileName = testStacktrace[0].fileName;
}
for (let i = stacktrace.length - 1; i >= 0; i--) {
let splitted = stacktrace[i].functionName.split('.');
if (entryPoints.includes(splitted[splitted.length - 1])) {
// remove everything below an entry point function (setup, draw, etc).
// (it's usually the internal initialization calls)
friendlyStack = stacktrace.slice(0, i + 1);
currentEntryPoint = splitted[splitted.length - 1];
// We call the error "internal" if the source of the error was a
// function from within the p5.js library file, but called from the
// user's code directly. We only need to check the topmost frame in
// the stack trace since any function internal to p5 should pass this
// check, not just public p5 functions.
if (stacktrace[0].fileName === p5FileName) {
isInternal = true;
break;
}
break;
}
}
// in some cases ( errors in promises, callbacks, etc), no entry-point
// function may be found in the stacktrace. In that case just use the
// entire stacktrace for friendlyStack
if (!friendlyStack) friendlyStack = stacktrace;
if (isInternal) {
// the frameIndex property is added before the filter, so frameIndex
// corresponds to the index of a frame in the original stacktrace.
// Then we filter out all frames which belong to the file that contains
// the p5 library
friendlyStack = friendlyStack
.map((frame, index) => {
frame.frameIndex = index;
return frame;
})
.filter(frame => frame.fileName !== p5FileName);
// a weird case, if for some reason we can't identify the function called
// from user's code
if (friendlyStack.length === 0) return [true, null];
// get the function just above the topmost frame in the friendlyStack.
// i.e the name of the library function called from user's code
const func = stacktrace[friendlyStack[0].frameIndex - 2].functionName
.split('.')
.slice(-1)[0];
// Try and get the location (line no.) from the top element of the stack
let locationObj;
if (
friendlyStack[0].fileName &&
friendlyStack[0].lineNumber &&
friendlyStack[0].columnNumber
) {
locationObj = {
location: `${friendlyStack[0].fileName}:${
friendlyStack[0].lineNumber
}:${friendlyStack[0].columnNumber}`,
file: friendlyStack[0].fileName.split('/').slice(-1),
line: friendlyStack[0].lineNumber
};
// if already handled by another part of the FES, don't handle again
if (p5._fesLogCache[locationObj.location]) return [true, null];
}
// Check if the error is due to a non loadX method being used incorrectly
// in preload
if (
currentEntryPoint === 'preload' &&
fn._preloadMethods[func] == null
) {
p5._friendlyError(
translator('fes.wrongPreload', {
func,
location: locationObj
? translator('fes.location', locationObj)
: '',
error: error.message
}),
'preload'
);
} else {
// Library error
p5._friendlyError(
translator('fes.libraryError', {
func,
location: locationObj
? translator('fes.location', locationObj)
: '',
error: error.message
}),
func
);
}
// Finally, if it's an internal error, print the friendlyStack
// ( fesErrorMonitor won't handle this error )
if (friendlyStack && friendlyStack.length) {
printFriendlyStack(friendlyStack);
}
}
return [isInternal, friendlyStack];
};
/**
* Handles "global" errors that the browser catches.
*
* Called when an error event happens and detects the type of error.
*
* Generates and prints a friendly error message using key:
* "fes.globalErrors.syntax.[*]", "fes.globalErrors.reference.[*]",
* "fes.globalErrors.type.[*]".
*
* @method fesErrorMonitor
* @private
* @param {*} e Event object to extract error details from
*/
const fesErrorMonitor = e => {
if (p5.disableFriendlyErrors) return;
// Don't try to handle an error intentionally emitted by FES to halt execution
if (e && (e instanceof FESError || e.reason instanceof FESError)) return;
// Try to get the error object from e
let error;
if (e instanceof Error) {
error = e;
} else if (e instanceof ErrorEvent) {
error = e.error;
} else if (e instanceof PromiseRejectionEvent) {
error = e.reason;
if (!(error instanceof Error)) return;
}
if (!error) return;
let stacktrace = p5._getErrorStackParser().parse(error);
// process the stacktrace from the browser and simplify it to give
// friendlyStack.
let [isInternal, friendlyStack] = processStack(error, stacktrace);
// if this is an internal library error, the type of the error is not relevant,
// only the user code that lead to it is.
if (isInternal) {
return;
}
const errList = strings[error.name];
if (!errList) return; // this type of error can't be handled yet
let matchedError;
for (const obj of errList) {
let string = obj.msg;
// capture the primary symbol mentioned in the error
string = string.replace(new RegExp('{{}}', 'g'), '([a-zA-Z0-9_]+)');
string = string.replace(new RegExp('{{.}}', 'g'), '(.+)');
string = string.replace(new RegExp('{}', 'g'), '(?:[a-zA-Z0-9_]+)');
let matched = error.message.match(string);
if (matched) {
matchedError = Object.assign({}, obj);
matchedError.match = matched;
break;
}
}
if (!matchedError) return;
// Try and get the location from the top element of the stack
let locationObj;
if (
stacktrace &&
stacktrace[0].fileName &&
stacktrace[0].lineNumber &&
stacktrace[0].columnNumber
) {
locationObj = {
location: `${stacktrace[0].fileName}:${stacktrace[0].lineNumber}:${
stacktrace[0].columnNumber
}`,
file: stacktrace[0].fileName.split('/').slice(-1),
line: friendlyStack[0].lineNumber
};
}
switch (error.name) {
case 'SyntaxError': {
// We can't really do much with syntax errors other than try to use
// a simpler framing of the error message. The stack isn't available
// for syntax errors
switch (matchedError.type) {
case 'INVALIDTOKEN': {
//Error if there is an invalid or unexpected token that doesn't belong at this position in the code
//let x = “not a string”; -> string not in proper quotes
let url =
'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Illegal_character#What_went_wrong';
p5._friendlyError(
translator('fes.globalErrors.syntax.invalidToken', {
url
})
);
break;
}
case 'UNEXPECTEDTOKEN': {
//Error if a specific language construct(, { ; etc) was expected, but something else was provided
//for (let i = 0; i < 5,; ++i) -> a comma after i<5 instead of a semicolon
let url =
'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Unexpected_token#What_went_wrong';
p5._friendlyError(
translator('fes.globalErrors.syntax.unexpectedToken', {
url
})
);
break;
}
case 'REDECLAREDVARIABLE': {
//Error if a variable is redeclared by the user. Example=>
//let a = 10;
//let a = 100;
let errSym = matchedError.match[1];
let url =
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Redeclared_parameter#what_went_wrong';
p5._friendlyError(
translator('fes.globalErrors.syntax.redeclaredVariable', {
symbol: errSym,
url
})
);
break;
}
case 'MISSINGINITIALIZER': {
//Error if a const variable is not initialized during declaration
//Example => const a;
let url =
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Missing_initializer_in_const#what_went_wrong';
p5._friendlyError(
translator('fes.globalErrors.syntax.missingInitializer', {
url
})
);
break;
}
case 'BADRETURNORYIELD': {
//Error when a return statement is misplaced(usually outside of a function)
// const a = function(){
// .....
// }
// return; -> misplaced return statement
let url =
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Bad_return_or_yield#what_went_wrong';
p5._friendlyError(
translator('fes.globalErrors.syntax.badReturnOrYield', {
url
})
);
break;
}
}
break;
}
case 'ReferenceError': {
switch (matchedError.type) {
case 'NOTDEFINED': {
//Error if there is a non-existent variable referenced somewhere
//let a = 10;
//console.log(x);
let errSym = matchedError.match[1];
if (errSym && handleMisspelling(errSym, error)) {
break;
}
// if the flow gets this far, this is likely not a misspelling
// of a p5 property/function
let url = 'https://p5js.org/examples/data-variable-scope.html';
p5._friendlyError(
translator('fes.globalErrors.reference.notDefined', {
url,
symbol: errSym,
location: locationObj
? translator('fes.location', locationObj)
: ''
})
);
if (friendlyStack) printFriendlyStack(friendlyStack);
break;
}
case 'CANNOTACCESS': {
//Error if a lexical variable was accessed before it was initialized
//console.log(a); -> variable accessed before it was initialized
//let a=100;
let errSym = matchedError.match[1];
let url =
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_lexical_declaration_before_init#what_went_wrong';
p5._friendlyError(
translator('fes.globalErrors.reference.cannotAccess', {
url,
symbol: errSym,
location: locationObj
? translator('fes.location', locationObj)
: ''
})
);
if (friendlyStack) printFriendlyStack(friendlyStack);
break;
}
}
break;
}
case 'TypeError': {
switch (matchedError.type) {
case 'NOTFUNC': {
//Error when some code expects you to provide a function, but that didn't happen
//let a = document.getElementByID('foo'); -> getElementById instead of getElementByID
let errSym = matchedError.match[1];
let splitSym = errSym.split('.');
let url =
'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_a_function#What_went_wrong';
// if errSym is aa.bb.cc , symbol would be cc and obj would aa.bb
let translationObj = {
url,
symbol: splitSym[splitSym.length - 1],
obj: splitSym.slice(0, splitSym.length - 1).join('.'),
location: locationObj
? translator('fes.location', locationObj)
: ''
};
// There are two cases to handle here. When the function is called
// as a property of an object and when it's called independently.
// Both have different explanations.
if (splitSym.length > 1) {
p5._friendlyError(
translator('fes.globalErrors.type.notfuncObj', translationObj)
);
} else {
p5._friendlyError(
translator('fes.globalErrors.type.notfunc', translationObj)
);
}
if (friendlyStack) printFriendlyStack(friendlyStack);
break;
}
case 'READNULL': {
//Error if a property of null is accessed
//let a = null;
//console.log(a.property); -> a is null
let errSym = matchedError.match[1];
let url =
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_property#what_went_wrong';
/*let url2 =
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null';*/
p5._friendlyError(
translator('fes.globalErrors.type.readFromNull', {
url,
symbol: errSym,
location: locationObj
? translator('fes.location', locationObj)
: ''
})
);
if (friendlyStack) printFriendlyStack(friendlyStack);
break;
}
case 'READUDEFINED': {
//Error if a property of undefined is accessed
//let a; -> default value of a is undefined
//console.log(a.property); -> a is undefined
let errSym = matchedError.match[1];
let url =
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_property#what_went_wrong';
/*let url2 =
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined#description';*/
p5._friendlyError(
translator('fes.globalErrors.type.readFromUndefined', {
url,
symbol: errSym,
location: locationObj
? translator('fes.location', locationObj)
: ''
})
);
if (friendlyStack) printFriendlyStack(friendlyStack);
break;
}
case 'CONSTASSIGN': {
//Error when a const variable is reassigned a value
//const a = 100;
//a=10;
let url =
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment#what_went_wrong';
p5._friendlyError(
translator('fes.globalErrors.type.constAssign', {
url,
location: locationObj
? translator('fes.location', locationObj)
: ''
})
);
if (friendlyStack) printFriendlyStack(friendlyStack);
break;
}
}
}
}
};
p5._fesErrorMonitor = fesErrorMonitor;
p5._checkForUserDefinedFunctions = checkForUserDefinedFunctions;
// logger for testing purposes.
p5._fesLogger = null;
p5._fesLogCache = {};
window.addEventListener('load', checkForUserDefinedFunctions, false);
window.addEventListener('error', p5._fesErrorMonitor, false);
window.addEventListener('unhandledrejection', p5._fesErrorMonitor, false);
/**
* Prints out all the colors in the color pallete with white text.
* For color blindness testing.
*/
/* function testColors() {
const str = 'A box of biscuits, a box of mixed biscuits and a biscuit mixer';
p5._friendlyError(str, 'print', '#ED225D'); // p5.js magenta
p5._friendlyError(str, 'print', '#2D7BB6'); // p5.js blue
p5._friendlyError(str, 'print', '#EE9900'); // p5.js orange
p5._friendlyError(str, 'print', '#A67F59'); // p5.js light brown
p5._friendlyError(str, 'print', '#704F21'); // p5.js gold
p5._friendlyError(str, 'print', '#1CC581'); // auto cyan
p5._friendlyError(str, 'print', '#FF6625'); // auto orange
p5._friendlyError(str, 'print', '#79EB22'); // auto green
p5._friendlyError(str, 'print', '#B40033'); // p5.js darkened magenta
p5._friendlyError(str, 'print', '#084B7F'); // p5.js darkened blue
p5._friendlyError(str, 'print', '#945F00'); // p5.js darkened orange
p5._friendlyError(str, 'print', '#6B441D'); // p5.js darkened brown
p5._friendlyError(str, 'print', '#2E1B00'); // p5.js darkened gold
p5._friendlyError(str, 'print', '#008851'); // auto dark cyan
p5._friendlyError(str, 'print', '#C83C00'); // auto dark orange
p5._friendlyError(str, 'print', '#4DB200'); // auto dark green
} */
}
// This is a lazily-defined list of p5 symbols that may be
// misused by beginners at top-level code, outside of setup/draw. We'd like
// to detect these errors and help the user by suggesting they move them
// into setup/draw.
//
// For more details, see https://github.com/processing/p5.js/issues/1121.
misusedAtTopLevelCode = null;
const FAQ_URL =
'https://github.com/processing/p5.js/wiki/p5.js-overview#why-cant-i-assign-variables-using-p5-functions-and-variables-before-setup';
/**
* A helper function for populating misusedAtTopLevel list.
*
* @method defineMisusedAtTopLevelCode
* @private
*/
defineMisusedAtTopLevelCode = () => {
const uniqueNamesFound = {};
const getSymbols = obj =>
Object.getOwnPropertyNames(obj)
.filter(name => {
if (name[0] === '_') {
return false;
}
if (name in uniqueNamesFound) {
return false;
}
uniqueNamesFound[name] = true;
return true;
})
.map(name => {
let type;
if (typeof obj[name] === 'function') {
type = 'function';
} else if (name === name.toUpperCase()) {
type = 'constant';
} else {
type = 'variable';
}
return { name, type };
});
misusedAtTopLevelCode = [].concat(
getSymbols(fn),
// At present, p5 only adds its constants to fn during
// construction, which may not have happened at the time a
// ReferenceError is thrown, so we'll manually add them to our list.
getSymbols(constants)
);
// This will ultimately ensure that we report the most specific error
// possible to the user, e.g. advising them about HALF_PI instead of PI
// when their code misuses the former.
misusedAtTopLevelCode.sort((a, b) => b.name.length - a.name.length);
};
/**
* Detects browser level error event for p5 constants/functions used outside
* of setup() and draw().
*
* Generates and prints a friendly error message using key:
* "fes.misusedTopLevel".
*
* @method helpForMisusedAtTopLevelCode
* @private
* @param {Event} e Error event
* @param {Boolean} log false
*
* @returns {Boolean} true
*/
const helpForMisusedAtTopLevelCode = (e, log) => {
if (!log) {
log = console.log.bind(console);
}
if (!misusedAtTopLevelCode) {
defineMisusedAtTopLevelCode();
}
// If we find that we're logging lots of false positives, we can
// uncomment the following code to avoid displaying anything if the
// user's code isn't likely to be using p5's global mode. (Note that
// setup/draw are more likely to be defined due to JS function hoisting.)
//
//if (!('setup' in window || 'draw' in window)) {
// return;
//}
misusedAtTopLevelCode.some(symbol => {
// Note that while just checking for the occurrence of the
// symbol name in the error message could result in false positives,
// a more rigorous test is difficult because different browsers
// log different messages, and the format of those messages may
// change over time.
//
// For example, if the user uses 'PI' in their code, it may result
// in any one of the following messages:
//
// * 'PI' is undefined (Microsoft Edge)
// * ReferenceError: PI is undefined (Firefox)
// * Uncaught ReferenceError: PI is not defined (Chrome)
if (e.message && e.message.match(`\\W?${symbol.name}\\W`) !== null) {
const symbolName =
symbol.type === 'function' ? `${symbol.name}()` : symbol.name;
if (typeof IS_MINIFIED !== 'undefined') {
log(
`Did you just try to use p5.js's ${symbolName} ${
symbol.type
}? If so, you may want to move it into your sketch's setup() function.\n\nFor more details, see: ${FAQ_URL}`
);
} else {
log(
translator('fes.misusedTopLevel', {
symbolName,
symbolType: symbol.type,
url: FAQ_URL
})
);
}
return true;
}
});
};
// Exposing this primarily for unit testing.
fn._helpForMisusedAtTopLevelCode = helpForMisusedAtTopLevelCode;
if (document.readyState !== 'complete') {
window.addEventListener('error', helpForMisusedAtTopLevelCode, false);
// Our job is only to catch ReferenceErrors that are thrown when
// global (non-instance mode) p5 APIs are used at the top-level
// scope of a file, so we'll unbind our error listener now to make
// sure we don't log false positives later.
window.addEventListener('load', () => {
window.removeEventListener('error', helpForMisusedAtTopLevelCode, false);
});
}
}
if (typeof p5 !== 'undefined') {
fesCore(p5, p5.prototype);
}
export { fesCore as default };