From 90003280f2f82cf61bd034dfc7b4460ee2d68fb6 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Sat, 23 Oct 2021 18:27:58 -0700 Subject: [PATCH 01/18] inline doc organization --- contributor_docs/fes_reference_dev_notes.md | 240 +++++ contributor_docs/friendly_error_system.md | 312 ++---- lib/empty-example/sketch.js | 3 +- src/core/friendly_errors/fes_core.js | 1049 +++++++++---------- src/core/friendly_errors/file_errors.js | 29 +- src/core/friendly_errors/sketch_reader.js | 12 +- src/core/friendly_errors/validate_params.js | 106 +- 7 files changed, 921 insertions(+), 830 deletions(-) create mode 100644 contributor_docs/fes_reference_dev_notes.md diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md new file mode 100644 index 0000000000..868a3fc464 --- /dev/null +++ b/contributor_docs/fes_reference_dev_notes.md @@ -0,0 +1,240 @@ +# FES Reference and Development Notes +This document contains development notes for p5.js's Friendly Error System (FES). + +### `core/friendly_errors/file_errors/friendlyFileLoadError()`: +* This function generates and displays friendly error messages if a file fails to load correctly. It also checks if the size of a file might be too large to load and produces a warning. +* This can be called through : `p5._friendlyFileLoadError(ERROR_TYPE, FILE_PATH)`. +* file loading error example: +````javascript +/// missing font file +let myFont; +function preload() { + myFont = loadFont('assets/OpenSans-Regular.ttf'); +}; +function setup() { + fill('#ED225D'); + textFont(myFont); + textSize(36); + text('p5*js', 10, 50); +}; +function draw() {}; +/// FES will generate the following message in the console: +/// > p5.js says: It looks like there was a problem loading your font. Try checking if the file path [assets/OpenSans-Regular.ttf] is correct, hosting the font online, or running a local server.[https://github.com/processing/p5.js/wiki/Local-server] +```` +* Currently version contains templates for generating error messages for `image`, `XML`, `table`, `text`, `json` and `font` files. +* Implemented to `image/loading_displaying/loadImage()`, `io/files/loadFont()`, `io/files/loadTable()`, `io/files/loadJSON()`, `io/files/loadStrings()`, `io/files/loadXML()`, `io/files/loadBytes()`. +* Error while loading a file due to its large size is implemented to all loadX methods. + +### `core/friendly_errors/validate_params/validateParameters()`: +* This function runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct type of parameters. +* missing parameter example: +````javascript +arc(1, 1, 10.5, 10); +/// FES will generate the following message in the console: +/// > p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] +/// > p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] + +```` +* wrong type example: +````javascript +arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); +/// FES will generate the following message in the console: +/// > p5.js says: arc() was expecting Number for parameter #0 (zero-based index), received string instead. [http://p5js.org/reference/#p5/arc] +```` +* This can be called through: `p5._validateParameters(FUNCT_NAME, ARGUMENTS)` +or, `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` inside the function that requires parameter validation. It is recommended to use static version, `p5._validateParameters` for general purposes. `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` mainly remained for debugging and unit testing purposes. +* Implemented to functions in `color/creating_reading`, `core/2d_primitives`, `core/curves`, and `utilities/string_functions`. + +### `core/friendly_errors/fes_core/fesErrorMonitor()`: +* This function is triggered whenever an error happens in the script. It attempts to help the user by providing more details, +likely causes and ways to address it. + +* Internal Error Example 1 +```js +function preload() { + // error in background() due to it being called in + // preload + background(200); +} + +/* +FES will show: +p5.js says: An error with message "Cannot read property 'background' of undefined" occured inside the p5js library when "background" was called (on line 4 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:4:3]). + +If not stated otherwise, it might be due to "background" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function. (http://p5js.org/reference/#/p5/preload) +*/ +``` + +* Internal Error Example 2 +```js +function setup() { + cnv = createCanvas(200, 200); + cnv.mouseClicked(); +} + +/* + +p5.js says: An error with message "Cannot read property 'bind' of undefined" occured inside the p5js library when mouseClicked was called (on line 3 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:3:7]) + +If not stated otherwise, it might be an issue with the arguments passed to mouseClicked. (http://p5js.org/reference/#/p5/mouseClicked) +*/ +``` + +* Error in user's sketch example (scope) +```js +function setup() { + let b = 1; +} +function draw() { + b += 1; +} +/* +FES will show: +p5.js says: There's an error due to "b" not being defined in the current scope (on line 5 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:5:3]). + +If you have defined it in your code, you should check its scope, spelling, and letter-casing (JavaScript is case-sensitive). For more: +https://p5js.org/examples/data-variable-scope.html +https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong +*/ +``` + +* Error in user's sketch example (spelling) +```js +function setup() { + colour(1, 2, 3); +} +/* +FES will show: +p5.js says: It seems that you may have accidentally written "colour" instead of "color" (on line 2 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:2:3]). + +Please correct it to color if you wish to use the function from p5.js (http://p5js.org/reference/#/p5/color) +*/ +``` + +### `core/friendly_errors/fes_core/sketch_reader/fesCodeReader()`: +* This function is executed everytime when the `load` event is triggered. It checks if a p5.js reserved constant or function is redefined by the user. + +* Redefining p5.js reserved constant +```js +function setup() { + //PI is a p5.js reserved constant + let PI = 100; +} + +/* +FES will show: +p5.js says: you have used a p5.js reserved variable "PI" make sure you change the variable name to something else. +(https://p5js.org/reference/#/p5/PI) +*/ +``` + +* Redefining p5.js reserved function +```js +function setup() { + //text is a p5.js reserved function + let text = 100; +} + +/* +FES will show: +p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. +*/ +``` + +### `core/friendly_errors/fes_core/checkForUserDefinedFunctions()`: +* Checks if any user defined function (`setup()`, `draw()`, `mouseMoved()`, etc.) has been used with a capitalization mistake +* For example +```js +function preLoad() { + loadImage('myimage.png'); +} +/* +FES will show: +p5.js says: It seems that you may have accidentally written preLoad instead of preload. + +Please correct it if it's not intentional. (http://p5js.org/reference/#/p5/preload) +*/ +``` + + +## Additional Features +* The FES welcomes the developer to p5 and the friendly debugger. +* The FES works in the IDE and the web editor. + +## Notes for Developers +* When creating p5.js Objects: any p5.js objects that will be used for parameters will need to assign value for `name` parameter (name of the object) within the class declaration. e.g.: +````javascript +p5.newObject = function(parameter) { + this.parameter = 'some parameter'; + this.name = 'p5.newObject'; +}; +```` +* Inline documentation: allowed parameter types are `Boolean`, `Number`, `String`, and name of the object (see the above bullet point). Use `Array` for any types of Array parameters. If needed, explain what kind of the specific types of array parameter are allowed (e.g. `Number[]`, `String[]`) in the description section. +* Currently supported class types (have their `name` parameter): `p5.Color`, `p5.Element`, `p5.Graphics`, `p5.Renderer`, `p5.Renderer2D`, `p5.Image`, `p5.Table`, `p5.TableRow`, `p5.XML`, `p5.Vector`, `p5.Font`, `p5.Geometry`, `p5.Matrix`, `p5.RendererGL`. + +## Disable the FES + +By default, FES is enabled for p5.js, and disabled in p5.min.js to prevent FES functions slowing down the process. The error checking system can significantly slow down your code (up to ~10x in some cases). See the [friendly error performance test](https://github.com/processing/p5.js-website/tree/main/src/assets/learn/performance/code/friendly-error-system/). + +You can disable this with one line of code at the top of your sketch: + +```javascript +p5.disableFriendlyErrors = true; // disables FES + +function setup() { + // Do setup stuff +} + +function draw() { + // Do drawing stuff +} +``` + +Note that this will disable the parts of the FES that cause performance slowdown (like argument checking). Friendly errors that have no performance cost (like giving an descriptive error if a file load fails, or warning you if you try to override p5.js functions in the global space), will remain in place. + +## Known Limitations +* The friendly error system slows the program down, so there is an option to turn it off via setting `p5.disableFriendlyErrors = true;`. In addition, the friendly error system is omitted by default in the minified (`p5.min.js`) version. +* FES may still result in false negative cases. These are usually caused by the mismatch between designs of the functions (e.g. drawing functions those are originally designed to be used interchangeably in both 2D and 3D settings) with actual usage cases. For example, drawing a 3D line with +```javascript +const x3; // undefined +line(0, 0, 100, 100, x3, Math.PI); +``` + will escape FES, because there is an acceptable parameter pattern (`Number`, `Number`, `Number`, `Number`) in `line()`'s inline documentation for drawing in 2D setting. This also means the current version of FES doesn't check for the environmental variables such as `_renderer.isP3D`. + * FES is only able to detect global variables overwritten when declared using `const` or `var`. If `let` is used, they go undetected. This is not currently solvable due to the way `let` instantiates variables. + + * The functionality described under **`fesErrorMonitor()`** currently only works on the web editor or if running on a local server. For more details see [this](https://github.com/processing/p5.js/pull/4730). + + * The extracting variable/function names feature of FES's `sketch_reader` is not perfect and some cases might go undetected (for eg: when all the code is written in a single line). + +## In The Works +* Identify more common error types and generalize with FES (e.g. `bezierVertex()`, `quadraticVertex()` - required object not initiated; checking Number parameters positive for `nf()` `nfc()` `nfp()` `nfs()`) + +## Thoughts for the Future +* re-introduce color coding for the Web Editor. +* More unit testings. +* More intuitive and narrowed down output messages. +* Completing the spanish translation for `validateParameters()` as well. +* All the colors are checked for being color blind friendly. +* More elaborate ascii is always welcome! +* Extend Global Error catching. This means catching errors that the browser is throwing to the console and matching them with friendly messages. `fesErrorMonitor()` does this for a select few kinds of errors but help in supporting more is welcome :) +* Improve `sketch_reader.js`'s code reading and variable/function name extracting functionality (which extracts names of the function and variables declared by the user in their code). For example currently `sketch_reader.js` is not able to extract variable/function names properly if all the code is written in a single line. +* `sketch_reader.js` can be extended and new features (for example: Alerting the user when they have declared a variable in the `draw()` function) can be added to it to better aid the user. + + +```javascript +// this snippet wraps window.console methods with a new function to modify their functionality +// it is not currently implemented, but could be to give nicer formatted error messages +const original = window.console; +const original_functions = { + log: original.log, + warn: original.warn, + error: original.error +} + +["log", "warn", "error"].forEach(function(func){ +window.console[func] = function(msg) { +//do something with the msg caught by the wrapper function, then call the original function +original_functions[func].apply(original, arguments) +}; +}); +``` diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index e9dd9c48b0..1ad2b56554 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -1,268 +1,132 @@ -# p5.js Friendly Error System (FES) +# 🌸 p5.js Friendly Error System (FES) ## Overview -The Friendly Error System (FES) is a system designed to help new programmers with common user errors as they get started learning. It catches common beginning errors and provides clear language and links to help a user resolve the error. FES is only applied to functions that are ones a user might encounter when they are just starting. An exception is made for particular common gotchas such as trying to load files without running a server, or calling loadImage() with a URL, etc. +The Friendly Error System (FES, 🌸) aims to help new programmers by providing error messages in simple, friendly language. It supplements browser console error messages by adding an alternative description of the error and links to helpful references. -The goal is to create accessible error messages to supplement the often cryptic console errors. For example, Javascript has no support for type checking as default making errors in parameter entry was harder to detect for new Javascript developers. +The FES prints messages in the console window, as seen in the [p5.js Web Editor](https://github.com/processing/p5.js-web-editor) and your browser JavaScript console. The single minified file of p5 (p5.min.js) omits the FES. -Messages generated by FES are written in natural language, linked to documentation, and assume a beginner level. The errors are triggered in multiple files through out p5.js, but most of the work and error writing happens in `src/core/friendly_errors`. + *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸](https://forms.gle/2qStHJFSiw9XMCQJ8) -By default, FES is enabled for `p5.js`, whereas completely disabled in `p5.min.js`. It is possible to disable FES by setting `p5.disableFriendlyErrors = true;`. -So far FES is able to detect and print messages for four kinds of errors: +## Writing Friendly Error Messages -1. `validateParameters()` checks a function’s input parameters based on inline documentations +In this section, we will talk about how to contribute by writing and translating error messages. -2. `friendlyFileLoadError()` catches file loading errors. These two kinds of error checking have been integrated into an existing (selected set of) p5 functions, but developers can add them to more p5 functions, or their own libraries, by calling `p5._validateParameters()` +The FES is a part of the p5.js' [internationalization](https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) effort. We generate all FES messages' content through [i18next](https://www.i18next.com/)-based `translator()` function. This dynamic error message generation happens for all languages, including English - the default language of p5. -3. `friendlyError()` can be called by any function to offer a helpful error message +We welcome contributions from all over the world! 🌐 -4. `helpForMisusedAtTopLevelCode()` is called on window load to check for use of p5.js functions outside of setup() or draw() +#### Writing Best Practice -Apart from this, the FES can also detect several other common errors that the browser may show. -This is handled by `fesErrorMonitor()`. The full list of errors that the FES can work with can be found in [src/core/friendly_errors/browser_errors.js](https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/browser_errors.js). +Writers writing FES messages should prioritize lowering the barrier to understanding error messages and debugging. -The FES can also help the user differentiate between errors that happen inside the library and errors that happen in the sketch. It also detects if the error was caused due to a non-loadX() method being called in `preload()` +Here are some highlights from our upcoming best practice doc: -Please also see inline notes in [src/core/friendly_errors/fes_core.js](https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/fes_core.js) for more technical information. +* Use simple sentences. Consider breaking your sentence into smaller blocks for best utilizing i18next's [interpolation](https://www.i18next.com/translation-function/interpolation) feature. +* Keep the language friendly and inclusive. Look for possible bias and harm in your language. Adhere to [p5.js Code of Conduct](https://github.com/processing/p5.js/blob/main/CODE_OF_CONDUCT.md#p5js-code-of-conduct). +* Avoid using figures of speech. Prioritize cross-cultural communication. +* Try to spot possible "[expert blind spots](https://tilt.colostate.edu/TipsAndGuides/Tip/181)" in an error message and its related docs. +* Introduce one technical concept or term at a time—link one external resource written in a beginner-friendly language with plenty of short, practical examples. -The FES can detect the declaration of reserved p5.js constants (for eg: PI) and functions (for eg: text) by the user. This operation is performed by `fesCodeReader()` in `sketch_reader.js`. You can have a look at the full code [src/core/friendly_errors/sketch_reader.js](https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/sketch_reader.js) +#### Translation File Location -### `core/friendly_errors/file_errors/friendlyFileLoadError()`: -* This function generates and displays friendly error messages if a file fails to load correctly. It also checks if the size of a file might be too large to load and produces a warning. -* This can be called through : `p5._friendlyFileLoadError(ERROR_TYPE, FILE_PATH)`. -* file loading error example: -````javascript -/// missing font file -let myFont; -function preload() { - myFont = loadFont('assets/OpenSans-Regular.ttf'); -}; -function setup() { - fill('#ED225D'); - textFont(myFont); - textSize(36); - text('p5*js', 10, 50); -}; -function draw() {}; -/// FES will generate the following message in the console: -/// > p5.js says: It looks like there was a problem loading your font. Try checking if the file path [assets/OpenSans-Regular.ttf] is correct, hosting the font online, or running a local server.[https://github.com/processing/p5.js/wiki/Local-server] -```` -* Currently version contains templates for generating error messages for `image`, `XML`, `table`, `text`, `json` and `font` files. -* Implemented to `image/loading_displaying/loadImage()`, `io/files/loadFont()`, `io/files/loadTable()`, `io/files/loadJSON()`, `io/files/loadStrings()`, `io/files/loadXML()`, `io/files/loadBytes()`. -* Error while loading a file due to its large size is implemented to all loadX methods. - -### `core/friendly_errors/validate_params/validateParameters()`: -* This function runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct type of parameters. -* missing parameter example: -````javascript -arc(1, 1, 10.5, 10); -/// FES will generate the following message in the console: -/// > p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] -/// > p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] - -```` -* wrong type example: -````javascript -arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); -/// FES will generate the following message in the console: -/// > p5.js says: arc() was expecting Number for parameter #0 (zero-based index), received string instead. [http://p5js.org/reference/#p5/arc] -```` -* This can be called through: `p5._validateParameters(FUNCT_NAME, ARGUMENTS)` -or, `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` inside the function that requires parameter validation. It is recommended to use static version, `p5._validateParameters` for general purposes. `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` mainly remained for debugging and unit testing purposes. -* Implemented to functions in `color/creating_reading`, `core/2d_primitives`, `core/curves`, and `utilities/string_functions`. - -### `core/friendly_errors/fes_core/fesErrorMonitor()`: -* This function is triggered whenever an error happens in the script. It attempts to help the user by providing more details, -likely causes and ways to address it. - -* Internal Error Example 1 -```js -function preload() { - // error in background() due to it being called in - // preload - background(200); -} - -/* -FES will show: -p5.js says: An error with message "Cannot read property 'background' of undefined" occured inside the p5js library when "background" was called (on line 4 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:4:3]). - -If not stated otherwise, it might be due to "background" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function. (http://p5js.org/reference/#/p5/preload) -*/ +`translator()` is based on i18next and imported from `src/core/internationalization.js`. It generates messages by looking up text data from a JSON translation file: +``` +translations/{{detected locale code, default=en}}/translation.json ``` -* Internal Error Example 2 -```js -function setup() { - cnv = createCanvas(200, 200); - cnv.mouseClicked(); -} +Example: +If the detected browser locale is Korean (language designator: `ko`), the `translator()` will read in translated text blocks from `translations/ko/translation.json`. Then `translator()` will assemble the text blocks into the final message. -/* +The language designator can also include regional information, such as `es-PE` (Spanish from Peru). -p5.js says: An error with message "Cannot read property 'bind' of undefined" occured inside the p5js library when mouseClicked was called (on line 3 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:3:7]) +#### Translation File Structure +`translation.json` has a [format used by i18next](https://www.i18next.com/misc/json-format). -If not stated otherwise, it might be an issue with the arguments passed to mouseClicked. (http://p5js.org/reference/#/p5/mouseClicked) -*/ +The basic format of a translation file's item has a key and a value (message) in double quotation marks `""`, closed by the curly brackets `{}`: ``` - -* Error in user's sketch example (scope) -```js -function setup() { - let b = 1; -} -function draw() { - b += 1; -} -/* -FES will show: -p5.js says: There's an error due to "b" not being defined in the current scope (on line 5 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:5:3]). - -If you have defined it in your code, you should check its scope, spelling, and letter-casing (JavaScript is case-sensitive). For more: -https://p5js.org/examples/data-variable-scope.html -https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong -*/ +{ "key": "value" } ``` - -* Error in user's sketch example (spelling) -```js -function setup() { - colour(1, 2, 3); -} -/* -FES will show: -p5.js says: It seems that you may have accidentally written "colour" instead of "color" (on line 2 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:2:3]). - -Please correct it to color if you wish to use the function from p5.js (http://p5js.org/reference/#/p5/color) -*/ +For example, we have a ASCII logo saved in this format: ``` - -### `core/friendly_errors/fes_core/sketch_reader/fesCodeReader()`: -* This function is executed everytime when the `load` event is triggered. It checks if a p5.js reserved constant or function is redefined by the user. - -* Redefining p5.js reserved constant -```js -function setup() { - //PI is a p5.js reserved constant - let PI = 100; -} - -/* -FES will show: -p5.js says: you have used a p5.js reserved variable "PI" make sure you change the variable name to something else. -(https://p5js.org/reference/#/p5/PI) -*/ +"logo": " _ \n /\\| |/\\ \n \\ ` ' / \n / , . \\ \n \\/|_|\\/ \n\n" ``` - -* Redefining p5.js reserved function -```js -function setup() { - //text is a p5.js reserved function - let text = 100; -} - -/* -FES will show: -p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. -*/ +i18next supports interpolation, which allows us to pass a variable to generate a message dynamically. We use curly brackets twice `{{}}` to set a placeholder of the variable: ``` +"greeting": "Hello, {{who}}!" +``` +Here, the key is `greeting`, and the variable name is `who`. -### `core/friendly_errors/fes_core/checkForUserDefinedFunctions()`: -* Checks if any user defined function (`setup()`, `draw()`, `mouseMoved()`, etc.) has been used with a capitalization mistake -* For example -```js -function preLoad() { - loadImage('myimage.png'); -} -/* -FES will show: -p5.js says: It seems that you may have accidentally written preLoad instead of preload. +To dynamically generate this message, we will need to pass a value: +``` +translator('greeting', { who: 'everyone' } ); +``` +The result generated by `translator` will look like this: +``` +Hello, everyone! +``` -Please correct it if it's not intentional. (http://p5js.org/reference/#/p5/preload) -*/ +Here is an item from `fes`'s `fileLoadError` that demonstrates interpolation: +``` +"image": "It looks like there was a problem loading your image. {{suggestion}}" +``` +To dynamically generate the final message, the FES will call `translator()` with the key and a pre-generated `suggestion` value. +``` +translator('fes.fileLoadError.image', { suggestion }); ``` +#### How to Add or Modify Translation -## Additional Features -* The FES welcomes the developer to p5 and the friendly debugger. -* The FES works in the IDE and the web editor. +The [internationalization doc](https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) has a step-by-step guide on adding and modifying translation files. -## Notes for Developers -* When creating p5.js Objects: any p5.js objects that will be used for parameters will need to assign value for `name` parameter (name of the object) within the class declaration. e.g.: -````javascript -p5.newObject = function(parameter) { - this.parameter = 'some parameter'; - this.name = 'p5.newObject'; -}; -```` -* Inline documentation: allowed parameter types are `Boolean`, `Number`, `String`, and name of the object (see the above bullet point). Use `Array` for any types of Array parameters. If needed, explain what kind of the specific types of array parameter are allowed (e.g. `Number[]`, `String[]`) in the description section. -* Currently supported class types (have their `name` parameter): `p5.Color`, `p5.Element`, `p5.Graphics`, `p5.Renderer`, `p5.Renderer2D`, `p5.Image`, `p5.Table`, `p5.TableRow`, `p5.XML`, `p5.Vector`, `p5.Font`, `p5.Geometry`, `p5.Matrix`, `p5.RendererGL`. -## Disable the FES +## Understanding How FES Works +In this section, we will give an overview of how FES generates and displays messages. For more detailed information on the FES functions, please see our [FES Reference + Dev Notes](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md). -By default, FES is enabled for p5.js, and disabled in p5.min.js to prevent FES functions slowing down the process. The error checking system can significantly slow down your code (up to ~10x in some cases). See the [friendly error performance test](https://github.com/processing/p5.js-website/tree/main/src/assets/learn/performance/code/friendly-error-system/). +#### Overview +p5.js calls the FES from multiple locations for different situations, when: +* The browser throws an error. +* The user code calls a function from the p5.js API. +* Other custom cases where the user would benefit from a help message. -You can disable this with one line of code at the top of your sketch: +#### FES Code Location +You can find the core components of the FES inside: +`src/core/friendly_errors`. +You can find the translation files used by the `translator()` inside: +`translations/`. -```javascript -p5.disableFriendlyErrors = true; // disables FES +#### FES Message Generators +These functions are responsible for catching errors and generating FES messages: +* [`_friendlyFileLoadError()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfile_errorsfriendlyfileloaderror) catches file loading errors. +* [`_validateParameters()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsvalidate_paramsvalidateparameters) checks a p5.js function’s input parameters based on inline documentations. +* [`_fesErrorMontitor()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfes_corefeserrormonitor) handles global errors. +* `helpForMisusedAtTopLevelCode()` is called on window load to check for use of p5.js functions outside of setup() or draw(). +* [`fesCodeReader()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfes_coresketch_readerfescodereader) checks if a p5.js reserved constant or function is redefined by the user. +* [`checkForUserDefinedFunctions()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfes_corecheckforuserdefinedfunctions) checks if any user defined function has been used with a capitalization mistake. -function setup() { - // Do setup stuff -} +#### FES Message Displayer +`fes_core.js/_friendlyError()` prints generated friendly error messages in the console. For example: -function draw() { - // Do drawing stuff -} ``` +p5._friendlyError( + translator('fes.globalErrors.type.notfunc', translationObj) +); +``` +This function can be called anywhere in p5. -Note that this will disable the parts of the FES that cause performance slowdown (like argument checking). Friendly errors that have no performance cost (like giving an descriptive error if a file load fails, or warning you if you try to override p5.js functions in the global space), will remain in place. +## Turning Off the FES +There may be cases where you want to [disable the FES for performance](https://github.com/processing/p5.js/wiki/Optimizing-p5.js-Code-for-Performance#disable-the-friendly-error-system-fes). -## Known Limitations -* The friendly error system slows the program down, so there is an option to turn it off via setting `p5.disableFriendlyErrors = true;`. In addition, the friendly error system is omitted by default in the minified (`p5.min.js`) version. -* FES may still result in false negative cases. These are usually caused by the mismatch between designs of the functions (e.g. drawing functions those are originally designed to be used interchangeably in both 2D and 3D settings) with actual usage cases. For example, drawing a 3D line with -```javascript -const x3; // undefined -line(0, 0, 100, 100, x3, Math.PI); +`p5.disableFriendlyErrors` allows you to turn off the FES when set to `true`. + +Example: ``` - will escape FES, because there is an acceptable parameter pattern (`Number`, `Number`, `Number`, `Number`) in `line()`'s inline documentation for drawing in 2D setting. This also means the current version of FES doesn't check for the environmental variables such as `_renderer.isP3D`. - * FES is only able to detect global variables overwritten when declared using `const` or `var`. If `let` is used, they go undetected. This is not currently solvable due to the way `let` instantiates variables. - - * The functionality described under **`fesErrorMonitor()`** currently only works on the web editor or if running on a local server. For more details see [this](https://github.com/processing/p5.js/pull/4730). - - * The extracting variable/function names feature of FES's `sketch_reader` is not perfect and some cases might go undetected (for eg: when all the code is written in a single line). - -## In The Works -* Identify more common error types and generalize with FES (e.g. `bezierVertex()`, `quadraticVertex()` - required object not initiated; checking Number parameters positive for `nf()` `nfc()` `nfp()` `nfs()`) - -## Thoughts for the Future -* re-introduce color coding for the Web Editor. -* More unit testings. -* More intuitive and narrowed down output messages. -* Completing the spanish translation for `validateParameters()` as well. -* All the colors are checked for being color blind friendly. -* More elaborate ascii is always welcome! -* Extend Global Error catching. This means catching errors that the browser is throwing to the console and matching them with friendly messages. `fesErrorMonitor()` does this for a select few kinds of errors but help in supporting more is welcome :) -* Improve `sketch_reader.js`'s code reading and variable/function name extracting functionality (which extracts names of the function and variables declared by the user in their code). For example currently `sketch_reader.js` is not able to extract variable/function names properly if all the code is written in a single line. -* `sketch_reader.js` can be extended and new features (for example: Alerting the user when they have declared a variable in the `draw()` function) can be added to it to better aid the user. - - -```javascript -// this snippet wraps window.console methods with a new function to modify their functionality -// it is not currently implemented, but could be to give nicer formatted error messages -const original = window.console; -const original_functions = { - log: original.log, - warn: original.warn, - error: original.error -} +p5.disableFriendlyErrors = true; -["log", "warn", "error"].forEach(function(func){ -window.console[func] = function(msg) { -//do something with the msg caught by the wrapper function, then call the original function -original_functions[func].apply(original, arguments) -}; -}); +function setup() { + createCanvas(100, 50); +} ``` + +The single minified file of p5 (p5.min.js) automatically omits the FES. diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index de6c862644..e895db2621 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,7 +1,8 @@ function setup() { // put setup code here + rect(0,0,'rt'); } function draw() { // put drawing code here -} \ No newline at end of file +} diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index c17adb5b4a..ea938e2e37 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -1,79 +1,6 @@ /** * @for p5 * @requires core - * - * This is the main file for the Friendly Error System (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) _fesErrorMontitor. - * - * _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 - * - * This file contains the core as well as miscellaneous functionality of the FES. - * - * helpForMisusedAtTopLevelCode is called by this file on window load to check for use - * of p5.js functions outside of setup() or draw() - * Items 1-3 above are called by functions in the p5 library located in other files. - * - * _fesErrorMonitor can be called either by an error event, an unhandled rejection event - * or it can be manually called in a catch block as follows: - * try { someCode(); } catch(err) { p5._fesErrorMonitor(err); } - * fesErrorMonitor is responsible for handling all kinds of errors that the browser may show. - * It gives a simplified explanation for these. It currently works with some kinds of - * ReferenceError, SyntaxError, and TypeError. The complete list of supported errors can be - * found in browser_errors.js. - * - * _friendlyFileLoadError is called by the loadX() methods. - * _friendlyError can be called by any function to offer a helpful error message. - * - * _validateParameters is called by functions in the p5.js API to help users ensure - * ther are calling p5 function with the right parameter types. The property - * disableFriendlyErrors = false can be set from a p5.js sketch to turn off parameter - * checking. The call sequence from _validateParameters looks something like this: - * - * _validateParameters - * buildArgTypeCache - * addType - * lookupParamDoc - * scoreOverload - * testParamTypes - * testParamType - * getOverloadErrors - * _friendlyParamError - * ValidationError - * report - * friendlyWelcome - * - * The call sequences to _friendlyFileLoadError and _friendlyError are like this: - * _friendlyFileLoadError - * report - * - * _friendlyError - * report - * - * The call sequence for _fesErrorMonitor roughly looks something like: - * _fesErrorMonitor - * processStack - * printFriendlyError - * (if type of error is ReferenceError) - * _handleMisspelling - * computeEditDistance - * report - * report - * printFriendlyStack - * (if type of error is SyntaxError, TypeError, etc) - * report - * printFriendlyStack - * - * report() is the main function that prints directly to console with the output - * of the error helper message. Note: friendlyWelcome() also prints to console directly. */ import p5 from '../main'; import { translator } from '../internationalization'; @@ -171,30 +98,17 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * 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 + * This is a generic method that can be called from anywhere in the p5 + * library to alert users to a common error. * - * @method mapToReference + * @method _friendlyError * @private - * @param {String} message the words to be said - * @param {String} [func] the name of the function to link - * - * @returns {String} + * @param {Number} message message to be printed + * @param {String} [method] name of method + * @param {Number|String} [color] CSS color string or error type */ - 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('/'); - msgWithReference = `${message} (http://p5js.org/reference/#/${referenceSection}/${funcName})`; - } - return msgWithReference; + p5._friendlyError = function(message, method, color) { + p5._report(message, method, color); }; /** @@ -236,18 +150,32 @@ if (typeof IS_MINIFIED !== 'undefined') { log(prefixedMsg); } }; + /** - * This is a generic method that can be called from anywhere in the p5 - * library to alert users to a common error. + * 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 _friendlyError + * @method mapToReference * @private - * @param {Number} message message to be printed - * @param {String} [method] name of method - * @param {Number|String} [color] CSS color string or error type + * @param {String} message the words to be said + * @param {String} [func] the name of the function to link + * + * @returns {String} */ - p5._friendlyError = function(message, method, color) { - p5._report(message, method, color); + 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('/'); + msgWithReference = `${message} (http://p5js.org/reference/#/${referenceSection}/${funcName})`; + } + return msgWithReference; }; /** @@ -265,84 +193,375 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * An implementation of - * https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm to - * compute the Levenshtein distance. It gives a measure of how dissimilar - * two strings are. If the "distance" between them is small enough, it is - * reasonable to think that one is the misspelled version of the other. - * @method computeEditDistance - * @private - * @param {String} w1 the first word - * @param {String} w2 the second word + * The main function for handling global errors. * - * @returns {Number} the "distance" between the two words, a smaller value - * indicates that the words are similar + * Called when an error happens. It detects the type of error + * and generate an appropriate error message. + * + * @method fesErrorMonitor + * @private + * @param {*} e The object to extract error details from */ - const computeEditDistance = (w1, w2) => { - const l1 = w1.length, - l2 = w2.length; - if (l1 === 0) return w2; - if (l2 === 0) return w1; + const fesErrorMonitor = e => { + if (p5.disableFriendlyErrors) 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 prev = []; - let cur = []; + let stacktrace = p5._getErrorStackParser().parse(error); + // process the stacktrace from the browser and simplify it to give + // friendlyStack. + let [isInternal, friendlyStack] = processStack(error, stacktrace); - for (let j = 0; j < l2 + 1; j++) { - cur[j] = j; + // 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; } - prev = cur; + const errList = errorTable[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); - 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; - } + if (matched) { + matchedError = Object.assign({}, obj); + matchedError.match = matched; + break; } - prev = cur; } - return cur[l2]; - }; - - /** - * checks if the various functions such as setup, draw, preload have been - * defined with capitalization mistakes - * @method checkForUserDefinedFunctions - * @private - * @param {*} context The current default context. It's set to window in - * "global mode" and to a p5 instance in "instance mode" - */ - const checkForUserDefinedFunctions = context => { - if (p5.disableFriendlyErrors) return; + if (!matchedError) 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; + // 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 + }; + } - const fxns = {}; - // lowercasename -> actualName mapping - fnNames.forEach(symbol => { - fxns[symbol.toLowerCase()] = symbol; - }); + 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]; - for (const prop of Object.keys(context)) { - const lowercase = prop.toLowerCase(); + if (errSym && handleMisspelling(errSym, error)) { + break; + } - // check if the lowercase property name has an entry in fxns, if the - // actual name with correct capitalization doesnt exist in context, + // if the flow gets this far, this is likely not a misspelling + // of a p5 property/function + let url1 = 'https://p5js.org/examples/data-variable-scope.html'; + let url2 = + 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong'; + p5._friendlyError( + translator('fes.globalErrors.reference.notDefined', { + url1, + url2, + 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 url1 = + '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', { + url1, + url2, + 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 url1 = + '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', { + url1, + url2, + 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 + } */ + + /** + * Checks capitalization for user defined functions. + * + * @method checkForUserDefinedFunctions + * @private + * @param {*} context The current default context. This is 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; + + 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[lowercase] && @@ -360,50 +579,103 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * compares the the symbol caught in the ReferenceErrror to everything - * in misusedAtTopLevel ( all public p5 properties ). The use of - * misusedAtTopLevel here is for convenience as it was an array that was - * already defined when spelling check was implemented. For this particular - * use-case, it's a misnomer. + * Measures dissimilarity between two strings by calculating + * the Levenshtein distance. * - * @method handleMisspelling + * 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} errSym the symbol to whose spelling to check - * @param {Error} error the ReferenceError object + * @param {String} w1 the first word + * @param {String} w2 the second word * - * @returns {Boolean} a boolean value indicating if this error was likely due - * to a mis-spelling + * @returns {Number} the "distance" between the two words, a smaller value + * indicates that the words are similar */ - const handleMisspelling = (errSym, error) => { - if (!misusedAtTopLevelCode) { - defineMisusedAtTopLevelCode(); - } + const computeEditDistance = (w1, w2) => { + const l1 = w1.length, + l2 = w2.length; + if (l1 === 0) return w2; + if (l2 === 0) return w1; - 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]; + let prev = []; + let cur = []; - if (dist < min) min = dist; - }); + for (let j = 0; j < l2 + 1; j++) { + cur[j] = j; + } - // if the closest match has more "distance" than the max allowed threshold - if (min > Math.min(EDIT_DIST_THRESHOLD, errSym.length)) return false; + prev = cur; - // 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 && + 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]; + }; + + /** + * Compares the symbol caught in the ReferenceErrror to everything in + * misusedAtTopLevel ( all public p5 properties ). + * + * @method handleMisspelling + * @private + * @param {String} errSym the symbol to whose spelling to check + * @param {Error} error the ReferenceError object + * + * @returns {Boolean} tell whether error was likely due to typo + */ + const handleMisspelling = (errSym, error) => { + // The name misusedAtTopLevel can be confusing. + // However it is used here because it was an array that was + // already defined when spelling check was implemented. + 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 && @@ -464,8 +736,7 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * prints a friendly stacktrace which only includes user-written functions - * and is easier for newcomers to understand + * Prints a friendly stacktrace for user-written functions * @method printFriendlyStack * @private * @param {Array} friendlyStack @@ -501,12 +772,14 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Takes a stacktrace array and filters out all frames that show internal p5 - * details. It also uses this processed stack to figure out if the error - * 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 error exact location of the error (the - * top of the stack) is a piece of code write in the p5.js library (which may - * or may not have been called from the user's sketch) + * details. + * + * The processed stack is used to find whether the error happended 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 write in the p5.js library + * (which may or may not have been called from the user's sketch) * * @method processStack * @private @@ -514,8 +787,9 @@ if (typeof IS_MINIFIED !== 'undefined') { * @param {Array} stacktrace * * @returns {Array} An array with two elements, [isInternal, friendlyStack] - * isInternal: a boolean indicating if the error happened internally - * friendlyStack: the simplified stacktrace, with internal details filtered + * 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 @@ -633,355 +907,12 @@ if (typeof IS_MINIFIED !== 'undefined') { } return [isInternal, friendlyStack]; }; +}; - /** - * The main function for handling global errors. Called when an error - * happens and is responsible for detecting the type of error that - * has happened and showing the appropriate message - * - * @method fesErrorMonitor - * @private - * @param {*} e The object to extract error details from - */ - const fesErrorMonitor = e => { - if (p5.disableFriendlyErrors) 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 = errorTable[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 url1 = 'https://p5js.org/examples/data-variable-scope.html'; - let url2 = - 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong'; - p5._friendlyError( - translator('fes.globalErrors.reference.notDefined', { - url1, - url2, - 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 url1 = - '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', { - url1, - url2, - 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 url1 = - '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', { - url1, - url2, - 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. +// This is a lazily-defined list of p5 symbols used for detecting misusage +// at top-level code, outside of setup()/draw(). // -// For more details, see https://github.com/processing/p5.js/issues/1121. +// Resolving 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'; diff --git a/src/core/friendly_errors/file_errors.js b/src/core/friendly_errors/file_errors.js index 034353b103..89ff6165c9 100644 --- a/src/core/friendly_errors/file_errors.js +++ b/src/core/friendly_errors/file_errors.js @@ -1,9 +1,6 @@ /** * @for p5 * @requires core - * - * This file contains the part of the FES responsible for dealing with - * file load errors */ import p5 from '../main'; import { translator } from '../internationalization'; @@ -79,19 +76,19 @@ if (typeof IS_MINIFIED !== 'undefined') { }; } }; - - /** - * This is called internally if there is a error during file loading. - * - * @method _friendlyFileLoadError - * @private - * @param {Number} errorType - * @param {String} filePath - */ - p5._friendlyFileLoadError = function(errorType, filePath) { - const { message, method } = fileLoadErrorCases(errorType, filePath); - p5._friendlyError(message, method, 3); - }; } +/** + * Called internally if there is a error during file loading. + * + * @method _friendlyFileLoadError + * @private + * @param {Number} errorType + * @param {String} filePath + */ +p5._friendlyFileLoadError = function(errorType, filePath) { + const { message, method } = fileLoadErrorCases(errorType, filePath); + p5._friendlyError(message, method, 3); +}; + export default p5; diff --git a/src/core/friendly_errors/sketch_reader.js b/src/core/friendly_errors/sketch_reader.js index bc44fbda9b..d0d6fbc809 100644 --- a/src/core/friendly_errors/sketch_reader.js +++ b/src/core/friendly_errors/sketch_reader.js @@ -1,13 +1,13 @@ /** * @for p5 * @requires core + */ + + /* p5._fesCodeReader() with the help of other helper functions + * performs the following tasks * - * This file contains the code for sketch reader functionality - * of the FES. - * p5._fesCodeReader() with the help of other helper functions performs the following tasks - * - * (I) Checks if any p5.js constant or function is declared by the user outside setup and draw function - * and report it. + * (I) Checks if any p5.js constant or function is declared by + * the user outside setup and draw function and report it. * * (II) In setup and draw function it performs: * 1. Extraction of the code written by the user diff --git a/src/core/friendly_errors/validate_params.js b/src/core/friendly_errors/validate_params.js index c4991d5aa5..5f7d8e083b 100644 --- a/src/core/friendly_errors/validate_params.js +++ b/src/core/friendly_errors/validate_params.js @@ -1,9 +1,6 @@ /** * @for p5 * @requires core - * - * This file contains the part of the FES responsible for validating function - * parameters */ import p5 from '../main'; import * as constants from '../constants'; @@ -93,9 +90,14 @@ if (typeof IS_MINIFIED !== 'undefined') { // before and so scoring can be skipped. This also prevents logging multiple // validation messages for the same thing. - // These two functions would be called repeatedly over and over again, - // so they need to be as optimized for performance as possible - + /** + * Query type and return the result as an object + * + * This would be called repeatedly over and over again, + * so it needs to be as optimized for performance as possible + * @method addType + * @private + */ const addType = (value, obj, func) => { let type = typeof value; if (basicTypes[type]) { @@ -157,6 +159,15 @@ if (typeof IS_MINIFIED !== 'undefined') { return obj; }; + + /** + * Build the argument type tree, argumentTree + * + * This would be called repeatedly over and over again, + * so it needs to be as optimized for performance as possible + * @method buildArgTypeCache + * @private + */ const buildArgTypeCache = (func, arr) => { // get the if an argument tree for current function already exists let obj = argumentTree[func]; @@ -183,8 +194,12 @@ if (typeof IS_MINIFIED !== 'undefined') { return obj; }; - // validateParameters() helper functions: - // lookupParamDoc() for querying data.json + /** + * Query data.json + * This is a helper function for validateParameters() + * @method lookupParamDoc + * @private + */ const lookupParamDoc = func => { // look for the docs in the `data.json` datastructure @@ -318,6 +333,14 @@ if (typeof IS_MINIFIED !== 'undefined') { }; }; + /** + * Checks whether input type is Number + * This is a helper function for validateParameters() + * @method isNumber + * @private + * + * @returns {String} a string indicating the type + */ const isNumber = param => { switch (typeof param) { case 'number': @@ -329,6 +352,11 @@ if (typeof IS_MINIFIED !== 'undefined') { } }; + /** + * Test type for non-object type parameter validation + * @method testParamType + * @private + */ const testParamType = (param, type) => { const isArray = param instanceof Array; let matches = true; @@ -373,7 +401,11 @@ if (typeof IS_MINIFIED !== 'undefined') { return matches ? 0 : 1; }; - // testType() for non-object type parameter validation + /** + * Test type for multiple parameters + * @method testParamTypes + * @private + */ const testParamTypes = (param, types) => { let minScore = 9999; for (let i = 0; minScore > 0 && i < types.length; i++) { @@ -383,8 +415,12 @@ if (typeof IS_MINIFIED !== 'undefined') { return minScore; }; - // generate a score (higher is worse) for applying these args to - // this overload. + /** + * generate a score (higher is worse) for applying these args to + * this overload. + * @method scoreOverload + * @private + */ const scoreOverload = (args, argCount, overload, minScore) => { let score = 0; const formats = overload.formats; @@ -416,7 +452,11 @@ if (typeof IS_MINIFIED !== 'undefined') { return score; }; - // gets a list of errors for this overload + /** + * Gets a list of errors for this overload + * @method getOverloadErrors + * @private + */ const getOverloadErrors = (args, argCount, overload) => { const formats = overload.formats; const minParams = overload.minParams; @@ -467,8 +507,12 @@ if (typeof IS_MINIFIED !== 'undefined') { return errorArray; }; - // a custom error type, used by the mocha - // tests when expecting validation errors + /** + * a custom error type, used by the mocha + * tests when expecting validation errors + * @method ValidationError + * @private + */ p5.ValidationError = (name => { class err extends Error { constructor(message, func, type) { @@ -485,7 +529,11 @@ if (typeof IS_MINIFIED !== 'undefined') { return err; })('ValidationError'); - // function for generating console.log() msg + /** + * Prints a friendly msg after parameter validation + * @method _friendlyParamError + * @private + */ p5._friendlyParamError = function(errorObj, func) { let message; let translationObj; @@ -608,10 +656,18 @@ if (typeof IS_MINIFIED !== 'undefined') { } }; - // if a function is called with some set of wrong arguments, and then called - // again with the same set of arguments, the messages due to the second call - // will be supressed. If two tests test on the same wrong arguments, the - // second test won't see the validationError. clearing argumentTree solves it + /** + * Clears cache to avoid having multiple FES messages for the same set of + * parameters. + * + * If a function is called with some set of wrong arguments, and then called + * again with the same set of arguments, the messages due to the second call + * will be supressed. If two tests test on the same wrong arguments, the + * second test won't see the validationError. clearing argumentTree solves it + * + * @method _clearValidateParamsCache + * @private + */ p5._clearValidateParamsCache = function clearValidateParamsCache() { for (let key of Object.keys(argumentTree)) { delete argumentTree[key]; @@ -624,17 +680,19 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * Validates parameters - * param {String} func the name of the function - * param {Array} args user input arguments + * Validates parameters and generates corresponding error messages + * @method _validateParameters + * @private + * @param {String} func the name of the function + * @param {Array} args user input arguments * - * example: + * @example: * const a; * ellipse(10,10,a,5); * console ouput: * "It looks like ellipse received an empty variable in spot #2." * - * example: + * @example: * ellipse(10,"foo",5,5); * console output: * "ellipse was expecting a number for parameter #1, From 94561f0c7bf1f64c648ff6dda37dcdc40649f621 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Sat, 23 Oct 2021 18:28:36 -0700 Subject: [PATCH 02/18] after lint --- src/core/friendly_errors/fes_core.js | 865 +++++++++++----------- src/core/friendly_errors/file_errors.js | 25 +- src/core/friendly_errors/sketch_reader.js | 19 +- 3 files changed, 452 insertions(+), 457 deletions(-) diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index ea938e2e37..a89851fd5b 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -98,17 +98,30 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * This is a generic method that can be called from anywhere in the p5 - * library to alert users to a common error. + * 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 _friendlyError + * @method mapToReference * @private - * @param {Number} message message to be printed - * @param {String} [method] name of method - * @param {Number|String} [color] CSS color string or error type + * @param {String} message the words to be said + * @param {String} [func] the name of the function to link + * + * @returns {String} */ - p5._friendlyError = function(message, method, color) { - p5._report(message, method, color); + 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('/'); + msgWithReference = `${message} (http://p5js.org/reference/#/${referenceSection}/${funcName})`; + } + return msgWithReference; }; /** @@ -150,32 +163,18 @@ if (typeof IS_MINIFIED !== 'undefined') { log(prefixedMsg); } }; - /** - * 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 + * This is a generic method that can be called from anywhere in the p5 + * library to alert users to a common error. * - * @method mapToReference + * @method _friendlyError * @private - * @param {String} message the words to be said - * @param {String} [func] the name of the function to link - * - * @returns {String} + * @param {Number} message message to be printed + * @param {String} [method] name of method + * @param {Number|String} [color] CSS color string or error type */ - 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('/'); - msgWithReference = `${message} (http://p5js.org/reference/#/${referenceSection}/${funcName})`; - } - return msgWithReference; + p5._friendlyError = function(message, method, color) { + p5._report(message, method, color); }; /** @@ -193,363 +192,74 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * The main function for handling global errors. + * Measures dissimilarity between two strings by calculating + * the Levenshtein distance. * - * Called when an error happens. It detects the type of error - * and generate an appropriate error message. + * If the "distance" between them is small enough, it is + * reasonable to think that one is the misspelled version of the other. * - * @method fesErrorMonitor + * Specifically, this uses the Wagner–Fischer algorithm. + * @method computeEditDistance * @private - * @param {*} e The object to extract error details from + * @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 fesErrorMonitor = e => { - if (p5.disableFriendlyErrors) 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; + const computeEditDistance = (w1, w2) => { + const l1 = w1.length, + l2 = w2.length; + if (l1 === 0) return w2; + if (l2 === 0) return w1; - let stacktrace = p5._getErrorStackParser().parse(error); - // process the stacktrace from the browser and simplify it to give - // friendlyStack. - let [isInternal, friendlyStack] = processStack(error, stacktrace); + let prev = []; + let cur = []; - // 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; + for (let j = 0; j < l2 + 1; j++) { + cur[j] = j; } - const errList = errorTable[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); + prev = cur; - if (matched) { - matchedError = Object.assign({}, obj); - matchedError.match = matched; - break; + 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; } - 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 - }; - } + return cur[l2]; + }; - 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]; + /** + * Checks capitalization for user defined functions. + * + * @method checkForUserDefinedFunctions + * @private + * @param {*} context The current default context. This is set to window + * in "global mode" and to a p5 instance in "instance mode" + */ + const checkForUserDefinedFunctions = context => { + if (p5.disableFriendlyErrors) return; - if (errSym && handleMisspelling(errSym, error)) { - break; - } - - // if the flow gets this far, this is likely not a misspelling - // of a p5 property/function - let url1 = 'https://p5js.org/examples/data-variable-scope.html'; - let url2 = - 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong'; - p5._friendlyError( - translator('fes.globalErrors.reference.notDefined', { - url1, - url2, - 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 url1 = - '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', { - url1, - url2, - 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 url1 = - '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', { - url1, - url2, - 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 - } */ - - /** - * Checks capitalization for user defined functions. - * - * @method checkForUserDefinedFunctions - * @private - * @param {*} context The current default context. This is 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 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; const fxns = {}; // lowercasename -> actualName mapping @@ -578,60 +288,6 @@ if (typeof IS_MINIFIED !== 'undefined') { } }; - /** - * 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]; - }; - /** * Compares the symbol caught in the ReferenceErrror to everything in * misusedAtTopLevel ( all public p5 properties ). @@ -644,9 +300,6 @@ if (typeof IS_MINIFIED !== 'undefined') { * @returns {Boolean} tell whether error was likely due to typo */ const handleMisspelling = (errSym, error) => { - // The name misusedAtTopLevel can be confusing. - // However it is used here because it was an array that was - // already defined when spelling check was implemented. if (!misusedAtTopLevelCode) { defineMisusedAtTopLevelCode(); } @@ -907,12 +560,356 @@ if (typeof IS_MINIFIED !== 'undefined') { } return [isInternal, friendlyStack]; }; -}; -// This is a lazily-defined list of p5 symbols used for detecting misusage -// at top-level code, outside of setup()/draw(). + /** + * The main function for handling global errors. + * + * Called when an error happens. It detects the type of error + * and generate an appropriate error message. + * + * @method fesErrorMonitor + * @private + * @param {*} e The object to extract error details from + */ + const fesErrorMonitor = e => { + if (p5.disableFriendlyErrors) 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 = errorTable[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 url1 = 'https://p5js.org/examples/data-variable-scope.html'; + let url2 = + 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong'; + p5._friendlyError( + translator('fes.globalErrors.reference.notDefined', { + url1, + url2, + 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 url1 = + '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', { + url1, + url2, + 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 url1 = + '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', { + url1, + url2, + 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. // -// Resolving https://github.com/processing/p5.js/issues/1121. +// 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'; diff --git a/src/core/friendly_errors/file_errors.js b/src/core/friendly_errors/file_errors.js index 89ff6165c9..aca008b8ab 100644 --- a/src/core/friendly_errors/file_errors.js +++ b/src/core/friendly_errors/file_errors.js @@ -76,19 +76,18 @@ if (typeof IS_MINIFIED !== 'undefined') { }; } }; + /** + * Called internally if there is a error during file loading. + * + * @method _friendlyFileLoadError + * @private + * @param {Number} errorType + * @param {String} filePath + */ + p5._friendlyFileLoadError = function(errorType, filePath) { + const { message, method } = fileLoadErrorCases(errorType, filePath); + p5._friendlyError(message, method, 3); + }; } -/** - * Called internally if there is a error during file loading. - * - * @method _friendlyFileLoadError - * @private - * @param {Number} errorType - * @param {String} filePath - */ -p5._friendlyFileLoadError = function(errorType, filePath) { - const { message, method } = fileLoadErrorCases(errorType, filePath); - p5._friendlyError(message, method, 3); -}; - export default p5; diff --git a/src/core/friendly_errors/sketch_reader.js b/src/core/friendly_errors/sketch_reader.js index d0d6fbc809..be3a446c8f 100644 --- a/src/core/friendly_errors/sketch_reader.js +++ b/src/core/friendly_errors/sketch_reader.js @@ -3,25 +3,24 @@ * @requires core */ - /* p5._fesCodeReader() with the help of other helper functions - * performs the following tasks - * - * (I) Checks if any p5.js constant or function is declared by +import p5 from '../main'; +import { translator } from '../internationalization'; +import * as constants from '../constants'; + +/** + * Checks if any p5.js constant or function is declared by * the user outside setup and draw function and report it. * - * (II) In setup and draw function it performs: + * Also, in setup() and draw() function it performs: * 1. Extraction of the code written by the user * 2. Conversion of the code to an array of lines of code * 3. Catching variable and function decleration * 4. Checking if the declared function/variable is a reserved p5.js * constant or function and report it. * + * @method _fesCodeReader + * @private */ - -import p5 from '../main'; -import { translator } from '../internationalization'; -import * as constants from '../constants'; - if (typeof IS_MINIFIED !== 'undefined') { p5._fesCodeReader = () => {}; } else { From 662411854fc110eec7d1e201611188dee6a4be5f Mon Sep 17 00:00:00 2001 From: A M Chung Date: Mon, 25 Oct 2021 13:36:23 -0700 Subject: [PATCH 03/18] new link --- contributor_docs/friendly_error_system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index 1ad2b56554..046e9cb356 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -6,7 +6,7 @@ The Friendly Error System (FES, 🌸) aims to help new programmers by providing The FES prints messages in the console window, as seen in the [p5.js Web Editor](https://github.com/processing/p5.js-web-editor) and your browser JavaScript console. The single minified file of p5 (p5.min.js) omits the FES. - *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸](https://forms.gle/2qStHJFSiw9XMCQJ8) + *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸](https://forms.gle/4cCGE1ecfoiaMGzt7) ## Writing Friendly Error Messages From 56938df311d301178f764e8c5c2e618494c66624 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Sat, 23 Oct 2021 18:27:58 -0700 Subject: [PATCH 04/18] inline doc organization --- contributor_docs/fes_reference_dev_notes.md | 240 +++++ contributor_docs/friendly_error_system.md | 312 ++---- lib/empty-example/sketch.js | 3 +- src/core/friendly_errors/fes_core.js | 1049 +++++++++---------- src/core/friendly_errors/file_errors.js | 29 +- src/core/friendly_errors/sketch_reader.js | 12 +- src/core/friendly_errors/validate_params.js | 106 +- 7 files changed, 921 insertions(+), 830 deletions(-) create mode 100644 contributor_docs/fes_reference_dev_notes.md diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md new file mode 100644 index 0000000000..868a3fc464 --- /dev/null +++ b/contributor_docs/fes_reference_dev_notes.md @@ -0,0 +1,240 @@ +# FES Reference and Development Notes +This document contains development notes for p5.js's Friendly Error System (FES). + +### `core/friendly_errors/file_errors/friendlyFileLoadError()`: +* This function generates and displays friendly error messages if a file fails to load correctly. It also checks if the size of a file might be too large to load and produces a warning. +* This can be called through : `p5._friendlyFileLoadError(ERROR_TYPE, FILE_PATH)`. +* file loading error example: +````javascript +/// missing font file +let myFont; +function preload() { + myFont = loadFont('assets/OpenSans-Regular.ttf'); +}; +function setup() { + fill('#ED225D'); + textFont(myFont); + textSize(36); + text('p5*js', 10, 50); +}; +function draw() {}; +/// FES will generate the following message in the console: +/// > p5.js says: It looks like there was a problem loading your font. Try checking if the file path [assets/OpenSans-Regular.ttf] is correct, hosting the font online, or running a local server.[https://github.com/processing/p5.js/wiki/Local-server] +```` +* Currently version contains templates for generating error messages for `image`, `XML`, `table`, `text`, `json` and `font` files. +* Implemented to `image/loading_displaying/loadImage()`, `io/files/loadFont()`, `io/files/loadTable()`, `io/files/loadJSON()`, `io/files/loadStrings()`, `io/files/loadXML()`, `io/files/loadBytes()`. +* Error while loading a file due to its large size is implemented to all loadX methods. + +### `core/friendly_errors/validate_params/validateParameters()`: +* This function runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct type of parameters. +* missing parameter example: +````javascript +arc(1, 1, 10.5, 10); +/// FES will generate the following message in the console: +/// > p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] +/// > p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] + +```` +* wrong type example: +````javascript +arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); +/// FES will generate the following message in the console: +/// > p5.js says: arc() was expecting Number for parameter #0 (zero-based index), received string instead. [http://p5js.org/reference/#p5/arc] +```` +* This can be called through: `p5._validateParameters(FUNCT_NAME, ARGUMENTS)` +or, `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` inside the function that requires parameter validation. It is recommended to use static version, `p5._validateParameters` for general purposes. `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` mainly remained for debugging and unit testing purposes. +* Implemented to functions in `color/creating_reading`, `core/2d_primitives`, `core/curves`, and `utilities/string_functions`. + +### `core/friendly_errors/fes_core/fesErrorMonitor()`: +* This function is triggered whenever an error happens in the script. It attempts to help the user by providing more details, +likely causes and ways to address it. + +* Internal Error Example 1 +```js +function preload() { + // error in background() due to it being called in + // preload + background(200); +} + +/* +FES will show: +p5.js says: An error with message "Cannot read property 'background' of undefined" occured inside the p5js library when "background" was called (on line 4 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:4:3]). + +If not stated otherwise, it might be due to "background" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function. (http://p5js.org/reference/#/p5/preload) +*/ +``` + +* Internal Error Example 2 +```js +function setup() { + cnv = createCanvas(200, 200); + cnv.mouseClicked(); +} + +/* + +p5.js says: An error with message "Cannot read property 'bind' of undefined" occured inside the p5js library when mouseClicked was called (on line 3 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:3:7]) + +If not stated otherwise, it might be an issue with the arguments passed to mouseClicked. (http://p5js.org/reference/#/p5/mouseClicked) +*/ +``` + +* Error in user's sketch example (scope) +```js +function setup() { + let b = 1; +} +function draw() { + b += 1; +} +/* +FES will show: +p5.js says: There's an error due to "b" not being defined in the current scope (on line 5 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:5:3]). + +If you have defined it in your code, you should check its scope, spelling, and letter-casing (JavaScript is case-sensitive). For more: +https://p5js.org/examples/data-variable-scope.html +https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong +*/ +``` + +* Error in user's sketch example (spelling) +```js +function setup() { + colour(1, 2, 3); +} +/* +FES will show: +p5.js says: It seems that you may have accidentally written "colour" instead of "color" (on line 2 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:2:3]). + +Please correct it to color if you wish to use the function from p5.js (http://p5js.org/reference/#/p5/color) +*/ +``` + +### `core/friendly_errors/fes_core/sketch_reader/fesCodeReader()`: +* This function is executed everytime when the `load` event is triggered. It checks if a p5.js reserved constant or function is redefined by the user. + +* Redefining p5.js reserved constant +```js +function setup() { + //PI is a p5.js reserved constant + let PI = 100; +} + +/* +FES will show: +p5.js says: you have used a p5.js reserved variable "PI" make sure you change the variable name to something else. +(https://p5js.org/reference/#/p5/PI) +*/ +``` + +* Redefining p5.js reserved function +```js +function setup() { + //text is a p5.js reserved function + let text = 100; +} + +/* +FES will show: +p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. +*/ +``` + +### `core/friendly_errors/fes_core/checkForUserDefinedFunctions()`: +* Checks if any user defined function (`setup()`, `draw()`, `mouseMoved()`, etc.) has been used with a capitalization mistake +* For example +```js +function preLoad() { + loadImage('myimage.png'); +} +/* +FES will show: +p5.js says: It seems that you may have accidentally written preLoad instead of preload. + +Please correct it if it's not intentional. (http://p5js.org/reference/#/p5/preload) +*/ +``` + + +## Additional Features +* The FES welcomes the developer to p5 and the friendly debugger. +* The FES works in the IDE and the web editor. + +## Notes for Developers +* When creating p5.js Objects: any p5.js objects that will be used for parameters will need to assign value for `name` parameter (name of the object) within the class declaration. e.g.: +````javascript +p5.newObject = function(parameter) { + this.parameter = 'some parameter'; + this.name = 'p5.newObject'; +}; +```` +* Inline documentation: allowed parameter types are `Boolean`, `Number`, `String`, and name of the object (see the above bullet point). Use `Array` for any types of Array parameters. If needed, explain what kind of the specific types of array parameter are allowed (e.g. `Number[]`, `String[]`) in the description section. +* Currently supported class types (have their `name` parameter): `p5.Color`, `p5.Element`, `p5.Graphics`, `p5.Renderer`, `p5.Renderer2D`, `p5.Image`, `p5.Table`, `p5.TableRow`, `p5.XML`, `p5.Vector`, `p5.Font`, `p5.Geometry`, `p5.Matrix`, `p5.RendererGL`. + +## Disable the FES + +By default, FES is enabled for p5.js, and disabled in p5.min.js to prevent FES functions slowing down the process. The error checking system can significantly slow down your code (up to ~10x in some cases). See the [friendly error performance test](https://github.com/processing/p5.js-website/tree/main/src/assets/learn/performance/code/friendly-error-system/). + +You can disable this with one line of code at the top of your sketch: + +```javascript +p5.disableFriendlyErrors = true; // disables FES + +function setup() { + // Do setup stuff +} + +function draw() { + // Do drawing stuff +} +``` + +Note that this will disable the parts of the FES that cause performance slowdown (like argument checking). Friendly errors that have no performance cost (like giving an descriptive error if a file load fails, or warning you if you try to override p5.js functions in the global space), will remain in place. + +## Known Limitations +* The friendly error system slows the program down, so there is an option to turn it off via setting `p5.disableFriendlyErrors = true;`. In addition, the friendly error system is omitted by default in the minified (`p5.min.js`) version. +* FES may still result in false negative cases. These are usually caused by the mismatch between designs of the functions (e.g. drawing functions those are originally designed to be used interchangeably in both 2D and 3D settings) with actual usage cases. For example, drawing a 3D line with +```javascript +const x3; // undefined +line(0, 0, 100, 100, x3, Math.PI); +``` + will escape FES, because there is an acceptable parameter pattern (`Number`, `Number`, `Number`, `Number`) in `line()`'s inline documentation for drawing in 2D setting. This also means the current version of FES doesn't check for the environmental variables such as `_renderer.isP3D`. + * FES is only able to detect global variables overwritten when declared using `const` or `var`. If `let` is used, they go undetected. This is not currently solvable due to the way `let` instantiates variables. + + * The functionality described under **`fesErrorMonitor()`** currently only works on the web editor or if running on a local server. For more details see [this](https://github.com/processing/p5.js/pull/4730). + + * The extracting variable/function names feature of FES's `sketch_reader` is not perfect and some cases might go undetected (for eg: when all the code is written in a single line). + +## In The Works +* Identify more common error types and generalize with FES (e.g. `bezierVertex()`, `quadraticVertex()` - required object not initiated; checking Number parameters positive for `nf()` `nfc()` `nfp()` `nfs()`) + +## Thoughts for the Future +* re-introduce color coding for the Web Editor. +* More unit testings. +* More intuitive and narrowed down output messages. +* Completing the spanish translation for `validateParameters()` as well. +* All the colors are checked for being color blind friendly. +* More elaborate ascii is always welcome! +* Extend Global Error catching. This means catching errors that the browser is throwing to the console and matching them with friendly messages. `fesErrorMonitor()` does this for a select few kinds of errors but help in supporting more is welcome :) +* Improve `sketch_reader.js`'s code reading and variable/function name extracting functionality (which extracts names of the function and variables declared by the user in their code). For example currently `sketch_reader.js` is not able to extract variable/function names properly if all the code is written in a single line. +* `sketch_reader.js` can be extended and new features (for example: Alerting the user when they have declared a variable in the `draw()` function) can be added to it to better aid the user. + + +```javascript +// this snippet wraps window.console methods with a new function to modify their functionality +// it is not currently implemented, but could be to give nicer formatted error messages +const original = window.console; +const original_functions = { + log: original.log, + warn: original.warn, + error: original.error +} + +["log", "warn", "error"].forEach(function(func){ +window.console[func] = function(msg) { +//do something with the msg caught by the wrapper function, then call the original function +original_functions[func].apply(original, arguments) +}; +}); +``` diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index e9dd9c48b0..1ad2b56554 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -1,268 +1,132 @@ -# p5.js Friendly Error System (FES) +# 🌸 p5.js Friendly Error System (FES) ## Overview -The Friendly Error System (FES) is a system designed to help new programmers with common user errors as they get started learning. It catches common beginning errors and provides clear language and links to help a user resolve the error. FES is only applied to functions that are ones a user might encounter when they are just starting. An exception is made for particular common gotchas such as trying to load files without running a server, or calling loadImage() with a URL, etc. +The Friendly Error System (FES, 🌸) aims to help new programmers by providing error messages in simple, friendly language. It supplements browser console error messages by adding an alternative description of the error and links to helpful references. -The goal is to create accessible error messages to supplement the often cryptic console errors. For example, Javascript has no support for type checking as default making errors in parameter entry was harder to detect for new Javascript developers. +The FES prints messages in the console window, as seen in the [p5.js Web Editor](https://github.com/processing/p5.js-web-editor) and your browser JavaScript console. The single minified file of p5 (p5.min.js) omits the FES. -Messages generated by FES are written in natural language, linked to documentation, and assume a beginner level. The errors are triggered in multiple files through out p5.js, but most of the work and error writing happens in `src/core/friendly_errors`. + *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸](https://forms.gle/2qStHJFSiw9XMCQJ8) -By default, FES is enabled for `p5.js`, whereas completely disabled in `p5.min.js`. It is possible to disable FES by setting `p5.disableFriendlyErrors = true;`. -So far FES is able to detect and print messages for four kinds of errors: +## Writing Friendly Error Messages -1. `validateParameters()` checks a function’s input parameters based on inline documentations +In this section, we will talk about how to contribute by writing and translating error messages. -2. `friendlyFileLoadError()` catches file loading errors. These two kinds of error checking have been integrated into an existing (selected set of) p5 functions, but developers can add them to more p5 functions, or their own libraries, by calling `p5._validateParameters()` +The FES is a part of the p5.js' [internationalization](https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) effort. We generate all FES messages' content through [i18next](https://www.i18next.com/)-based `translator()` function. This dynamic error message generation happens for all languages, including English - the default language of p5. -3. `friendlyError()` can be called by any function to offer a helpful error message +We welcome contributions from all over the world! 🌐 -4. `helpForMisusedAtTopLevelCode()` is called on window load to check for use of p5.js functions outside of setup() or draw() +#### Writing Best Practice -Apart from this, the FES can also detect several other common errors that the browser may show. -This is handled by `fesErrorMonitor()`. The full list of errors that the FES can work with can be found in [src/core/friendly_errors/browser_errors.js](https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/browser_errors.js). +Writers writing FES messages should prioritize lowering the barrier to understanding error messages and debugging. -The FES can also help the user differentiate between errors that happen inside the library and errors that happen in the sketch. It also detects if the error was caused due to a non-loadX() method being called in `preload()` +Here are some highlights from our upcoming best practice doc: -Please also see inline notes in [src/core/friendly_errors/fes_core.js](https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/fes_core.js) for more technical information. +* Use simple sentences. Consider breaking your sentence into smaller blocks for best utilizing i18next's [interpolation](https://www.i18next.com/translation-function/interpolation) feature. +* Keep the language friendly and inclusive. Look for possible bias and harm in your language. Adhere to [p5.js Code of Conduct](https://github.com/processing/p5.js/blob/main/CODE_OF_CONDUCT.md#p5js-code-of-conduct). +* Avoid using figures of speech. Prioritize cross-cultural communication. +* Try to spot possible "[expert blind spots](https://tilt.colostate.edu/TipsAndGuides/Tip/181)" in an error message and its related docs. +* Introduce one technical concept or term at a time—link one external resource written in a beginner-friendly language with plenty of short, practical examples. -The FES can detect the declaration of reserved p5.js constants (for eg: PI) and functions (for eg: text) by the user. This operation is performed by `fesCodeReader()` in `sketch_reader.js`. You can have a look at the full code [src/core/friendly_errors/sketch_reader.js](https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/sketch_reader.js) +#### Translation File Location -### `core/friendly_errors/file_errors/friendlyFileLoadError()`: -* This function generates and displays friendly error messages if a file fails to load correctly. It also checks if the size of a file might be too large to load and produces a warning. -* This can be called through : `p5._friendlyFileLoadError(ERROR_TYPE, FILE_PATH)`. -* file loading error example: -````javascript -/// missing font file -let myFont; -function preload() { - myFont = loadFont('assets/OpenSans-Regular.ttf'); -}; -function setup() { - fill('#ED225D'); - textFont(myFont); - textSize(36); - text('p5*js', 10, 50); -}; -function draw() {}; -/// FES will generate the following message in the console: -/// > p5.js says: It looks like there was a problem loading your font. Try checking if the file path [assets/OpenSans-Regular.ttf] is correct, hosting the font online, or running a local server.[https://github.com/processing/p5.js/wiki/Local-server] -```` -* Currently version contains templates for generating error messages for `image`, `XML`, `table`, `text`, `json` and `font` files. -* Implemented to `image/loading_displaying/loadImage()`, `io/files/loadFont()`, `io/files/loadTable()`, `io/files/loadJSON()`, `io/files/loadStrings()`, `io/files/loadXML()`, `io/files/loadBytes()`. -* Error while loading a file due to its large size is implemented to all loadX methods. - -### `core/friendly_errors/validate_params/validateParameters()`: -* This function runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct type of parameters. -* missing parameter example: -````javascript -arc(1, 1, 10.5, 10); -/// FES will generate the following message in the console: -/// > p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] -/// > p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] - -```` -* wrong type example: -````javascript -arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); -/// FES will generate the following message in the console: -/// > p5.js says: arc() was expecting Number for parameter #0 (zero-based index), received string instead. [http://p5js.org/reference/#p5/arc] -```` -* This can be called through: `p5._validateParameters(FUNCT_NAME, ARGUMENTS)` -or, `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` inside the function that requires parameter validation. It is recommended to use static version, `p5._validateParameters` for general purposes. `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` mainly remained for debugging and unit testing purposes. -* Implemented to functions in `color/creating_reading`, `core/2d_primitives`, `core/curves`, and `utilities/string_functions`. - -### `core/friendly_errors/fes_core/fesErrorMonitor()`: -* This function is triggered whenever an error happens in the script. It attempts to help the user by providing more details, -likely causes and ways to address it. - -* Internal Error Example 1 -```js -function preload() { - // error in background() due to it being called in - // preload - background(200); -} - -/* -FES will show: -p5.js says: An error with message "Cannot read property 'background' of undefined" occured inside the p5js library when "background" was called (on line 4 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:4:3]). - -If not stated otherwise, it might be due to "background" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function. (http://p5js.org/reference/#/p5/preload) -*/ +`translator()` is based on i18next and imported from `src/core/internationalization.js`. It generates messages by looking up text data from a JSON translation file: +``` +translations/{{detected locale code, default=en}}/translation.json ``` -* Internal Error Example 2 -```js -function setup() { - cnv = createCanvas(200, 200); - cnv.mouseClicked(); -} +Example: +If the detected browser locale is Korean (language designator: `ko`), the `translator()` will read in translated text blocks from `translations/ko/translation.json`. Then `translator()` will assemble the text blocks into the final message. -/* +The language designator can also include regional information, such as `es-PE` (Spanish from Peru). -p5.js says: An error with message "Cannot read property 'bind' of undefined" occured inside the p5js library when mouseClicked was called (on line 3 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:3:7]) +#### Translation File Structure +`translation.json` has a [format used by i18next](https://www.i18next.com/misc/json-format). -If not stated otherwise, it might be an issue with the arguments passed to mouseClicked. (http://p5js.org/reference/#/p5/mouseClicked) -*/ +The basic format of a translation file's item has a key and a value (message) in double quotation marks `""`, closed by the curly brackets `{}`: ``` - -* Error in user's sketch example (scope) -```js -function setup() { - let b = 1; -} -function draw() { - b += 1; -} -/* -FES will show: -p5.js says: There's an error due to "b" not being defined in the current scope (on line 5 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:5:3]). - -If you have defined it in your code, you should check its scope, spelling, and letter-casing (JavaScript is case-sensitive). For more: -https://p5js.org/examples/data-variable-scope.html -https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong -*/ +{ "key": "value" } ``` - -* Error in user's sketch example (spelling) -```js -function setup() { - colour(1, 2, 3); -} -/* -FES will show: -p5.js says: It seems that you may have accidentally written "colour" instead of "color" (on line 2 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:2:3]). - -Please correct it to color if you wish to use the function from p5.js (http://p5js.org/reference/#/p5/color) -*/ +For example, we have a ASCII logo saved in this format: ``` - -### `core/friendly_errors/fes_core/sketch_reader/fesCodeReader()`: -* This function is executed everytime when the `load` event is triggered. It checks if a p5.js reserved constant or function is redefined by the user. - -* Redefining p5.js reserved constant -```js -function setup() { - //PI is a p5.js reserved constant - let PI = 100; -} - -/* -FES will show: -p5.js says: you have used a p5.js reserved variable "PI" make sure you change the variable name to something else. -(https://p5js.org/reference/#/p5/PI) -*/ +"logo": " _ \n /\\| |/\\ \n \\ ` ' / \n / , . \\ \n \\/|_|\\/ \n\n" ``` - -* Redefining p5.js reserved function -```js -function setup() { - //text is a p5.js reserved function - let text = 100; -} - -/* -FES will show: -p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. -*/ +i18next supports interpolation, which allows us to pass a variable to generate a message dynamically. We use curly brackets twice `{{}}` to set a placeholder of the variable: ``` +"greeting": "Hello, {{who}}!" +``` +Here, the key is `greeting`, and the variable name is `who`. -### `core/friendly_errors/fes_core/checkForUserDefinedFunctions()`: -* Checks if any user defined function (`setup()`, `draw()`, `mouseMoved()`, etc.) has been used with a capitalization mistake -* For example -```js -function preLoad() { - loadImage('myimage.png'); -} -/* -FES will show: -p5.js says: It seems that you may have accidentally written preLoad instead of preload. +To dynamically generate this message, we will need to pass a value: +``` +translator('greeting', { who: 'everyone' } ); +``` +The result generated by `translator` will look like this: +``` +Hello, everyone! +``` -Please correct it if it's not intentional. (http://p5js.org/reference/#/p5/preload) -*/ +Here is an item from `fes`'s `fileLoadError` that demonstrates interpolation: +``` +"image": "It looks like there was a problem loading your image. {{suggestion}}" +``` +To dynamically generate the final message, the FES will call `translator()` with the key and a pre-generated `suggestion` value. +``` +translator('fes.fileLoadError.image', { suggestion }); ``` +#### How to Add or Modify Translation -## Additional Features -* The FES welcomes the developer to p5 and the friendly debugger. -* The FES works in the IDE and the web editor. +The [internationalization doc](https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) has a step-by-step guide on adding and modifying translation files. -## Notes for Developers -* When creating p5.js Objects: any p5.js objects that will be used for parameters will need to assign value for `name` parameter (name of the object) within the class declaration. e.g.: -````javascript -p5.newObject = function(parameter) { - this.parameter = 'some parameter'; - this.name = 'p5.newObject'; -}; -```` -* Inline documentation: allowed parameter types are `Boolean`, `Number`, `String`, and name of the object (see the above bullet point). Use `Array` for any types of Array parameters. If needed, explain what kind of the specific types of array parameter are allowed (e.g. `Number[]`, `String[]`) in the description section. -* Currently supported class types (have their `name` parameter): `p5.Color`, `p5.Element`, `p5.Graphics`, `p5.Renderer`, `p5.Renderer2D`, `p5.Image`, `p5.Table`, `p5.TableRow`, `p5.XML`, `p5.Vector`, `p5.Font`, `p5.Geometry`, `p5.Matrix`, `p5.RendererGL`. -## Disable the FES +## Understanding How FES Works +In this section, we will give an overview of how FES generates and displays messages. For more detailed information on the FES functions, please see our [FES Reference + Dev Notes](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md). -By default, FES is enabled for p5.js, and disabled in p5.min.js to prevent FES functions slowing down the process. The error checking system can significantly slow down your code (up to ~10x in some cases). See the [friendly error performance test](https://github.com/processing/p5.js-website/tree/main/src/assets/learn/performance/code/friendly-error-system/). +#### Overview +p5.js calls the FES from multiple locations for different situations, when: +* The browser throws an error. +* The user code calls a function from the p5.js API. +* Other custom cases where the user would benefit from a help message. -You can disable this with one line of code at the top of your sketch: +#### FES Code Location +You can find the core components of the FES inside: +`src/core/friendly_errors`. +You can find the translation files used by the `translator()` inside: +`translations/`. -```javascript -p5.disableFriendlyErrors = true; // disables FES +#### FES Message Generators +These functions are responsible for catching errors and generating FES messages: +* [`_friendlyFileLoadError()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfile_errorsfriendlyfileloaderror) catches file loading errors. +* [`_validateParameters()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsvalidate_paramsvalidateparameters) checks a p5.js function’s input parameters based on inline documentations. +* [`_fesErrorMontitor()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfes_corefeserrormonitor) handles global errors. +* `helpForMisusedAtTopLevelCode()` is called on window load to check for use of p5.js functions outside of setup() or draw(). +* [`fesCodeReader()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfes_coresketch_readerfescodereader) checks if a p5.js reserved constant or function is redefined by the user. +* [`checkForUserDefinedFunctions()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfes_corecheckforuserdefinedfunctions) checks if any user defined function has been used with a capitalization mistake. -function setup() { - // Do setup stuff -} +#### FES Message Displayer +`fes_core.js/_friendlyError()` prints generated friendly error messages in the console. For example: -function draw() { - // Do drawing stuff -} ``` +p5._friendlyError( + translator('fes.globalErrors.type.notfunc', translationObj) +); +``` +This function can be called anywhere in p5. -Note that this will disable the parts of the FES that cause performance slowdown (like argument checking). Friendly errors that have no performance cost (like giving an descriptive error if a file load fails, or warning you if you try to override p5.js functions in the global space), will remain in place. +## Turning Off the FES +There may be cases where you want to [disable the FES for performance](https://github.com/processing/p5.js/wiki/Optimizing-p5.js-Code-for-Performance#disable-the-friendly-error-system-fes). -## Known Limitations -* The friendly error system slows the program down, so there is an option to turn it off via setting `p5.disableFriendlyErrors = true;`. In addition, the friendly error system is omitted by default in the minified (`p5.min.js`) version. -* FES may still result in false negative cases. These are usually caused by the mismatch between designs of the functions (e.g. drawing functions those are originally designed to be used interchangeably in both 2D and 3D settings) with actual usage cases. For example, drawing a 3D line with -```javascript -const x3; // undefined -line(0, 0, 100, 100, x3, Math.PI); +`p5.disableFriendlyErrors` allows you to turn off the FES when set to `true`. + +Example: ``` - will escape FES, because there is an acceptable parameter pattern (`Number`, `Number`, `Number`, `Number`) in `line()`'s inline documentation for drawing in 2D setting. This also means the current version of FES doesn't check for the environmental variables such as `_renderer.isP3D`. - * FES is only able to detect global variables overwritten when declared using `const` or `var`. If `let` is used, they go undetected. This is not currently solvable due to the way `let` instantiates variables. - - * The functionality described under **`fesErrorMonitor()`** currently only works on the web editor or if running on a local server. For more details see [this](https://github.com/processing/p5.js/pull/4730). - - * The extracting variable/function names feature of FES's `sketch_reader` is not perfect and some cases might go undetected (for eg: when all the code is written in a single line). - -## In The Works -* Identify more common error types and generalize with FES (e.g. `bezierVertex()`, `quadraticVertex()` - required object not initiated; checking Number parameters positive for `nf()` `nfc()` `nfp()` `nfs()`) - -## Thoughts for the Future -* re-introduce color coding for the Web Editor. -* More unit testings. -* More intuitive and narrowed down output messages. -* Completing the spanish translation for `validateParameters()` as well. -* All the colors are checked for being color blind friendly. -* More elaborate ascii is always welcome! -* Extend Global Error catching. This means catching errors that the browser is throwing to the console and matching them with friendly messages. `fesErrorMonitor()` does this for a select few kinds of errors but help in supporting more is welcome :) -* Improve `sketch_reader.js`'s code reading and variable/function name extracting functionality (which extracts names of the function and variables declared by the user in their code). For example currently `sketch_reader.js` is not able to extract variable/function names properly if all the code is written in a single line. -* `sketch_reader.js` can be extended and new features (for example: Alerting the user when they have declared a variable in the `draw()` function) can be added to it to better aid the user. - - -```javascript -// this snippet wraps window.console methods with a new function to modify their functionality -// it is not currently implemented, but could be to give nicer formatted error messages -const original = window.console; -const original_functions = { - log: original.log, - warn: original.warn, - error: original.error -} +p5.disableFriendlyErrors = true; -["log", "warn", "error"].forEach(function(func){ -window.console[func] = function(msg) { -//do something with the msg caught by the wrapper function, then call the original function -original_functions[func].apply(original, arguments) -}; -}); +function setup() { + createCanvas(100, 50); +} ``` + +The single minified file of p5 (p5.min.js) automatically omits the FES. diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index de6c862644..e895db2621 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,7 +1,8 @@ function setup() { // put setup code here + rect(0,0,'rt'); } function draw() { // put drawing code here -} \ No newline at end of file +} diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index c17adb5b4a..ea938e2e37 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -1,79 +1,6 @@ /** * @for p5 * @requires core - * - * This is the main file for the Friendly Error System (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) _fesErrorMontitor. - * - * _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 - * - * This file contains the core as well as miscellaneous functionality of the FES. - * - * helpForMisusedAtTopLevelCode is called by this file on window load to check for use - * of p5.js functions outside of setup() or draw() - * Items 1-3 above are called by functions in the p5 library located in other files. - * - * _fesErrorMonitor can be called either by an error event, an unhandled rejection event - * or it can be manually called in a catch block as follows: - * try { someCode(); } catch(err) { p5._fesErrorMonitor(err); } - * fesErrorMonitor is responsible for handling all kinds of errors that the browser may show. - * It gives a simplified explanation for these. It currently works with some kinds of - * ReferenceError, SyntaxError, and TypeError. The complete list of supported errors can be - * found in browser_errors.js. - * - * _friendlyFileLoadError is called by the loadX() methods. - * _friendlyError can be called by any function to offer a helpful error message. - * - * _validateParameters is called by functions in the p5.js API to help users ensure - * ther are calling p5 function with the right parameter types. The property - * disableFriendlyErrors = false can be set from a p5.js sketch to turn off parameter - * checking. The call sequence from _validateParameters looks something like this: - * - * _validateParameters - * buildArgTypeCache - * addType - * lookupParamDoc - * scoreOverload - * testParamTypes - * testParamType - * getOverloadErrors - * _friendlyParamError - * ValidationError - * report - * friendlyWelcome - * - * The call sequences to _friendlyFileLoadError and _friendlyError are like this: - * _friendlyFileLoadError - * report - * - * _friendlyError - * report - * - * The call sequence for _fesErrorMonitor roughly looks something like: - * _fesErrorMonitor - * processStack - * printFriendlyError - * (if type of error is ReferenceError) - * _handleMisspelling - * computeEditDistance - * report - * report - * printFriendlyStack - * (if type of error is SyntaxError, TypeError, etc) - * report - * printFriendlyStack - * - * report() is the main function that prints directly to console with the output - * of the error helper message. Note: friendlyWelcome() also prints to console directly. */ import p5 from '../main'; import { translator } from '../internationalization'; @@ -171,30 +98,17 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * 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 + * This is a generic method that can be called from anywhere in the p5 + * library to alert users to a common error. * - * @method mapToReference + * @method _friendlyError * @private - * @param {String} message the words to be said - * @param {String} [func] the name of the function to link - * - * @returns {String} + * @param {Number} message message to be printed + * @param {String} [method] name of method + * @param {Number|String} [color] CSS color string or error type */ - 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('/'); - msgWithReference = `${message} (http://p5js.org/reference/#/${referenceSection}/${funcName})`; - } - return msgWithReference; + p5._friendlyError = function(message, method, color) { + p5._report(message, method, color); }; /** @@ -236,18 +150,32 @@ if (typeof IS_MINIFIED !== 'undefined') { log(prefixedMsg); } }; + /** - * This is a generic method that can be called from anywhere in the p5 - * library to alert users to a common error. + * 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 _friendlyError + * @method mapToReference * @private - * @param {Number} message message to be printed - * @param {String} [method] name of method - * @param {Number|String} [color] CSS color string or error type + * @param {String} message the words to be said + * @param {String} [func] the name of the function to link + * + * @returns {String} */ - p5._friendlyError = function(message, method, color) { - p5._report(message, method, color); + 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('/'); + msgWithReference = `${message} (http://p5js.org/reference/#/${referenceSection}/${funcName})`; + } + return msgWithReference; }; /** @@ -265,84 +193,375 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * An implementation of - * https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm to - * compute the Levenshtein distance. It gives a measure of how dissimilar - * two strings are. If the "distance" between them is small enough, it is - * reasonable to think that one is the misspelled version of the other. - * @method computeEditDistance - * @private - * @param {String} w1 the first word - * @param {String} w2 the second word + * The main function for handling global errors. * - * @returns {Number} the "distance" between the two words, a smaller value - * indicates that the words are similar + * Called when an error happens. It detects the type of error + * and generate an appropriate error message. + * + * @method fesErrorMonitor + * @private + * @param {*} e The object to extract error details from */ - const computeEditDistance = (w1, w2) => { - const l1 = w1.length, - l2 = w2.length; - if (l1 === 0) return w2; - if (l2 === 0) return w1; + const fesErrorMonitor = e => { + if (p5.disableFriendlyErrors) 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 prev = []; - let cur = []; + let stacktrace = p5._getErrorStackParser().parse(error); + // process the stacktrace from the browser and simplify it to give + // friendlyStack. + let [isInternal, friendlyStack] = processStack(error, stacktrace); - for (let j = 0; j < l2 + 1; j++) { - cur[j] = j; + // 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; } - prev = cur; + const errList = errorTable[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); - 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; - } + if (matched) { + matchedError = Object.assign({}, obj); + matchedError.match = matched; + break; } - prev = cur; } - return cur[l2]; - }; - - /** - * checks if the various functions such as setup, draw, preload have been - * defined with capitalization mistakes - * @method checkForUserDefinedFunctions - * @private - * @param {*} context The current default context. It's set to window in - * "global mode" and to a p5 instance in "instance mode" - */ - const checkForUserDefinedFunctions = context => { - if (p5.disableFriendlyErrors) return; + if (!matchedError) 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; + // 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 + }; + } - const fxns = {}; - // lowercasename -> actualName mapping - fnNames.forEach(symbol => { - fxns[symbol.toLowerCase()] = symbol; - }); + 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]; - for (const prop of Object.keys(context)) { - const lowercase = prop.toLowerCase(); + if (errSym && handleMisspelling(errSym, error)) { + break; + } - // check if the lowercase property name has an entry in fxns, if the - // actual name with correct capitalization doesnt exist in context, + // if the flow gets this far, this is likely not a misspelling + // of a p5 property/function + let url1 = 'https://p5js.org/examples/data-variable-scope.html'; + let url2 = + 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong'; + p5._friendlyError( + translator('fes.globalErrors.reference.notDefined', { + url1, + url2, + 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 url1 = + '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', { + url1, + url2, + 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 url1 = + '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', { + url1, + url2, + 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 + } */ + + /** + * Checks capitalization for user defined functions. + * + * @method checkForUserDefinedFunctions + * @private + * @param {*} context The current default context. This is 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; + + 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[lowercase] && @@ -360,50 +579,103 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * compares the the symbol caught in the ReferenceErrror to everything - * in misusedAtTopLevel ( all public p5 properties ). The use of - * misusedAtTopLevel here is for convenience as it was an array that was - * already defined when spelling check was implemented. For this particular - * use-case, it's a misnomer. + * Measures dissimilarity between two strings by calculating + * the Levenshtein distance. * - * @method handleMisspelling + * 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} errSym the symbol to whose spelling to check - * @param {Error} error the ReferenceError object + * @param {String} w1 the first word + * @param {String} w2 the second word * - * @returns {Boolean} a boolean value indicating if this error was likely due - * to a mis-spelling + * @returns {Number} the "distance" between the two words, a smaller value + * indicates that the words are similar */ - const handleMisspelling = (errSym, error) => { - if (!misusedAtTopLevelCode) { - defineMisusedAtTopLevelCode(); - } + const computeEditDistance = (w1, w2) => { + const l1 = w1.length, + l2 = w2.length; + if (l1 === 0) return w2; + if (l2 === 0) return w1; - 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]; + let prev = []; + let cur = []; - if (dist < min) min = dist; - }); + for (let j = 0; j < l2 + 1; j++) { + cur[j] = j; + } - // if the closest match has more "distance" than the max allowed threshold - if (min > Math.min(EDIT_DIST_THRESHOLD, errSym.length)) return false; + prev = cur; - // 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 && + 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]; + }; + + /** + * Compares the symbol caught in the ReferenceErrror to everything in + * misusedAtTopLevel ( all public p5 properties ). + * + * @method handleMisspelling + * @private + * @param {String} errSym the symbol to whose spelling to check + * @param {Error} error the ReferenceError object + * + * @returns {Boolean} tell whether error was likely due to typo + */ + const handleMisspelling = (errSym, error) => { + // The name misusedAtTopLevel can be confusing. + // However it is used here because it was an array that was + // already defined when spelling check was implemented. + 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 && @@ -464,8 +736,7 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * prints a friendly stacktrace which only includes user-written functions - * and is easier for newcomers to understand + * Prints a friendly stacktrace for user-written functions * @method printFriendlyStack * @private * @param {Array} friendlyStack @@ -501,12 +772,14 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Takes a stacktrace array and filters out all frames that show internal p5 - * details. It also uses this processed stack to figure out if the error - * 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 error exact location of the error (the - * top of the stack) is a piece of code write in the p5.js library (which may - * or may not have been called from the user's sketch) + * details. + * + * The processed stack is used to find whether the error happended 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 write in the p5.js library + * (which may or may not have been called from the user's sketch) * * @method processStack * @private @@ -514,8 +787,9 @@ if (typeof IS_MINIFIED !== 'undefined') { * @param {Array} stacktrace * * @returns {Array} An array with two elements, [isInternal, friendlyStack] - * isInternal: a boolean indicating if the error happened internally - * friendlyStack: the simplified stacktrace, with internal details filtered + * 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 @@ -633,355 +907,12 @@ if (typeof IS_MINIFIED !== 'undefined') { } return [isInternal, friendlyStack]; }; +}; - /** - * The main function for handling global errors. Called when an error - * happens and is responsible for detecting the type of error that - * has happened and showing the appropriate message - * - * @method fesErrorMonitor - * @private - * @param {*} e The object to extract error details from - */ - const fesErrorMonitor = e => { - if (p5.disableFriendlyErrors) 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 = errorTable[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 url1 = 'https://p5js.org/examples/data-variable-scope.html'; - let url2 = - 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong'; - p5._friendlyError( - translator('fes.globalErrors.reference.notDefined', { - url1, - url2, - 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 url1 = - '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', { - url1, - url2, - 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 url1 = - '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', { - url1, - url2, - 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. +// This is a lazily-defined list of p5 symbols used for detecting misusage +// at top-level code, outside of setup()/draw(). // -// For more details, see https://github.com/processing/p5.js/issues/1121. +// Resolving 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'; diff --git a/src/core/friendly_errors/file_errors.js b/src/core/friendly_errors/file_errors.js index 034353b103..89ff6165c9 100644 --- a/src/core/friendly_errors/file_errors.js +++ b/src/core/friendly_errors/file_errors.js @@ -1,9 +1,6 @@ /** * @for p5 * @requires core - * - * This file contains the part of the FES responsible for dealing with - * file load errors */ import p5 from '../main'; import { translator } from '../internationalization'; @@ -79,19 +76,19 @@ if (typeof IS_MINIFIED !== 'undefined') { }; } }; - - /** - * This is called internally if there is a error during file loading. - * - * @method _friendlyFileLoadError - * @private - * @param {Number} errorType - * @param {String} filePath - */ - p5._friendlyFileLoadError = function(errorType, filePath) { - const { message, method } = fileLoadErrorCases(errorType, filePath); - p5._friendlyError(message, method, 3); - }; } +/** + * Called internally if there is a error during file loading. + * + * @method _friendlyFileLoadError + * @private + * @param {Number} errorType + * @param {String} filePath + */ +p5._friendlyFileLoadError = function(errorType, filePath) { + const { message, method } = fileLoadErrorCases(errorType, filePath); + p5._friendlyError(message, method, 3); +}; + export default p5; diff --git a/src/core/friendly_errors/sketch_reader.js b/src/core/friendly_errors/sketch_reader.js index bc44fbda9b..d0d6fbc809 100644 --- a/src/core/friendly_errors/sketch_reader.js +++ b/src/core/friendly_errors/sketch_reader.js @@ -1,13 +1,13 @@ /** * @for p5 * @requires core + */ + + /* p5._fesCodeReader() with the help of other helper functions + * performs the following tasks * - * This file contains the code for sketch reader functionality - * of the FES. - * p5._fesCodeReader() with the help of other helper functions performs the following tasks - * - * (I) Checks if any p5.js constant or function is declared by the user outside setup and draw function - * and report it. + * (I) Checks if any p5.js constant or function is declared by + * the user outside setup and draw function and report it. * * (II) In setup and draw function it performs: * 1. Extraction of the code written by the user diff --git a/src/core/friendly_errors/validate_params.js b/src/core/friendly_errors/validate_params.js index c4991d5aa5..5f7d8e083b 100644 --- a/src/core/friendly_errors/validate_params.js +++ b/src/core/friendly_errors/validate_params.js @@ -1,9 +1,6 @@ /** * @for p5 * @requires core - * - * This file contains the part of the FES responsible for validating function - * parameters */ import p5 from '../main'; import * as constants from '../constants'; @@ -93,9 +90,14 @@ if (typeof IS_MINIFIED !== 'undefined') { // before and so scoring can be skipped. This also prevents logging multiple // validation messages for the same thing. - // These two functions would be called repeatedly over and over again, - // so they need to be as optimized for performance as possible - + /** + * Query type and return the result as an object + * + * This would be called repeatedly over and over again, + * so it needs to be as optimized for performance as possible + * @method addType + * @private + */ const addType = (value, obj, func) => { let type = typeof value; if (basicTypes[type]) { @@ -157,6 +159,15 @@ if (typeof IS_MINIFIED !== 'undefined') { return obj; }; + + /** + * Build the argument type tree, argumentTree + * + * This would be called repeatedly over and over again, + * so it needs to be as optimized for performance as possible + * @method buildArgTypeCache + * @private + */ const buildArgTypeCache = (func, arr) => { // get the if an argument tree for current function already exists let obj = argumentTree[func]; @@ -183,8 +194,12 @@ if (typeof IS_MINIFIED !== 'undefined') { return obj; }; - // validateParameters() helper functions: - // lookupParamDoc() for querying data.json + /** + * Query data.json + * This is a helper function for validateParameters() + * @method lookupParamDoc + * @private + */ const lookupParamDoc = func => { // look for the docs in the `data.json` datastructure @@ -318,6 +333,14 @@ if (typeof IS_MINIFIED !== 'undefined') { }; }; + /** + * Checks whether input type is Number + * This is a helper function for validateParameters() + * @method isNumber + * @private + * + * @returns {String} a string indicating the type + */ const isNumber = param => { switch (typeof param) { case 'number': @@ -329,6 +352,11 @@ if (typeof IS_MINIFIED !== 'undefined') { } }; + /** + * Test type for non-object type parameter validation + * @method testParamType + * @private + */ const testParamType = (param, type) => { const isArray = param instanceof Array; let matches = true; @@ -373,7 +401,11 @@ if (typeof IS_MINIFIED !== 'undefined') { return matches ? 0 : 1; }; - // testType() for non-object type parameter validation + /** + * Test type for multiple parameters + * @method testParamTypes + * @private + */ const testParamTypes = (param, types) => { let minScore = 9999; for (let i = 0; minScore > 0 && i < types.length; i++) { @@ -383,8 +415,12 @@ if (typeof IS_MINIFIED !== 'undefined') { return minScore; }; - // generate a score (higher is worse) for applying these args to - // this overload. + /** + * generate a score (higher is worse) for applying these args to + * this overload. + * @method scoreOverload + * @private + */ const scoreOverload = (args, argCount, overload, minScore) => { let score = 0; const formats = overload.formats; @@ -416,7 +452,11 @@ if (typeof IS_MINIFIED !== 'undefined') { return score; }; - // gets a list of errors for this overload + /** + * Gets a list of errors for this overload + * @method getOverloadErrors + * @private + */ const getOverloadErrors = (args, argCount, overload) => { const formats = overload.formats; const minParams = overload.minParams; @@ -467,8 +507,12 @@ if (typeof IS_MINIFIED !== 'undefined') { return errorArray; }; - // a custom error type, used by the mocha - // tests when expecting validation errors + /** + * a custom error type, used by the mocha + * tests when expecting validation errors + * @method ValidationError + * @private + */ p5.ValidationError = (name => { class err extends Error { constructor(message, func, type) { @@ -485,7 +529,11 @@ if (typeof IS_MINIFIED !== 'undefined') { return err; })('ValidationError'); - // function for generating console.log() msg + /** + * Prints a friendly msg after parameter validation + * @method _friendlyParamError + * @private + */ p5._friendlyParamError = function(errorObj, func) { let message; let translationObj; @@ -608,10 +656,18 @@ if (typeof IS_MINIFIED !== 'undefined') { } }; - // if a function is called with some set of wrong arguments, and then called - // again with the same set of arguments, the messages due to the second call - // will be supressed. If two tests test on the same wrong arguments, the - // second test won't see the validationError. clearing argumentTree solves it + /** + * Clears cache to avoid having multiple FES messages for the same set of + * parameters. + * + * If a function is called with some set of wrong arguments, and then called + * again with the same set of arguments, the messages due to the second call + * will be supressed. If two tests test on the same wrong arguments, the + * second test won't see the validationError. clearing argumentTree solves it + * + * @method _clearValidateParamsCache + * @private + */ p5._clearValidateParamsCache = function clearValidateParamsCache() { for (let key of Object.keys(argumentTree)) { delete argumentTree[key]; @@ -624,17 +680,19 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * Validates parameters - * param {String} func the name of the function - * param {Array} args user input arguments + * Validates parameters and generates corresponding error messages + * @method _validateParameters + * @private + * @param {String} func the name of the function + * @param {Array} args user input arguments * - * example: + * @example: * const a; * ellipse(10,10,a,5); * console ouput: * "It looks like ellipse received an empty variable in spot #2." * - * example: + * @example: * ellipse(10,"foo",5,5); * console output: * "ellipse was expecting a number for parameter #1, From 5b795898f663c03d079b19e886f810c67ce4decc Mon Sep 17 00:00:00 2001 From: A M Chung Date: Sat, 23 Oct 2021 18:28:36 -0700 Subject: [PATCH 05/18] after lint --- src/core/friendly_errors/fes_core.js | 865 +++++++++++----------- src/core/friendly_errors/file_errors.js | 25 +- src/core/friendly_errors/sketch_reader.js | 19 +- 3 files changed, 452 insertions(+), 457 deletions(-) diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index ea938e2e37..a89851fd5b 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -98,17 +98,30 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * This is a generic method that can be called from anywhere in the p5 - * library to alert users to a common error. + * 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 _friendlyError + * @method mapToReference * @private - * @param {Number} message message to be printed - * @param {String} [method] name of method - * @param {Number|String} [color] CSS color string or error type + * @param {String} message the words to be said + * @param {String} [func] the name of the function to link + * + * @returns {String} */ - p5._friendlyError = function(message, method, color) { - p5._report(message, method, color); + 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('/'); + msgWithReference = `${message} (http://p5js.org/reference/#/${referenceSection}/${funcName})`; + } + return msgWithReference; }; /** @@ -150,32 +163,18 @@ if (typeof IS_MINIFIED !== 'undefined') { log(prefixedMsg); } }; - /** - * 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 + * This is a generic method that can be called from anywhere in the p5 + * library to alert users to a common error. * - * @method mapToReference + * @method _friendlyError * @private - * @param {String} message the words to be said - * @param {String} [func] the name of the function to link - * - * @returns {String} + * @param {Number} message message to be printed + * @param {String} [method] name of method + * @param {Number|String} [color] CSS color string or error type */ - 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('/'); - msgWithReference = `${message} (http://p5js.org/reference/#/${referenceSection}/${funcName})`; - } - return msgWithReference; + p5._friendlyError = function(message, method, color) { + p5._report(message, method, color); }; /** @@ -193,363 +192,74 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * The main function for handling global errors. + * Measures dissimilarity between two strings by calculating + * the Levenshtein distance. * - * Called when an error happens. It detects the type of error - * and generate an appropriate error message. + * If the "distance" between them is small enough, it is + * reasonable to think that one is the misspelled version of the other. * - * @method fesErrorMonitor + * Specifically, this uses the Wagner–Fischer algorithm. + * @method computeEditDistance * @private - * @param {*} e The object to extract error details from + * @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 fesErrorMonitor = e => { - if (p5.disableFriendlyErrors) 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; + const computeEditDistance = (w1, w2) => { + const l1 = w1.length, + l2 = w2.length; + if (l1 === 0) return w2; + if (l2 === 0) return w1; - let stacktrace = p5._getErrorStackParser().parse(error); - // process the stacktrace from the browser and simplify it to give - // friendlyStack. - let [isInternal, friendlyStack] = processStack(error, stacktrace); + let prev = []; + let cur = []; - // 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; + for (let j = 0; j < l2 + 1; j++) { + cur[j] = j; } - const errList = errorTable[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); + prev = cur; - if (matched) { - matchedError = Object.assign({}, obj); - matchedError.match = matched; - break; + 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; } - 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 - }; - } + return cur[l2]; + }; - 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]; + /** + * Checks capitalization for user defined functions. + * + * @method checkForUserDefinedFunctions + * @private + * @param {*} context The current default context. This is set to window + * in "global mode" and to a p5 instance in "instance mode" + */ + const checkForUserDefinedFunctions = context => { + if (p5.disableFriendlyErrors) return; - if (errSym && handleMisspelling(errSym, error)) { - break; - } - - // if the flow gets this far, this is likely not a misspelling - // of a p5 property/function - let url1 = 'https://p5js.org/examples/data-variable-scope.html'; - let url2 = - 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong'; - p5._friendlyError( - translator('fes.globalErrors.reference.notDefined', { - url1, - url2, - 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 url1 = - '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', { - url1, - url2, - 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 url1 = - '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', { - url1, - url2, - 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 - } */ - - /** - * Checks capitalization for user defined functions. - * - * @method checkForUserDefinedFunctions - * @private - * @param {*} context The current default context. This is 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 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; const fxns = {}; // lowercasename -> actualName mapping @@ -578,60 +288,6 @@ if (typeof IS_MINIFIED !== 'undefined') { } }; - /** - * 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]; - }; - /** * Compares the symbol caught in the ReferenceErrror to everything in * misusedAtTopLevel ( all public p5 properties ). @@ -644,9 +300,6 @@ if (typeof IS_MINIFIED !== 'undefined') { * @returns {Boolean} tell whether error was likely due to typo */ const handleMisspelling = (errSym, error) => { - // The name misusedAtTopLevel can be confusing. - // However it is used here because it was an array that was - // already defined when spelling check was implemented. if (!misusedAtTopLevelCode) { defineMisusedAtTopLevelCode(); } @@ -907,12 +560,356 @@ if (typeof IS_MINIFIED !== 'undefined') { } return [isInternal, friendlyStack]; }; -}; -// This is a lazily-defined list of p5 symbols used for detecting misusage -// at top-level code, outside of setup()/draw(). + /** + * The main function for handling global errors. + * + * Called when an error happens. It detects the type of error + * and generate an appropriate error message. + * + * @method fesErrorMonitor + * @private + * @param {*} e The object to extract error details from + */ + const fesErrorMonitor = e => { + if (p5.disableFriendlyErrors) 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 = errorTable[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 url1 = 'https://p5js.org/examples/data-variable-scope.html'; + let url2 = + 'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong'; + p5._friendlyError( + translator('fes.globalErrors.reference.notDefined', { + url1, + url2, + 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 url1 = + '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', { + url1, + url2, + 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 url1 = + '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', { + url1, + url2, + 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. // -// Resolving https://github.com/processing/p5.js/issues/1121. +// 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'; diff --git a/src/core/friendly_errors/file_errors.js b/src/core/friendly_errors/file_errors.js index 89ff6165c9..aca008b8ab 100644 --- a/src/core/friendly_errors/file_errors.js +++ b/src/core/friendly_errors/file_errors.js @@ -76,19 +76,18 @@ if (typeof IS_MINIFIED !== 'undefined') { }; } }; + /** + * Called internally if there is a error during file loading. + * + * @method _friendlyFileLoadError + * @private + * @param {Number} errorType + * @param {String} filePath + */ + p5._friendlyFileLoadError = function(errorType, filePath) { + const { message, method } = fileLoadErrorCases(errorType, filePath); + p5._friendlyError(message, method, 3); + }; } -/** - * Called internally if there is a error during file loading. - * - * @method _friendlyFileLoadError - * @private - * @param {Number} errorType - * @param {String} filePath - */ -p5._friendlyFileLoadError = function(errorType, filePath) { - const { message, method } = fileLoadErrorCases(errorType, filePath); - p5._friendlyError(message, method, 3); -}; - export default p5; diff --git a/src/core/friendly_errors/sketch_reader.js b/src/core/friendly_errors/sketch_reader.js index d0d6fbc809..be3a446c8f 100644 --- a/src/core/friendly_errors/sketch_reader.js +++ b/src/core/friendly_errors/sketch_reader.js @@ -3,25 +3,24 @@ * @requires core */ - /* p5._fesCodeReader() with the help of other helper functions - * performs the following tasks - * - * (I) Checks if any p5.js constant or function is declared by +import p5 from '../main'; +import { translator } from '../internationalization'; +import * as constants from '../constants'; + +/** + * Checks if any p5.js constant or function is declared by * the user outside setup and draw function and report it. * - * (II) In setup and draw function it performs: + * Also, in setup() and draw() function it performs: * 1. Extraction of the code written by the user * 2. Conversion of the code to an array of lines of code * 3. Catching variable and function decleration * 4. Checking if the declared function/variable is a reserved p5.js * constant or function and report it. * + * @method _fesCodeReader + * @private */ - -import p5 from '../main'; -import { translator } from '../internationalization'; -import * as constants from '../constants'; - if (typeof IS_MINIFIED !== 'undefined') { p5._fesCodeReader = () => {}; } else { From 557176198bae4bfbd71e88b63769373780468198 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Mon, 25 Oct 2021 13:36:23 -0700 Subject: [PATCH 06/18] new link --- contributor_docs/friendly_error_system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index 1ad2b56554..046e9cb356 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -6,7 +6,7 @@ The Friendly Error System (FES, 🌸) aims to help new programmers by providing The FES prints messages in the console window, as seen in the [p5.js Web Editor](https://github.com/processing/p5.js-web-editor) and your browser JavaScript console. The single minified file of p5 (p5.min.js) omits the FES. - *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸](https://forms.gle/2qStHJFSiw9XMCQJ8) + *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸](https://forms.gle/4cCGE1ecfoiaMGzt7) ## Writing Friendly Error Messages From ad411e1a4d3bf44f075b18354df4c73594de1f6d Mon Sep 17 00:00:00 2001 From: A M Chung Date: Mon, 25 Oct 2021 14:07:38 -0700 Subject: [PATCH 07/18] minor edits --- contributor_docs/friendly_error_system.md | 47 +++++++++++++++-------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index 046e9cb356..b2952292ea 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -4,31 +4,40 @@ The Friendly Error System (FES, 🌸) aims to help new programmers by providing error messages in simple, friendly language. It supplements browser console error messages by adding an alternative description of the error and links to helpful references. -The FES prints messages in the console window, as seen in the [p5.js Web Editor](https://github.com/processing/p5.js-web-editor) and your browser JavaScript console. The single minified file of p5 (p5.min.js) omits the FES. +The FES prints messages in the console window, as seen in the [p5.js Web Editor] and your browser JavaScript console. The single minified file of p5 (p5.min.js) omits the FES. - *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸](https://forms.gle/4cCGE1ecfoiaMGzt7) + *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸] +[p5.js Web Editor]: (https://github.com/processing/p5.js-web-editor) +[🌸 SURVEY 🌸]: (https://forms.gle/4cCGE1ecfoiaMGzt7) ## Writing Friendly Error Messages In this section, we will talk about how to contribute by writing and translating error messages. -The FES is a part of the p5.js' [internationalization](https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) effort. We generate all FES messages' content through [i18next](https://www.i18next.com/)-based `translator()` function. This dynamic error message generation happens for all languages, including English - the default language of p5. +The FES is a part of the p5.js' [internationalization] effort. We generate all FES messages' content through [i18next]-based `translator()` function. This dynamic error message generation happens for all languages, including English - the default language of p5. We welcome contributions from all over the world! 🌐 +[internationalization]: (https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) +[i18next]: (https://www.i18next.com/) + #### Writing Best Practice Writers writing FES messages should prioritize lowering the barrier to understanding error messages and debugging. Here are some highlights from our upcoming best practice doc: -* Use simple sentences. Consider breaking your sentence into smaller blocks for best utilizing i18next's [interpolation](https://www.i18next.com/translation-function/interpolation) feature. -* Keep the language friendly and inclusive. Look for possible bias and harm in your language. Adhere to [p5.js Code of Conduct](https://github.com/processing/p5.js/blob/main/CODE_OF_CONDUCT.md#p5js-code-of-conduct). +* Use simple sentences. Consider breaking your sentence into smaller blocks for best utilizing i18next's [interpolation] feature. +* Keep the language friendly and inclusive. Look for possible bias and harm in your language. Adhere to [p5.js Code of Conduct]. * Avoid using figures of speech. Prioritize cross-cultural communication. -* Try to spot possible "[expert blind spots](https://tilt.colostate.edu/TipsAndGuides/Tip/181)" in an error message and its related docs. +* Try to spot possible "[expert blind spots]" in an error message and its related docs. * Introduce one technical concept or term at a time—link one external resource written in a beginner-friendly language with plenty of short, practical examples. +[interpolation]: (https://www.i18next.com/translation-function/interpolation) +[p5.js Code of Conduct]: (https://github.com/processing/p5.js/blob/main/CODE_OF_CONDUCT.md#p5js-code-of-conduct) +[expert blind spots]: (https://tilt.colostate.edu/TipsAndGuides/Tip/181) + #### Translation File Location `translator()` is based on i18next and imported from `src/core/internationalization.js`. It generates messages by looking up text data from a JSON translation file: @@ -78,11 +87,14 @@ translator('fes.fileLoadError.image', { suggestion }); #### How to Add or Modify Translation -The [internationalization doc](https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) has a step-by-step guide on adding and modifying translation files. +The [internationalization doc] has a step-by-step guide on adding and modifying translation files. +[internationalization doc]: (https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) ## Understanding How FES Works -In this section, we will give an overview of how FES generates and displays messages. For more detailed information on the FES functions, please see our [FES Reference + Dev Notes](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md). +In this section, we will give an overview of how FES generates and displays messages. For more detailed information on the FES functions, please see our [FES Reference + Dev Notes]. + +[FES Reference + Dev Notes]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md) #### Overview p5.js calls the FES from multiple locations for different situations, when: @@ -98,12 +110,15 @@ You can find the translation files used by the `translator()` inside: #### FES Message Generators These functions are responsible for catching errors and generating FES messages: -* [`_friendlyFileLoadError()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfile_errorsfriendlyfileloaderror) catches file loading errors. -* [`_validateParameters()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsvalidate_paramsvalidateparameters) checks a p5.js function’s input parameters based on inline documentations. -* [`_fesErrorMontitor()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfes_corefeserrormonitor) handles global errors. -* `helpForMisusedAtTopLevelCode()` is called on window load to check for use of p5.js functions outside of setup() or draw(). -* [`fesCodeReader()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfes_coresketch_readerfescodereader) checks if a p5.js reserved constant or function is redefined by the user. -* [`checkForUserDefinedFunctions()`](https://github.com/almchung/p5jsdocs/blob/main/fes_reference_dev_notes.md#corefriendly_errorsfes_corecheckforuserdefinedfunctions) checks if any user defined function has been used with a capitalization mistake. +* [`_friendlyFileLoadError()`] catches file loading errors. +* [`_validateParameters()`] checks a p5.js function’s input parameters based on inline documentations. +* [`_fesErrorMontitor()`] handles global errors. + +[`_friendlyFileLoadError()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfile_errorsfriendlyfileloaderror) +[`_validateParameters()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsvalidate_paramsvalidateparameters) +[`_fesErrorMontitor()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfes_corefeserrormonitor) +[`fesCodeReader()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfes_coresketch_readerfescodereader) +[`checkForUserDefinedFunctions()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfes_corecheckforuserdefinedfunctions) #### FES Message Displayer `fes_core.js/_friendlyError()` prints generated friendly error messages in the console. For example: @@ -116,7 +131,7 @@ p5._friendlyError( This function can be called anywhere in p5. ## Turning Off the FES -There may be cases where you want to [disable the FES for performance](https://github.com/processing/p5.js/wiki/Optimizing-p5.js-Code-for-Performance#disable-the-friendly-error-system-fes). +There may be cases where you want to [disable the FES for performance]. `p5.disableFriendlyErrors` allows you to turn off the FES when set to `true`. @@ -130,3 +145,5 @@ function setup() { ``` The single minified file of p5 (p5.min.js) automatically omits the FES. + +[disable the FES for performance]: (https://github.com/processing/p5.js/wiki/Optimizing-p5.js-Code-for-Performance#disable-the-friendly-error-system-fes) From 3d242200b8c943513aca3aa8ad46b6c46b0500de Mon Sep 17 00:00:00 2001 From: A M Chung Date: Mon, 1 Nov 2021 14:55:18 -0700 Subject: [PATCH 08/18] fes ref + dev doc + inline doc updates --- contributor_docs/fes_reference_dev_notes.md | 392 +++++++++++++------- contributor_docs/friendly_error_system.md | 15 +- src/core/friendly_errors/fes_core.js | 19 +- src/core/friendly_errors/file_errors.js | 2 +- src/core/friendly_errors/sketch_reader.js | 12 +- src/core/friendly_errors/validate_params.js | 4 +- src/core/main.js | 11 +- 7 files changed, 293 insertions(+), 162 deletions(-) diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md index 868a3fc464..06e0ada590 100644 --- a/contributor_docs/fes_reference_dev_notes.md +++ b/contributor_docs/fes_reference_dev_notes.md @@ -1,11 +1,63 @@ -# FES Reference and Development Notes -This document contains development notes for p5.js's Friendly Error System (FES). - -### `core/friendly_errors/file_errors/friendlyFileLoadError()`: -* This function generates and displays friendly error messages if a file fails to load correctly. It also checks if the size of a file might be too large to load and produces a warning. -* This can be called through : `p5._friendlyFileLoadError(ERROR_TYPE, FILE_PATH)`. -* file loading error example: -````javascript +# FES Reference and Notes from Developers +This document contains reference and development notes for the p5.js Friendly Error System (FES). + +## FES Functions: Reference + +### _report() +##### Description +`_report()` is the primary function that prints directly to the console with the output of the error helper message. +If `_fesLogger` is set ( i.e., we are running tests ), _report will call _fesLogger instead of console.log. +##### Syntax +````JavaScript +_report(message) +```` +````JavaScript +_report(message, func) +```` +````JavaScript +_report(message, func, color) +```` +##### Parameters +``` +@param {String} message message to be printed +@param {String} [func] name of function calling +@param {Number|String} [color] CSS color string or error type +``` +##### Location +core/friendly_errors/fes_core.js + + +### _friendlyError() +##### Description +`_friendlyError()` creates and prints a friendly error message. Any p5 function can call this function to offer a friendly error message. +The call sequence to `_friendlyError` looks something like this: +``` +_friendlyError + _report +``` +##### Syntax +````JavaScript +_friendlyError(message) +```` +````JavaScript +_friendlyError(message, func) +```` +````JavaScript +_friendlyError(message, func, color) +```` +##### Parameters +``` +@param {String} message the words to be said +@param {String} [func] the name of the function to link +@param {Number|String} [color] CSS color string or error type +``` +##### Location +core/friendly_errors/fes_core.js + +### _friendlyFileLoadError() +##### Examples +File loading error example: +````JavaScript /// missing font file let myFont; function preload() { @@ -18,152 +70,224 @@ function setup() { text('p5*js', 10, 50); }; function draw() {}; -/// FES will generate the following message in the console: -/// > p5.js says: It looks like there was a problem loading your font. Try checking if the file path [assets/OpenSans-Regular.ttf] is correct, hosting the font online, or running a local server.[https://github.com/processing/p5.js/wiki/Local-server] +// FES will generate the following message in the console: +// > 🌸 p5.js says: It looks like there was a problem loading your font. Try checking if the file path [assets/OpenSans-Regular.ttf] is correct, hosting the font online, or running a local server.[https://github.com/processing/p5.js/wiki/Local-server] +```` +##### Description +`_friendlyFileLoadError()` is called by the `loadX()` functions if there is an error during file loading. + +This function generates and displays friendly error messages if a file fails to load correctly. It also checks if the size of a file might be too large to load and produces a warning. + +Currently version contains templates for generating error messages for `image`, `XML`, `table`, `text`, `json` and `font` files. +Implemented to: +* `image/loading_displaying/loadImage()` +* `io/files/loadFont()` +* `io/files/loadTable()` +* `io/files/loadJSON()` +* `io/files/loadStrings()` +* `io/files/loadXML()` +* `io/files/loadBytes()`. + +The call sequence to `_friendlyFileLoadError` looks something like this: +``` +_friendlyFileLoadError + _report +``` +##### Syntax +````JavaScript +_friendlyFileLoadError(errorType, filePath) ```` -* Currently version contains templates for generating error messages for `image`, `XML`, `table`, `text`, `json` and `font` files. -* Implemented to `image/loading_displaying/loadImage()`, `io/files/loadFont()`, `io/files/loadTable()`, `io/files/loadJSON()`, `io/files/loadStrings()`, `io/files/loadXML()`, `io/files/loadBytes()`. -* Error while loading a file due to its large size is implemented to all loadX methods. +##### Parameters +``` +@param {Number} errorType +@param {String} filePath +``` +##### Location +core/friendly_errors/file_errors.js -### `core/friendly_errors/validate_params/validateParameters()`: -* This function runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct type of parameters. -* missing parameter example: -````javascript +### validateParameters() +##### Examples +Missing parameter example: +````JavaScript arc(1, 1, 10.5, 10); -/// FES will generate the following message in the console: -/// > p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] -/// > p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] - +// FES will generate the following message in the console: +// > 🌸 p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] +// > 🌸 p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] ```` -* wrong type example: -````javascript +Wrong type example: +````JavaScript arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); -/// FES will generate the following message in the console: -/// > p5.js says: arc() was expecting Number for parameter #0 (zero-based index), received string instead. [http://p5js.org/reference/#p5/arc] +// FES will generate the following message in the console: +// > 🌸 p5.js says: arc() was expecting Number for parameter #0 (zero-based index), received string instead. [http://p5js.org/reference/#p5/arc] ```` -* This can be called through: `p5._validateParameters(FUNCT_NAME, ARGUMENTS)` -or, `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` inside the function that requires parameter validation. It is recommended to use static version, `p5._validateParameters` for general purposes. `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` mainly remained for debugging and unit testing purposes. -* Implemented to functions in `color/creating_reading`, `core/2d_primitives`, `core/curves`, and `utilities/string_functions`. +##### Description +`validateParameters()` runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct type of parameters. -### `core/friendly_errors/fes_core/fesErrorMonitor()`: -* This function is triggered whenever an error happens in the script. It attempts to help the user by providing more details, -likely causes and ways to address it. +This function can be called through: `p5._validateParameters(FUNCT_NAME, ARGUMENTS)` or, `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` inside the function that requires parameter validation. It is recommended to use static version, `p5._validateParameters` for general purposes. `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` mainly remained for debugging and unit testing purposes. -* Internal Error Example 1 -```js +Implemented to functions in: +* `color/creating_reading` +* `core/2d_primitives` +* `core/curves` +* `utilities/string_functions` + +##### Syntax +````JavaScript +_validateParameters(func, args) +```` +##### Parameters +``` +@param {String} func the name of the function +@param {Array} args user input arguments +``` +##### Location +core/friendly_errors/validate_params.js + +### fesErrorMonitor() +##### Examples +Internal Error Example 1 +````JavaScript function preload() { // error in background() due to it being called in // preload background(200); } - -/* -FES will show: -p5.js says: An error with message "Cannot read property 'background' of undefined" occured inside the p5js library when "background" was called (on line 4 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:4:3]). - -If not stated otherwise, it might be due to "background" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function. (http://p5js.org/reference/#/p5/preload) -*/ -``` - -* Internal Error Example 2 -```js +// FES will show: +// > 🌸 p5.js says: An error with message "Cannot read property 'background' of undefined" occured inside the p5js library when "background" was called (on line 4 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:4:3]). +//If not stated otherwise, it might be due to "background" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function. (http://p5js.org/reference/#/p5/preload) +```` +Internal Error Example 2 +````JavaScript function setup() { cnv = createCanvas(200, 200); cnv.mouseClicked(); } - -/* - -p5.js says: An error with message "Cannot read property 'bind' of undefined" occured inside the p5js library when mouseClicked was called (on line 3 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:3:7]) - -If not stated otherwise, it might be an issue with the arguments passed to mouseClicked. (http://p5js.org/reference/#/p5/mouseClicked) -*/ -``` - -* Error in user's sketch example (scope) -```js +// > 🌸 p5.js says: An error with message "Cannot read property 'bind' of undefined" occured inside the p5js library when mouseClicked was called (on line 3 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:3:7]) If not stated otherwise, it might be an issue with the arguments passed to mouseClicked. (http://p5js.org/reference/#/p5/mouseClicked) +```` +Error in user's sketch example (scope) +````JavaScript function setup() { let b = 1; } function draw() { b += 1; } -/* -FES will show: -p5.js says: There's an error due to "b" not being defined in the current scope (on line 5 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:5:3]). - -If you have defined it in your code, you should check its scope, spelling, and letter-casing (JavaScript is case-sensitive). For more: -https://p5js.org/examples/data-variable-scope.html -https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong -*/ -``` - -* Error in user's sketch example (spelling) -```js +// FES will show: +// > 🌸 p5.js says: There's an error due to "b" not being defined in the current scope (on line 5 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:5:3]). If you have defined it in your code, you should check its scope, spelling, and letter-casing (JavaScript is case-sensitive). For more: https://p5js.org/examples/data-variable-scope.html https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong +```` +Error in user's sketch example (spelling) +````JavaScript function setup() { colour(1, 2, 3); } -/* -FES will show: -p5.js says: It seems that you may have accidentally written "colour" instead of "color" (on line 2 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:2:3]). +// FES will show: +// > 🌸 p5.js says: It seems that you may have accidentally written "colour" instead of "color" (on line 2 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:2:3]). Please correct it to color if you wish to use the function from p5.js (http://p5js.org/reference/#/p5/color) +```` +##### Description +`fesErrorMonitor()` handles various errors that the browser show. The function generates global error messages. -Please correct it to color if you wish to use the function from p5.js (http://p5js.org/reference/#/p5/color) -*/ +`_fesErrorMonitor()` can be called either by an error event, an unhandled rejection event, or it can be manually called in a catch block as follows: +``` +try { someCode(); } catch(err) { p5._fesErrorMonitor(err); } ``` -### `core/friendly_errors/fes_core/sketch_reader/fesCodeReader()`: -* This function is executed everytime when the `load` event is triggered. It checks if a p5.js reserved constant or function is redefined by the user. +The function currently works with some kinds of ReferenceError, SyntaxError, and TypeError. You can find the complete list of supported errors in `browser_errors.js`. -* Redefining p5.js reserved constant -```js +The call sequence for `_fesErrorMonitor` roughly looks something like this: +``` + _fesErrorMonitor + processStack + printFriendlyError + (if type of error is ReferenceError) + _handleMisspelling + computeEditDistance + _report + _report + printFriendlyStack + (if type of error is SyntaxError, TypeError, etc) + _report + printFriendlyStack +``` +##### Syntax +````JavaScript +fesErrorMonitor(event) +```` +##### Parameters +``` +@param {*} e Event object to extract error details from +``` +##### Location +core/friendly_errors/fes_core.js + +### _fesCodeReader() +##### Examples +Redefining p5.js reserved constant +````JavaScript function setup() { //PI is a p5.js reserved constant let PI = 100; } - -/* -FES will show: -p5.js says: you have used a p5.js reserved variable "PI" make sure you change the variable name to something else. -(https://p5js.org/reference/#/p5/PI) -*/ -``` - -* Redefining p5.js reserved function -```js +// FES will show: +// > 🌸 p5.js says: you have used a p5.js reserved variable "PI" make sure you change the variable name to something else. (https://p5js.org/reference/#/p5/PI) +```` +Redefining p5.js reserved function +````JavaScript function setup() { //text is a p5.js reserved function let text = 100; } - -/* -FES will show: -p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. -*/ -``` - -### `core/friendly_errors/fes_core/checkForUserDefinedFunctions()`: -* Checks if any user defined function (`setup()`, `draw()`, `mouseMoved()`, etc.) has been used with a capitalization mistake -* For example -```js +// FES will show: +// > 🌸 p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. +```` +##### Description +`_fesCodeReader()` checks if (1) any p5.js constant or function is used outside of setup and draw function and (2) any p5.js reserved constant or function is redeclared. +In setup() and draw() function it performs: + * Extraction of the code written by the user + * Conversion of the code to an array of lines of code + * Catching variable and function declaration + * Checking if the declared function/variable is a reserved p5.js constant or function and report it. + +This function is executed whenever the `load` event is triggered. + +##### Location +core/friendly_errors/fes_core/sketch_reader.js + +### checkForUserDefinedFunctions() +##### Examples +````JavaScript function preLoad() { loadImage('myimage.png'); } -/* -FES will show: -p5.js says: It seems that you may have accidentally written preLoad instead of preload. - -Please correct it if it's not intentional. (http://p5js.org/reference/#/p5/preload) -*/ +// FES will show: +// > 🌸 p5.js says: It seems that you may have accidentally written preLoad instead of preload. Please correct it if it's not intentional. (http://p5js.org/reference/#/p5/preload) +```` +##### Description +Checks if any user defined function (`setup()`, `draw()`, `mouseMoved()`, etc.) has been used with a capitalization mistake. +##### Syntax +````JavaScript +checkForUserDefinedFunctions(context) +```` +##### Parameters ``` +@param {*} context Current default context. + Set to window in "global mode" and + to a p5 instance in "instance mode" +``` +##### Location +core/friendly_errors/fes_core.js + +## Development Notes: Notes from Developers +#### Misc. FES Functions that Generates Friendly Errors +* `helpForMisusedAtTopLevelCode()` is called by `fes_core.js` on window load to check for use of p5.js functions outside of `setup()` or `draw()`. -## Additional Features -* The FES welcomes the developer to p5 and the friendly debugger. -* The FES works in the IDE and the web editor. +* `friendlyWelcome()` prints to console directly. (Hidden by default.) -## Notes for Developers -* When creating p5.js Objects: any p5.js objects that will be used for parameters will need to assign value for `name` parameter (name of the object) within the class declaration. e.g.: -````javascript +* `stacktrace.js` contains the code to parse the error stack, borrowed from https://github.com/stacktracejs/stacktrace.js. This is called by `fesErrorMonitor()` and `handleMisspelling()` + +#### Preparing p5.js Objects for Parameter Validation +* Any p5.js objects that will be used for parameter validation will need to assign value for `name` parameter (name of the object) within the class declaration. e.g.: +```javascript p5.newObject = function(parameter) { this.parameter = 'some parameter'; this.name = 'p5.newObject'; @@ -172,13 +296,13 @@ p5.newObject = function(parameter) { * Inline documentation: allowed parameter types are `Boolean`, `Number`, `String`, and name of the object (see the above bullet point). Use `Array` for any types of Array parameters. If needed, explain what kind of the specific types of array parameter are allowed (e.g. `Number[]`, `String[]`) in the description section. * Currently supported class types (have their `name` parameter): `p5.Color`, `p5.Element`, `p5.Graphics`, `p5.Renderer`, `p5.Renderer2D`, `p5.Image`, `p5.Table`, `p5.TableRow`, `p5.XML`, `p5.Vector`, `p5.Font`, `p5.Geometry`, `p5.Matrix`, `p5.RendererGL`. -## Disable the FES +#### Performance Issue with the FES -By default, FES is enabled for p5.js, and disabled in p5.min.js to prevent FES functions slowing down the process. The error checking system can significantly slow down your code (up to ~10x in some cases). See the [friendly error performance test](https://github.com/processing/p5.js-website/tree/main/src/assets/learn/performance/code/friendly-error-system/). +By default, FES is enabled for p5.js, and disabled in `p5.min.js` to prevent FES functions slowing down the process. The error checking system can significantly slow down your code (up to ~10x in some cases). See the [friendly error performance test](https://github.com/processing/p5.js-website/tree/main/src/assets/learn/performance/code/friendly-error-system/). You can disable this with one line of code at the top of your sketch: -```javascript +```JavaScript p5.disableFriendlyErrors = true; // disables FES function setup() { @@ -192,49 +316,51 @@ function draw() { Note that this will disable the parts of the FES that cause performance slowdown (like argument checking). Friendly errors that have no performance cost (like giving an descriptive error if a file load fails, or warning you if you try to override p5.js functions in the global space), will remain in place. + ## Known Limitations -* The friendly error system slows the program down, so there is an option to turn it off via setting `p5.disableFriendlyErrors = true;`. In addition, the friendly error system is omitted by default in the minified (`p5.min.js`) version. + * FES may still result in false negative cases. These are usually caused by the mismatch between designs of the functions (e.g. drawing functions those are originally designed to be used interchangeably in both 2D and 3D settings) with actual usage cases. For example, drawing a 3D line with -```javascript +```JavaScript const x3; // undefined line(0, 0, 100, 100, x3, Math.PI); ``` - will escape FES, because there is an acceptable parameter pattern (`Number`, `Number`, `Number`, `Number`) in `line()`'s inline documentation for drawing in 2D setting. This also means the current version of FES doesn't check for the environmental variables such as `_renderer.isP3D`. - * FES is only able to detect global variables overwritten when declared using `const` or `var`. If `let` is used, they go undetected. This is not currently solvable due to the way `let` instantiates variables. +will escape FES, because there is an acceptable parameter pattern (`Number`, `Number`, `Number`, `Number`) in `line()`'s inline documentation for drawing in 2D setting. This also means the current version of FES doesn't check for the environmental variables such as `_renderer.isP3D`. - * The functionality described under **`fesErrorMonitor()`** currently only works on the web editor or if running on a local server. For more details see [this](https://github.com/processing/p5.js/pull/4730). +* FES is only able to detect global variables overwritten when declared using `const` or `var`. If `let` is used, they go undetected. This is not currently solvable due to the way `let` instantiates variables. - * The extracting variable/function names feature of FES's `sketch_reader` is not perfect and some cases might go undetected (for eg: when all the code is written in a single line). +* The functionality described under **`fesErrorMonitor()`** currently only works on the web editor or if running on a local server. For more details see [this](https://github.com/processing/p5.js/pull/4730). -## In The Works -* Identify more common error types and generalize with FES (e.g. `bezierVertex()`, `quadraticVertex()` - required object not initiated; checking Number parameters positive for `nf()` `nfc()` `nfp()` `nfs()`) +* The extracting variable/function names feature of FES's `sketch_reader` is not perfect and some cases might go undetected (for eg: when all the code is written in a single line). ## Thoughts for the Future * re-introduce color coding for the Web Editor. + * More unit testings. + * More intuitive and narrowed down output messages. -* Completing the spanish translation for `validateParameters()` as well. + * All the colors are checked for being color blind friendly. -* More elaborate ascii is always welcome! + +* Identify more common error types and generalize with FES (e.g. `bezierVertex()`, `quadraticVertex()` - required object not initiated; checking Number parameters positive for `nf()` `nfc()` `nfp()` `nfs()`) + * Extend Global Error catching. This means catching errors that the browser is throwing to the console and matching them with friendly messages. `fesErrorMonitor()` does this for a select few kinds of errors but help in supporting more is welcome :) -* Improve `sketch_reader.js`'s code reading and variable/function name extracting functionality (which extracts names of the function and variables declared by the user in their code). For example currently `sketch_reader.js` is not able to extract variable/function names properly if all the code is written in a single line. -* `sketch_reader.js` can be extended and new features (for example: Alerting the user when they have declared a variable in the `draw()` function) can be added to it to better aid the user. +* Improve `sketch_reader.js`'s code reading and variable/function name extracting functionality (which extracts names of the function and variables declared by the user in their code). For example currently `sketch_reader.js` is not able to extract variable/function names properly if all the code is written in a single line. -```javascript +* `sketch_reader.js` can be extended and new features (for example: Alerting the user when they have declared a variable in the `draw()` function) can be added to it to better aid the user. +```JavaScript // this snippet wraps window.console methods with a new function to modify their functionality // it is not currently implemented, but could be to give nicer formatted error messages const original = window.console; const original_functions = { - log: original.log, - warn: original.warn, - error: original.error + log: original.log, + warn: original.warn, + error: original.error } - ["log", "warn", "error"].forEach(function(func){ -window.console[func] = function(msg) { -//do something with the msg caught by the wrapper function, then call the original function -original_functions[func].apply(original, arguments) -}; + window.console[func] = function(msg) { + //do something with the msg caught by the wrapper function, then call the original function + original_functions[func].apply(original, arguments) + }; }); ``` diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index b2952292ea..4a825d9ec6 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -94,7 +94,7 @@ The [internationalization doc] has a step-by-step guide on adding and modifying ## Understanding How FES Works In this section, we will give an overview of how FES generates and displays messages. For more detailed information on the FES functions, please see our [FES Reference + Dev Notes]. -[FES Reference + Dev Notes]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md) +[FES Reference + Dev Notes]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md) #### Overview p5.js calls the FES from multiple locations for different situations, when: @@ -109,16 +109,17 @@ You can find the translation files used by the `translator()` inside: `translations/`. #### FES Message Generators -These functions are responsible for catching errors and generating FES messages: +These functions are mainly responsible for catching errors and generating FES messages: * [`_friendlyFileLoadError()`] catches file loading errors. * [`_validateParameters()`] checks a p5.js function’s input parameters based on inline documentations. * [`_fesErrorMontitor()`] handles global errors. -[`_friendlyFileLoadError()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfile_errorsfriendlyfileloaderror) -[`_validateParameters()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsvalidate_paramsvalidateparameters) -[`_fesErrorMontitor()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfes_corefeserrormonitor) -[`fesCodeReader()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfes_coresketch_readerfescodereader) -[`checkForUserDefinedFunctions()`]: (https://github.com/almchung/p5.js/blob/gsdocs-fes/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfes_corecheckforuserdefinedfunctions) +For full reference, please see our [Dev Notes]. + +[`_friendlyFileLoadError()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfile_errorsfriendlyfileloaderror) +[`_validateParameters()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsvalidate_paramsvalidateparameters) +[`_fesErrorMontitor()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfes_corefeserrormonitor) +[Dev Notes]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md) #### FES Message Displayer `fes_core.js/_friendlyError()` prints generated friendly error messages in the console. For example: diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index a89851fd5b..968cc5dd4e 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -169,12 +169,12 @@ if (typeof IS_MINIFIED !== 'undefined') { * * @method _friendlyError * @private - * @param {Number} message message to be printed - * @param {String} [method] name of method - * @param {Number|String} [color] CSS color string or error type + * @param {String} message message to be printed + * @param {String} [func] name of function calling + * @param {Number|String} [color] CSS color string or error type */ - p5._friendlyError = function(message, method, color) { - p5._report(message, method, color); + p5._friendlyError = function(message, func, color) { + p5._report(message, func, color); }; /** @@ -249,8 +249,8 @@ if (typeof IS_MINIFIED !== 'undefined') { * * @method checkForUserDefinedFunctions * @private - * @param {*} context The current default context. This is set to window - * in "global mode" and to a p5 instance in "instance mode" + * @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; @@ -562,14 +562,15 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * The main function for handling global errors. + * Handles various errors that the browser show. + * This is the main function for handling global errors. * * Called when an error happens. It detects the type of error * and generate an appropriate error message. * * @method fesErrorMonitor * @private - * @param {*} e The object to extract error details from + * @param {*} e Event object to extract error details from */ const fesErrorMonitor = e => { if (p5.disableFriendlyErrors) return; diff --git a/src/core/friendly_errors/file_errors.js b/src/core/friendly_errors/file_errors.js index aca008b8ab..4f4075abf1 100644 --- a/src/core/friendly_errors/file_errors.js +++ b/src/core/friendly_errors/file_errors.js @@ -77,7 +77,7 @@ if (typeof IS_MINIFIED !== 'undefined') { } }; /** - * Called internally if there is a error during file loading. + * Called internally if there is an error during file loading. * * @method _friendlyFileLoadError * @private diff --git a/src/core/friendly_errors/sketch_reader.js b/src/core/friendly_errors/sketch_reader.js index be3a446c8f..20e9ee1c33 100644 --- a/src/core/friendly_errors/sketch_reader.js +++ b/src/core/friendly_errors/sketch_reader.js @@ -8,15 +8,9 @@ import { translator } from '../internationalization'; import * as constants from '../constants'; /** - * Checks if any p5.js constant or function is declared by - * the user outside setup and draw function and report it. - * - * Also, in setup() and draw() function it performs: - * 1. Extraction of the code written by the user - * 2. Conversion of the code to an array of lines of code - * 3. Catching variable and function decleration - * 4. Checking if the declared function/variable is a reserved p5.js - * constant or function and report it. + * Checks if any p5.js constant/function is declared outside of setup + * and draw function. Also checks any reserved constant/function is + * redeclared. * * @method _fesCodeReader * @private diff --git a/src/core/friendly_errors/validate_params.js b/src/core/friendly_errors/validate_params.js index 5f7d8e083b..bec5cde80f 100644 --- a/src/core/friendly_errors/validate_params.js +++ b/src/core/friendly_errors/validate_params.js @@ -680,7 +680,9 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * Validates parameters and generates corresponding error messages + * Runs parameter validation by matching the input parameters with information + * from `docs/reference/data.json` + * * @method _validateParameters * @private * @param {String} func the name of the function diff --git a/src/core/main.js b/src/core/main.js index 7f84adf884..739566bd8f 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -691,8 +691,15 @@ class p5 { p5.instance = null; /** - * Allows for the friendly error system (FES) to be turned off when creating a sketch, - * which can give a significant boost to performance when needed. + * Turn off some features of the friendly error system (FES), which can give + * a significant boost to performance when needed. + * + * Note that this will disable the parts of the FES that cause performance + * slowdown (like argument checking). Friendly errors that have no performance + * cost (like giving an descriptive error if a file load fails, or warning you + * if you try to override p5.js functions in the global space), + * will remain in place. + * * See * disabling the friendly error system. * From 150657495d1313ad7fb41cfe8698f7449534591a Mon Sep 17 00:00:00 2001 From: A M Chung Date: Mon, 1 Nov 2021 15:05:09 -0700 Subject: [PATCH 09/18] fes ref + dev doc + inline doc updates --- contributor_docs/fes_reference_dev_notes.md | 14 ++++++------- contributor_docs/friendly_error_system.md | 22 ++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md index 06e0ada590..b0d2a4dbe5 100644 --- a/contributor_docs/fes_reference_dev_notes.md +++ b/contributor_docs/fes_reference_dev_notes.md @@ -3,7 +3,7 @@ This document contains reference and development notes for the p5.js Friendly Er ## FES Functions: Reference -### _report() +### `_report()` ##### Description `_report()` is the primary function that prints directly to the console with the output of the error helper message. If `_fesLogger` is set ( i.e., we are running tests ), _report will call _fesLogger instead of console.log. @@ -27,7 +27,7 @@ _report(message, func, color) core/friendly_errors/fes_core.js -### _friendlyError() +### `_friendlyError()` ##### Description `_friendlyError()` creates and prints a friendly error message. Any p5 function can call this function to offer a friendly error message. The call sequence to `_friendlyError` looks something like this: @@ -54,7 +54,7 @@ _friendlyError(message, func, color) ##### Location core/friendly_errors/fes_core.js -### _friendlyFileLoadError() +### `_friendlyFileLoadError()` ##### Examples File loading error example: ````JavaScript @@ -105,7 +105,7 @@ _friendlyFileLoadError(errorType, filePath) ##### Location core/friendly_errors/file_errors.js -### validateParameters() +### `validateParameters()` ##### Examples Missing parameter example: ````JavaScript @@ -143,7 +143,7 @@ _validateParameters(func, args) ##### Location core/friendly_errors/validate_params.js -### fesErrorMonitor() +### `fesErrorMonitor()` ##### Examples Internal Error Example 1 ````JavaScript @@ -219,7 +219,7 @@ fesErrorMonitor(event) ##### Location core/friendly_errors/fes_core.js -### _fesCodeReader() +### `_fesCodeReader()` ##### Examples Redefining p5.js reserved constant ````JavaScript @@ -252,7 +252,7 @@ This function is executed whenever the `load` event is triggered. ##### Location core/friendly_errors/fes_core/sketch_reader.js -### checkForUserDefinedFunctions() +### `checkForUserDefinedFunctions()` ##### Examples ````JavaScript function preLoad() { diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index 4a825d9ec6..5a5011b0ff 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -54,21 +54,21 @@ The language designator can also include regional information, such as `es-PE` ( `translation.json` has a [format used by i18next](https://www.i18next.com/misc/json-format). The basic format of a translation file's item has a key and a value (message) in double quotation marks `""`, closed by the curly brackets `{}`: -``` +```json { "key": "value" } ``` For example, we have a ASCII logo saved in this format: -``` +```json "logo": " _ \n /\\| |/\\ \n \\ ` ' / \n / , . \\ \n \\/|_|\\/ \n\n" ``` i18next supports interpolation, which allows us to pass a variable to generate a message dynamically. We use curly brackets twice `{{}}` to set a placeholder of the variable: -``` +```json "greeting": "Hello, {{who}}!" ``` Here, the key is `greeting`, and the variable name is `who`. To dynamically generate this message, we will need to pass a value: -``` +```JavaScript translator('greeting', { who: 'everyone' } ); ``` The result generated by `translator` will look like this: @@ -77,11 +77,11 @@ Hello, everyone! ``` Here is an item from `fes`'s `fileLoadError` that demonstrates interpolation: -``` +```json "image": "It looks like there was a problem loading your image. {{suggestion}}" ``` To dynamically generate the final message, the FES will call `translator()` with the key and a pre-generated `suggestion` value. -``` +```JavaScript translator('fes.fileLoadError.image', { suggestion }); ``` @@ -116,15 +116,15 @@ These functions are mainly responsible for catching errors and generating FES me For full reference, please see our [Dev Notes]. -[`_friendlyFileLoadError()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfile_errorsfriendlyfileloaderror) -[`_validateParameters()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsvalidate_paramsvalidateparameters) -[`_fesErrorMontitor()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#corefriendly_errorsfes_corefeserrormonitor) +[`_friendlyFileLoadError()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#_friendlyfileloaderror) +[`_validateParameters()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#validateparameters) +[`_fesErrorMontitor()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#feserrormonitor) [Dev Notes]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md) #### FES Message Displayer `fes_core.js/_friendlyError()` prints generated friendly error messages in the console. For example: -``` +```JavaScript p5._friendlyError( translator('fes.globalErrors.type.notfunc', translationObj) ); @@ -137,7 +137,7 @@ There may be cases where you want to [disable the FES for performance]. `p5.disableFriendlyErrors` allows you to turn off the FES when set to `true`. Example: -``` +```JavaScript p5.disableFriendlyErrors = true; function setup() { From 04e45f0368b71b7093b1bf77898dc3815eff008b Mon Sep 17 00:00:00 2001 From: A M Chung Date: Mon, 1 Nov 2021 16:43:49 -0700 Subject: [PATCH 10/18] revert back example sketch --- lib/empty-example/sketch.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index e895db2621..69b202ee71 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,6 +1,5 @@ function setup() { // put setup code here - rect(0,0,'rt'); } function draw() { From fca3fbcbb57d0d67f3834f0174f1b754448979f4 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Tue, 2 Nov 2021 16:34:43 -0700 Subject: [PATCH 11/18] edited for clarity --- contributor_docs/fes_reference_dev_notes.md | 94 ++++++++++++++++----- contributor_docs/friendly_error_system.md | 20 ++--- src/core/friendly_errors/browser_errors.js | 15 ++-- src/core/friendly_errors/fes_core.js | 51 ++++++++--- src/core/friendly_errors/sketch_reader.js | 8 +- translations/en/translation.json | 4 +- 6 files changed, 140 insertions(+), 52 deletions(-) diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md index b0d2a4dbe5..8e17ecc237 100644 --- a/contributor_docs/fes_reference_dev_notes.md +++ b/contributor_docs/fes_reference_dev_notes.md @@ -30,11 +30,25 @@ core/friendly_errors/fes_core.js ### `_friendlyError()` ##### Description `_friendlyError()` creates and prints a friendly error message. Any p5 function can call this function to offer a friendly error message. + +Implemented to functions in: +* `core/friendly_errors/fes_core/fesErrorMonitor()` +* `core/friendly_errors/fes_core/checkForUserDefinedFunctions()` +* `core/friendly_errors/fes_core/handleMisspelling()`` +* `core/friendly_errors/fes_core/processStack()` +* `core/friendly_errors/file_errors` +* `core/friendly_errors/sketch_reader` +* `core/friendly_errors/validate_params/_friendlyParamError()` +* `core/main/_createFriendlyGlobalFunctionBinder()` +* `core/shape/vertex` +* `math/p5.Vector` + The call sequence to `_friendlyError` looks something like this: ``` _friendlyError _report ``` + ##### Syntax ````JavaScript _friendlyError(message) @@ -56,7 +70,7 @@ core/friendly_errors/fes_core.js ### `_friendlyFileLoadError()` ##### Examples -File loading error example: +Example of file loading error: ````JavaScript /// missing font file let myFont; @@ -76,10 +90,11 @@ function draw() {}; ##### Description `_friendlyFileLoadError()` is called by the `loadX()` functions if there is an error during file loading. -This function generates and displays friendly error messages if a file fails to load correctly. It also checks if the size of a file might be too large to load and produces a warning. +This function generates and displays friendly error messages if a file fails to load correctly. It also checks and produces a warning if the size of a file is too large to load. + +The current version contains templates for generating error messages for `image`, `XML`, `table`, `text`, `json` and `font` files. -Currently version contains templates for generating error messages for `image`, `XML`, `table`, `text`, `json` and `font` files. -Implemented to: +Implemented to functions in: * `image/loading_displaying/loadImage()` * `io/files/loadFont()` * `io/files/loadTable()` @@ -107,30 +122,71 @@ core/friendly_errors/file_errors.js ### `validateParameters()` ##### Examples -Missing parameter example: +Example of a missing parameter: ````JavaScript arc(1, 1, 10.5, 10); // FES will generate the following message in the console: // > 🌸 p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] // > 🌸 p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] ```` -Wrong type example: +Example of a type mismatch: ````JavaScript arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); // FES will generate the following message in the console: // > 🌸 p5.js says: arc() was expecting Number for parameter #0 (zero-based index), received string instead. [http://p5js.org/reference/#p5/arc] ```` ##### Description -`validateParameters()` runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct type of parameters. +`validateParameters()` runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct types of parameters. This function can be called through: `p5._validateParameters(FUNCT_NAME, ARGUMENTS)` or, `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` inside the function that requires parameter validation. It is recommended to use static version, `p5._validateParameters` for general purposes. `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` mainly remained for debugging and unit testing purposes. Implemented to functions in: +* `accessibility/outputs` * `color/creating_reading` -* `core/2d_primitives` -* `core/curves` +* `color/setting` +* `core/environment` +* `core/rendering` +* `core/shape/2d_primitives` +* `core/shape/attributes` +* `core/shape/curves` +* `core/shape/vertex` +* `core/transform` +* `data/p5.TypedDict` +* `dom/dom` +* `events/acceleration` +* `events/keyboard` +* `image/image` +* `image/loading_displaying` +* `image/p5.Image` +* `image/pixel` +* `io/files` +* `math/calculation` +* `math/random` +* `typography/attributes` +* `typography/loading_displaying` * `utilities/string_functions` - +* `webgl/3d_primitives` +* `webgl/interaction` +* `webgl/light` +* `webgl/loading` +* `webgl/material` +* `webgl/p5.Camera` + +The call sequence from _validateParameters looks something like this: +``` +validateParameters + buildArgTypeCache + addType + lookupParamDoc + scoreOverload + testParamTypes + testParamType + getOverloadErrors + _friendlyParamError + ValidationError + report + friendlyWelcome +``` ##### Syntax ````JavaScript _validateParameters(func, args) @@ -184,7 +240,7 @@ function setup() { // > 🌸 p5.js says: It seems that you may have accidentally written "colour" instead of "color" (on line 2 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:2:3]). Please correct it to color if you wish to use the function from p5.js (http://p5js.org/reference/#/p5/color) ```` ##### Description -`fesErrorMonitor()` handles various errors that the browser show. The function generates global error messages. +`fesErrorMonitor()` handles various errors that the browser shows. The function generates global error messages. `_fesErrorMonitor()` can be called either by an error event, an unhandled rejection event, or it can be manually called in a catch block as follows: ``` @@ -219,7 +275,7 @@ fesErrorMonitor(event) ##### Location core/friendly_errors/fes_core.js -### `_fesCodeReader()` +### `fesCodeReader()` ##### Examples Redefining p5.js reserved constant ````JavaScript @@ -240,8 +296,8 @@ function setup() { // > 🌸 p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. ```` ##### Description -`_fesCodeReader()` checks if (1) any p5.js constant or function is used outside of setup and draw function and (2) any p5.js reserved constant or function is redeclared. -In setup() and draw() function it performs: +`fesCodeReader()` checks (1) if any p5.js constant or function is used outside of the setup() and/or draw() function and (2) if any p5.js reserved constant or function is redeclared. +In setup() and draw() functions it performs: * Extraction of the code written by the user * Conversion of the code to an array of lines of code * Catching variable and function declaration @@ -286,7 +342,7 @@ core/friendly_errors/fes_core.js * `stacktrace.js` contains the code to parse the error stack, borrowed from https://github.com/stacktracejs/stacktrace.js. This is called by `fesErrorMonitor()` and `handleMisspelling()` #### Preparing p5.js Objects for Parameter Validation -* Any p5.js objects that will be used for parameter validation will need to assign value for `name` parameter (name of the object) within the class declaration. e.g.: +* Any p5.js objects that will be used for parameter validation will need to assign a value for `name` parameter (name of the object) within the class declaration. e.g.: ```javascript p5.newObject = function(parameter) { this.parameter = 'some parameter'; @@ -332,14 +388,14 @@ will escape FES, because there is an acceptable parameter pattern (`Number`, `Nu * The extracting variable/function names feature of FES's `sketch_reader` is not perfect and some cases might go undetected (for eg: when all the code is written in a single line). -## Thoughts for the Future -* re-introduce color coding for the Web Editor. +## Thoughts for Future Work +* Re-introduce color coding for the Web Editor. -* More unit testings. +* Add more unit tests for comprehensive test coverage. * More intuitive and narrowed down output messages. -* All the colors are checked for being color blind friendly. +* All the colors being used should be color blind friendly. * Identify more common error types and generalize with FES (e.g. `bezierVertex()`, `quadraticVertex()` - required object not initiated; checking Number parameters positive for `nf()` `nfc()` `nfp()` `nfs()`) diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index 5a5011b0ff..e810060335 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -2,7 +2,7 @@ ## Overview -The Friendly Error System (FES, 🌸) aims to help new programmers by providing error messages in simple, friendly language. It supplements browser console error messages by adding an alternative description of the error and links to helpful references. +The Friendly Error System (FES, 🌸) aims to help new programmers by providing error messages in simple, friendly language. It supplements your browser's console error messages by adding an alternative description of the error and links to helpful references. The FES prints messages in the console window, as seen in the [p5.js Web Editor] and your browser JavaScript console. The single minified file of p5 (p5.min.js) omits the FES. @@ -13,20 +13,20 @@ The FES prints messages in the console window, as seen in the [p5.js Web Editor] ## Writing Friendly Error Messages -In this section, we will talk about how to contribute by writing and translating error messages. +In this section, we will describe how you can contribute to the p5.js library by writing and translating error messages. -The FES is a part of the p5.js' [internationalization] effort. We generate all FES messages' content through [i18next]-based `translator()` function. This dynamic error message generation happens for all languages, including English - the default language of p5. +The FES is a part of the p5.js' [internationalization] effort. We generate all FES messages' content through [i18next]-based `translator()` function. This dynamic error message generation happens for all languages, including English - the default language of the p5.js. -We welcome contributions from all over the world! 🌐 +We welcome contributions from all around the world! 🌐 [internationalization]: (https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) [i18next]: (https://www.i18next.com/) -#### Writing Best Practice +#### Writing Best Practices -Writers writing FES messages should prioritize lowering the barrier to understanding error messages and debugging. +FES message writers should prioritize lowering the barrier of understanding error messages and debugging. -Here are some highlights from our upcoming best practice doc: +Here are some highlights from our upcoming best-practice doc: * Use simple sentences. Consider breaking your sentence into smaller blocks for best utilizing i18next's [interpolation] feature. * Keep the language friendly and inclusive. Look for possible bias and harm in your language. Adhere to [p5.js Code of Conduct]. @@ -38,7 +38,7 @@ Here are some highlights from our upcoming best practice doc: [p5.js Code of Conduct]: (https://github.com/processing/p5.js/blob/main/CODE_OF_CONDUCT.md#p5js-code-of-conduct) [expert blind spots]: (https://tilt.colostate.edu/TipsAndGuides/Tip/181) -#### Translation File Location +#### Location of Translation Files `translator()` is based on i18next and imported from `src/core/internationalization.js`. It generates messages by looking up text data from a JSON translation file: ``` @@ -50,7 +50,7 @@ If the detected browser locale is Korean (language designator: `ko`), the `trans The language designator can also include regional information, such as `es-PE` (Spanish from Peru). -#### Translation File Structure +#### Structure of Translation Files `translation.json` has a [format used by i18next](https://www.i18next.com/misc/json-format). The basic format of a translation file's item has a key and a value (message) in double quotation marks `""`, closed by the curly brackets `{}`: @@ -145,6 +145,6 @@ function setup() { } ``` -The single minified file of p5 (p5.min.js) automatically omits the FES. +The single minified file of p5 (i.e., p5.min.js) automatically omits the FES. [disable the FES for performance]: (https://github.com/processing/p5.js/wiki/Optimizing-p5.js-Code-for-Performance#disable-the-friendly-error-system-fes) diff --git a/src/core/friendly_errors/browser_errors.js b/src/core/friendly_errors/browser_errors.js index 1ab7784e4e..74ac394866 100644 --- a/src/core/friendly_errors/browser_errors.js +++ b/src/core/friendly_errors/browser_errors.js @@ -1,9 +1,12 @@ -// Different browsers may use different error strings for the same error. -// Extracting info from them is much easier and cleaner if we have a predefined -// lookup against which we try and match the errors obtained from the browser, -// classify them into types and extract the required information. The contents -// of this file serve as that lookup. The FES can use this to give a simplified -// explanation for all kinds of errors. +// This contains a data table used by ./fes_core.js/fesErrorMonitor(). +// +// Note: Different browsers use different error strings for the same error. +// Extracting info from the browser error messages is easier and cleaner +// if we have a predefined lookup. This file serves as that lookup. +// Using this lookup we match the errors obtained from the browser, classify +// them into types and extract the required information. +// The FES can use the extracted info to generate a friendly error message +// for the matching error. const strings = { ReferenceError: [ { diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index 968cc5dd4e..1255157095 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -126,6 +126,7 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Prints out a fancy, colorful message to the console log + * Attaches Friendly Errors prefix [fes.pre] to the message. * * @method _report * @private @@ -178,7 +179,8 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * This is called internally if there is a error with autoplay. + * This is called internally if there is a error with autoplay. Generates + * and prints a friendly error message [fes.autoplay]. * * @method _friendlyAutoplayError * @private @@ -245,7 +247,8 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * Checks capitalization for user defined functions. + * Checks capitalization for user defined functions. Generates and prints + * a friendly error message [fes.checkUserDefinedFns]. * * @method checkForUserDefinedFunctions * @private @@ -290,7 +293,8 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Compares the symbol caught in the ReferenceErrror to everything in - * misusedAtTopLevel ( all public p5 properties ). + * misusedAtTopLevel ( all public p5 properties ). Generates and prints + * a friendly error message [fes.misspelling]. * * @method handleMisspelling * @private @@ -389,7 +393,10 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * Prints a friendly stacktrace for user-written functions + * Prints a friendly stacktrace for user-written functions for "global" errors + * Generates and prints a friendly error message [fes.globalErrors.stackTop, + * fes.globalErrors.stackSubseq]. + * * @method printFriendlyStack * @private * @param {Array} friendlyStack @@ -425,14 +432,16 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Takes a stacktrace array and filters out all frames that show internal p5 - * details. + * details. Generates and prints a friendly error message [fes.wrongPreload, + * fes.libraryError]. * * The processed stack is used to find whether the error happended 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 write in the p5.js library - * (which may or may not have been called from the user's sketch) + * + * "Internally" here means that the exact location of the error (the top of + * the stack) is a piece of code write in the p5.js library (which may or + * may not have been called from the user's sketch). * * @method processStack * @private @@ -562,11 +571,11 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * Handles various errors that the browser show. - * This is the main function for handling global errors. + * Handles "global" errors that the browser catches. * - * Called when an error happens. It detects the type of error - * and generate an appropriate error message. + * Called when an error event happens and detects the type of error. + * Generates and prints a friendly error message [fes.globalErrors.syntax.*, + * fes.globalErrors.reference.*, fes.globalErrors.type.*]. * * @method fesErrorMonitor * @private @@ -915,6 +924,12 @@ 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 = {}; @@ -960,6 +975,18 @@ defineMisusedAtTopLevelCode = () => { 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(). + * Then generates and prints a friendly error message [fes.misusedTopLevel]. + * + * @method helpForMisusedAtTopLevelCode + * @private + * @param {e} event + * @param {log} log message + * + * @returns {Boolean} true + */ const helpForMisusedAtTopLevelCode = (e, log) => { if (!log) { log = console.log.bind(console); diff --git a/src/core/friendly_errors/sketch_reader.js b/src/core/friendly_errors/sketch_reader.js index 20e9ee1c33..7f2265ec65 100644 --- a/src/core/friendly_errors/sketch_reader.js +++ b/src/core/friendly_errors/sketch_reader.js @@ -53,7 +53,8 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Takes a list of variables defined by the user in the code * as an array and checks if the list contains p5.js constants and functions. - * If found then display a friendly error message. + * If found then generates and print a friendly error + * [fes.sketchReaderErrors.reservedConst, fes.sketchReaderErrors.reservedFunc] * * @method checkForConstsAndFuncs * @private @@ -241,8 +242,9 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * Checks if any p5.js constant or function is - * declared outside a function and reports it if found. + * Checks if any p5.js constant or function is declared outside a function + * and reports it if found. Generates and print a friendly error + * [fes.sketchReaderErrors.reservedConst, fes.sketchReaderErrors.reservedFunc] * * @method globalConstFuncCheck * @private diff --git a/translations/en/translation.json b/translations/en/translation.json index bb60da95e9..f0afe49617 100644 --- a/translations/en/translation.json +++ b/translations/en/translation.json @@ -42,7 +42,7 @@ "readFromUndefined": "\nError ▶️ Cannot read property of undefined. Check the line number in error and make sure the variable which is being operated is not undefined.\nFor more: \n{{url1}}\n{{url2}}" } }, - "libraryError": "An error with message \"{{error}}\" occured inside the p5js library when {{func}} was called {{location}}\n\nIf not stated otherwise, it might be an issue with the arguments passed to {{func}}.", + "libraryError": "An error with message \"{{error}}\" occurred inside the p5js library when {{func}} was called {{location}}\n\nIf not stated otherwise, it might be an issue with the arguments passed to {{func}}.", "location": "(on line {{line}} in {{file}} [{{location}}])", "misspelling": "It seems that you may have accidentally written \"{{name}}\" instead of \"{{actualName}}\" {{location}}.\n\nPlease correct it to {{actualName}} if you wish to use the {{type}} from p5.js", "misspelling_plural": "It seems that you may have accidentally written \"{{name}}\" {{location}}.\n\nYou may have meant one of the following:\n{{suggestions}}", @@ -67,6 +67,6 @@ "reservedFunc": "you have used a p5.js reserved function \"{{symbol}}\" make sure you change the function name to something else." }, "welcome": "Welcome! This is your friendly debugger. To turn me off, switch to using p5.min.js.", - "wrongPreload": "An error with message \"{{error}}\" occured inside the p5js library when \"{{func}}\" was called {{location}}.\n\nIf not stated otherwise, it might be due to \"{{func}}\" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function." + "wrongPreload": "An error with message \"{{error}}\" occurred inside the p5js library when \"{{func}}\" was called {{location}}.\n\nIf not stated otherwise, it might be due to \"{{func}}\" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function." } } From 1bef8213feed60c3a724bddfc841e3344b1b8e41 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Tue, 2 Nov 2021 20:34:31 -0700 Subject: [PATCH 12/18] adding inline docs --- contributor_docs/fes_reference_dev_notes.md | 60 ++++++++++++++++----- src/core/friendly_errors/fes_core.js | 36 ++++++++----- src/core/friendly_errors/file_errors.js | 3 ++ src/core/friendly_errors/sketch_reader.js | 13 ++--- src/core/friendly_errors/validate_params.js | 4 +- 5 files changed, 83 insertions(+), 33 deletions(-) diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md index 8e17ecc237..800199d83c 100644 --- a/contributor_docs/fes_reference_dev_notes.md +++ b/contributor_docs/fes_reference_dev_notes.md @@ -34,7 +34,7 @@ core/friendly_errors/fes_core.js Implemented to functions in: * `core/friendly_errors/fes_core/fesErrorMonitor()` * `core/friendly_errors/fes_core/checkForUserDefinedFunctions()` -* `core/friendly_errors/fes_core/handleMisspelling()`` +* `core/friendly_errors/fes_core/handleMisspelling()` * `core/friendly_errors/fes_core/processStack()` * `core/friendly_errors/file_errors` * `core/friendly_errors/sketch_reader` @@ -70,7 +70,7 @@ core/friendly_errors/fes_core.js ### `_friendlyFileLoadError()` ##### Examples -Example of file loading error: +Example of File Loading Error: ````JavaScript /// missing font file let myFont; @@ -90,6 +90,8 @@ function draw() {}; ##### Description `_friendlyFileLoadError()` is called by the `loadX()` functions if there is an error during file loading. +Generates and prints a friendly error message using key: `fes.fileLoadError.*`. + This function generates and displays friendly error messages if a file fails to load correctly. It also checks and produces a warning if the size of a file is too large to load. The current version contains templates for generating error messages for `image`, `XML`, `table`, `text`, `json` and `font` files. @@ -122,14 +124,14 @@ core/friendly_errors/file_errors.js ### `validateParameters()` ##### Examples -Example of a missing parameter: +Example of a Missing Parameter: ````JavaScript arc(1, 1, 10.5, 10); // FES will generate the following message in the console: // > 🌸 p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] // > 🌸 p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] ```` -Example of a type mismatch: +Example of a Type Mismatch: ````JavaScript arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); // FES will generate the following message in the console: @@ -138,6 +140,8 @@ arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); ##### Description `validateParameters()` runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct types of parameters. +Generates and prints a friendly error message using key: `fes.friendlyParamError.*`. + This function can be called through: `p5._validateParameters(FUNCT_NAME, ARGUMENTS)` or, `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` inside the function that requires parameter validation. It is recommended to use static version, `p5._validateParameters` for general purposes. `p5.prototype._validateParameters(FUNCT_NAME, ARGUMENTS)` mainly remained for debugging and unit testing purposes. Implemented to functions in: @@ -220,7 +224,7 @@ function setup() { } // > 🌸 p5.js says: An error with message "Cannot read property 'bind' of undefined" occured inside the p5js library when mouseClicked was called (on line 3 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:3:7]) If not stated otherwise, it might be an issue with the arguments passed to mouseClicked. (http://p5js.org/reference/#/p5/mouseClicked) ```` -Error in user's sketch example (scope) +Example of an Error in User's Sketch (Scope) ````JavaScript function setup() { let b = 1; @@ -231,7 +235,7 @@ function draw() { // FES will show: // > 🌸 p5.js says: There's an error due to "b" not being defined in the current scope (on line 5 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:5:3]). If you have defined it in your code, you should check its scope, spelling, and letter-casing (JavaScript is case-sensitive). For more: https://p5js.org/examples/data-variable-scope.html https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong ```` -Error in user's sketch example (spelling) +Example of an Error in User's Sketch Example (Spelling) ````JavaScript function setup() { colour(1, 2, 3); @@ -242,6 +246,12 @@ function setup() { ##### Description `fesErrorMonitor()` handles various errors that the browser shows. The function generates global error messages. +Generates and prints... +* ... a friendly error message using key: `fes.globalErrors.syntax.*`, `fes.globalErrors.reference.*`, `fes.globalErrors.type.*`. +* ... an "internal library" error message via `processStack()` using key: `fes.wrongPreload`, `fes.libraryError`. +* ... a stacktrace message via `printFriendlyStack()` using key: `fes.globalErrors.stackTop`,`fes.globalErrors.stackSubseq`. +* ... a spell-check message (from a reference error) via `handleMisspelling()` using key: `fes.misspelling`. + `_fesErrorMonitor()` can be called either by an error event, an unhandled rejection event, or it can be manually called in a catch block as follows: ``` try { someCode(); } catch(err) { p5._fesErrorMonitor(err); } @@ -277,7 +287,7 @@ core/friendly_errors/fes_core.js ### `fesCodeReader()` ##### Examples -Redefining p5.js reserved constant +Example of Redefining p5.js Reserved Constant ````JavaScript function setup() { //PI is a p5.js reserved constant @@ -286,7 +296,7 @@ function setup() { // FES will show: // > 🌸 p5.js says: you have used a p5.js reserved variable "PI" make sure you change the variable name to something else. (https://p5js.org/reference/#/p5/PI) ```` -Redefining p5.js reserved function +Example of Redefining p5.js Reserved Function ````JavaScript function setup() { //text is a p5.js reserved function @@ -296,8 +306,11 @@ function setup() { // > 🌸 p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. ```` ##### Description -`fesCodeReader()` checks (1) if any p5.js constant or function is used outside of the setup() and/or draw() function and (2) if any p5.js reserved constant or function is redeclared. -In setup() and draw() functions it performs: +`fesCodeReader()` checks (1) if any p5.js constant or function is used outside of the `setup()` and/or `draw()` function and (2) if any p5.js reserved constant or function is redeclared. + +Generates and print a friendly error message with type: `fes.sketchReaderErrors.reservedConst`, `fes.sketchReaderErrors.reservedFunc`. + +In `setup()` and `draw()` functions it performs: * Extraction of the code written by the user * Conversion of the code to an array of lines of code * Catching variable and function declaration @@ -306,7 +319,7 @@ In setup() and draw() functions it performs: This function is executed whenever the `load` event is triggered. ##### Location -core/friendly_errors/fes_core/sketch_reader.js +core/friendly_errors/sketch_reader.js ### `checkForUserDefinedFunctions()` ##### Examples @@ -319,6 +332,8 @@ function preLoad() { ```` ##### Description Checks if any user defined function (`setup()`, `draw()`, `mouseMoved()`, etc.) has been used with a capitalization mistake. + +Generates and prints a friendly error message using key: `fes.checkUserDefinedFns`. ##### Syntax ````JavaScript checkForUserDefinedFunctions(context) @@ -332,11 +347,30 @@ checkForUserDefinedFunctions(context) ##### Location core/friendly_errors/fes_core.js +### `_friendlyAutoplayError()` +##### Description +`_friendlyAutoplayError()` is called internally if there is an error with autoplay. + +Generates and prints a friendly error message using key: `fes.autoplay`. +##### Location +core/friendly_errors/fes_core.js + + +### `helpForMisusedAtTopLevelCode()` +##### Description +`helpForMisusedAtTopLevelCode()` is called by `fes_core.js` on window load to check for use of p5.js functions outside of `setup()` or `draw()`. + +Generates and prints a friendly error message using key: `fes.misusedTopLevel`. +##### Parameters +``` +@param {e} event +@param {log} log message +``` +##### Location +core/friendly_errors/fes_core.js ## Development Notes: Notes from Developers #### Misc. FES Functions that Generates Friendly Errors -* `helpForMisusedAtTopLevelCode()` is called by `fes_core.js` on window load to check for use of p5.js functions outside of `setup()` or `draw()`. - * `friendlyWelcome()` prints to console directly. (Hidden by default.) * `stacktrace.js` contains the code to parse the error stack, borrowed from https://github.com/stacktracejs/stacktrace.js. This is called by `fesErrorMonitor()` and `handleMisspelling()` diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index 1255157095..42fd496eb7 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -179,7 +179,7 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * This is called internally if there is a error with autoplay. Generates + * This is called internally if there is an error with autoplay. Generates * and prints a friendly error message [fes.autoplay]. * * @method _friendlyAutoplayError @@ -247,8 +247,10 @@ if (typeof IS_MINIFIED !== 'undefined') { }; /** - * Checks capitalization for user defined functions. Generates and prints - * a friendly error message [fes.checkUserDefinedFns]. + * Checks capitalization for user defined functions. + * + * Generates and prints a friendly error message using key: + * "fes.checkUserDefinedFns". * * @method checkForUserDefinedFunctions * @private @@ -293,8 +295,9 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Compares the symbol caught in the ReferenceErrror to everything in - * misusedAtTopLevel ( all public p5 properties ). Generates and prints - * a friendly error message [fes.misspelling]. + * misusedAtTopLevel ( all public p5 properties ). + * + * Generates and prints a friendly error message using key: "fes.misspelling". * * @method handleMisspelling * @private @@ -351,7 +354,7 @@ if (typeof IS_MINIFIED !== 'undefined') { 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" + // "fes.misspelling_plural" msg = translator('fes.misspelling', { name: errSym, actualName: matchedSymbols[0].name, @@ -394,8 +397,9 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Prints a friendly stacktrace for user-written functions for "global" errors - * Generates and prints a friendly error message [fes.globalErrors.stackTop, - * fes.globalErrors.stackSubseq]. + * + * Generates and prints a friendly error message using key: + * "fes.globalErrors.stackTop", "fes.globalErrors.stackSubseq". * * @method printFriendlyStack * @private @@ -432,8 +436,10 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Takes a stacktrace array and filters out all frames that show internal p5 - * details. Generates and prints a friendly error message [fes.wrongPreload, - * fes.libraryError]. + * details. + * + * Generates and prints a friendly error message using key: + * "fes.wrongPreload", "fes.libraryError". * * The processed stack is used to find whether the error happended internally * within the library, and if the error was due to a non-loadX() method @@ -574,8 +580,10 @@ if (typeof IS_MINIFIED !== 'undefined') { * 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 [fes.globalErrors.syntax.*, - * fes.globalErrors.reference.*, fes.globalErrors.type.*]. + * + * Generates and prints a friendly error message using key: + * "fes.globalErrors.syntax.[*]", "fes.globalErrors.reference.[*]", + * "fes.globalErrors.type.[*]". * * @method fesErrorMonitor * @private @@ -978,7 +986,9 @@ defineMisusedAtTopLevelCode = () => { /** * Detects browser level error event for p5 constants/functions used outside * of setup() and draw(). - * Then generates and prints a friendly error message [fes.misusedTopLevel]. + * + * Generates and prints a friendly error message using key: + * "fes.misusedTopLevel". * * @method helpForMisusedAtTopLevelCode * @private diff --git a/src/core/friendly_errors/file_errors.js b/src/core/friendly_errors/file_errors.js index 4f4075abf1..4a70b63051 100644 --- a/src/core/friendly_errors/file_errors.js +++ b/src/core/friendly_errors/file_errors.js @@ -79,6 +79,9 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Called internally if there is an error during file loading. * + * Generates and prints a friendly error message using key: + * "fes.fileLoadError.[*]". + * * @method _friendlyFileLoadError * @private * @param {Number} errorType diff --git a/src/core/friendly_errors/sketch_reader.js b/src/core/friendly_errors/sketch_reader.js index 7f2265ec65..b4495b92e7 100644 --- a/src/core/friendly_errors/sketch_reader.js +++ b/src/core/friendly_errors/sketch_reader.js @@ -8,10 +8,14 @@ import { translator } from '../internationalization'; import * as constants from '../constants'; /** - * Checks if any p5.js constant/function is declared outside of setup - * and draw function. Also checks any reserved constant/function is + * Checks if any p5.js constant/function is declared outside of setup() + * and draw() function. Also checks any reserved constant/function is * redeclared. * + * Generates and prints a friendly error message using key: + * "fes.sketchReaderErrors.reservedConst", + * "fes.sketchReaderErrors.reservedFunc". + * * @method _fesCodeReader * @private */ @@ -53,8 +57,6 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Takes a list of variables defined by the user in the code * as an array and checks if the list contains p5.js constants and functions. - * If found then generates and print a friendly error - * [fes.sketchReaderErrors.reservedConst, fes.sketchReaderErrors.reservedFunc] * * @method checkForConstsAndFuncs * @private @@ -243,8 +245,7 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Checks if any p5.js constant or function is declared outside a function - * and reports it if found. Generates and print a friendly error - * [fes.sketchReaderErrors.reservedConst, fes.sketchReaderErrors.reservedFunc] + * and reports it if found. * * @method globalConstFuncCheck * @private diff --git a/src/core/friendly_errors/validate_params.js b/src/core/friendly_errors/validate_params.js index bec5cde80f..97255cbd9d 100644 --- a/src/core/friendly_errors/validate_params.js +++ b/src/core/friendly_errors/validate_params.js @@ -681,7 +681,9 @@ if (typeof IS_MINIFIED !== 'undefined') { /** * Runs parameter validation by matching the input parameters with information - * from `docs/reference/data.json` + * from `docs/reference/data.json`. + * Generates and prints a friendly error message using key: + * "fes.friendlyParamError.[*]". * * @method _validateParameters * @private From 58d247fad7f9bf36887878226c72b43e555c2534 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Tue, 2 Nov 2021 20:43:27 -0700 Subject: [PATCH 13/18] minor fixes --- lib/empty-example/sketch.js | 2 +- src/core/friendly_errors/fes_core.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 69b202ee71..de6c862644 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -4,4 +4,4 @@ function setup() { function draw() { // put drawing code here -} +} \ No newline at end of file diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index 42fd496eb7..6dccbd61ad 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -170,9 +170,9 @@ if (typeof IS_MINIFIED !== 'undefined') { * * @method _friendlyError * @private - * @param {String} message message to be printed - * @param {String} [func] name of function calling - * @param {Number|String} [color] CSS color string or error type + * @param {String} message message to be printed + * @param {String} [func] name of function calling + * @param {Number|String} [color] CSS color string or error type */ p5._friendlyError = function(message, func, color) { p5._report(message, func, color); From e4ad52d34b6e50ba2919bceb95beb949666d28fb Mon Sep 17 00:00:00 2001 From: A M Chung Date: Tue, 2 Nov 2021 21:27:37 -0700 Subject: [PATCH 14/18] minor fixes --- contributor_docs/fes_reference_dev_notes.md | 24 ++++++++++----------- src/core/friendly_errors/fes_core.js | 24 ++++++++++----------- src/core/friendly_errors/file_errors.js | 4 ++-- src/core/friendly_errors/validate_params.js | 4 ++-- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md index 800199d83c..90b98a9f77 100644 --- a/contributor_docs/fes_reference_dev_notes.md +++ b/contributor_docs/fes_reference_dev_notes.md @@ -19,8 +19,8 @@ _report(message, func, color) ```` ##### Parameters ``` -@param {String} message message to be printed -@param {String} [func] name of function calling +@param {String} message Message to be printed +@param {String} [func] Name of function @param {Number|String} [color] CSS color string or error type ``` ##### Location @@ -61,9 +61,9 @@ _friendlyError(message, func, color) ```` ##### Parameters ``` -@param {String} message the words to be said -@param {String} [func] the name of the function to link -@param {Number|String} [color] CSS color string or error type +@param {String} message Message to be printed +@param {String} [func] Name of the function +@param {Number|String} [color] CSS color string or error type ``` ##### Location core/friendly_errors/fes_core.js @@ -116,8 +116,8 @@ _friendlyFileLoadError(errorType, filePath) ```` ##### Parameters ``` -@param {Number} errorType -@param {String} filePath +@param {Number} errorType Number of file load error type +@param {String} filePath Path to file caused the error ``` ##### Location core/friendly_errors/file_errors.js @@ -197,8 +197,8 @@ _validateParameters(func, args) ```` ##### Parameters ``` -@param {String} func the name of the function -@param {Array} args user input arguments +@param {String} func Name of the function +@param {Array} args User input arguments ``` ##### Location core/friendly_errors/validate_params.js @@ -280,7 +280,7 @@ fesErrorMonitor(event) ```` ##### Parameters ``` -@param {*} e Event object to extract error details from +@param {*} e Error event ``` ##### Location core/friendly_errors/fes_core.js @@ -363,8 +363,8 @@ core/friendly_errors/fes_core.js Generates and prints a friendly error message using key: `fes.misusedTopLevel`. ##### Parameters ``` -@param {e} event -@param {log} log message +@param {*} err Error event +@param {Boolean} log false ``` ##### Location core/friendly_errors/fes_core.js diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index 6dccbd61ad..e8386c94f7 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -103,8 +103,8 @@ if (typeof IS_MINIFIED !== 'undefined') { * * @method mapToReference * @private - * @param {String} message the words to be said - * @param {String} [func] the name of the function to link + * @param {String} message the words to be said + * @param {String} [func] the name of function * * @returns {String} */ @@ -130,9 +130,9 @@ if (typeof IS_MINIFIED !== 'undefined') { * * @method _report * @private - * @param {String} message the words to be said - * @param {String} [func] the name of the function to link - * @param {Number|String} [color] CSS color string or error type + * @param {String} message Message to be printed + * @param {String} [func] Name of function + * @param {Number|String} [color] CSS color string or error type * * @return console logs */ @@ -170,9 +170,9 @@ if (typeof IS_MINIFIED !== 'undefined') { * * @method _friendlyError * @private - * @param {String} message message to be printed - * @param {String} [func] name of function calling - * @param {Number|String} [color] CSS color string or error type + * @param {String} message Message to be printed + * @param {String} [func] Name of the function linked to error + * @param {Number|String} [color] CSS color string or error type */ p5._friendlyError = function(message, func, color) { p5._report(message, func, color); @@ -301,8 +301,8 @@ if (typeof IS_MINIFIED !== 'undefined') { * * @method handleMisspelling * @private - * @param {String} errSym the symbol to whose spelling to check - * @param {Error} error the ReferenceError object + * @param {String} errSym Symbol to whose spelling to check + * @param {Error} error ReferenceError object * * @returns {Boolean} tell whether error was likely due to typo */ @@ -992,8 +992,8 @@ defineMisusedAtTopLevelCode = () => { * * @method helpForMisusedAtTopLevelCode * @private - * @param {e} event - * @param {log} log message + * @param {Event} e Error event + * @param {Boolean} log false * * @returns {Boolean} true */ diff --git a/src/core/friendly_errors/file_errors.js b/src/core/friendly_errors/file_errors.js index 4a70b63051..4bc3bd85cb 100644 --- a/src/core/friendly_errors/file_errors.js +++ b/src/core/friendly_errors/file_errors.js @@ -84,8 +84,8 @@ if (typeof IS_MINIFIED !== 'undefined') { * * @method _friendlyFileLoadError * @private - * @param {Number} errorType - * @param {String} filePath + * @param {Number} errorType Number of file load error type + * @param {String} filePath Path to file caused the error */ p5._friendlyFileLoadError = function(errorType, filePath) { const { message, method } = fileLoadErrorCases(errorType, filePath); diff --git a/src/core/friendly_errors/validate_params.js b/src/core/friendly_errors/validate_params.js index 97255cbd9d..064780e6ee 100644 --- a/src/core/friendly_errors/validate_params.js +++ b/src/core/friendly_errors/validate_params.js @@ -687,8 +687,8 @@ if (typeof IS_MINIFIED !== 'undefined') { * * @method _validateParameters * @private - * @param {String} func the name of the function - * @param {Array} args user input arguments + * @param {String} func Name of the function + * @param {Array} args User input arguments * * @example: * const a; From fa66fc14c917413a9a4c235193d7daffcbd20824 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Fri, 5 Nov 2021 18:37:49 -0700 Subject: [PATCH 15/18] updated reflecting comments from kate --- contributor_docs/fes_reference_dev_notes.md | 227 +++++++++++--------- src/core/friendly_errors/fes_core.js | 24 ++- 2 files changed, 150 insertions(+), 101 deletions(-) diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md index 90b98a9f77..3eb26ada9b 100644 --- a/contributor_docs/fes_reference_dev_notes.md +++ b/contributor_docs/fes_reference_dev_notes.md @@ -1,5 +1,20 @@ # FES Reference and Notes from Developers -This document contains reference and development notes for the p5.js Friendly Error System (FES). +This document contains reference and development notes for the p5.js Friendly Error System (FES). The FES houses several functions responsible for generating friendly error messages for different types of errors. These functions gather errors from various locations, including error events triggered by the browser, mistakes found while scanning the user code, parameter checking within the library, etc. + +Main functions for generating the friendly error messages are: +* `_validateParameters()` +* `_friendlyFileLoadError()` +* `_friendlyError()` +* `helpForMisusedAtTopLevelCode()` +* `_fesErrorMontitor()` + +These functions are located throughout the `core/friendly_errors/` folder. +* `fes_core.js` contains the core as well as miscellaneous functionality of the FES. +* `_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 + +The following section presents the full reference for the FES functions. ## FES Functions: Reference @@ -21,7 +36,7 @@ _report(message, func, color) ``` @param {String} message Message to be printed @param {String} [func] Name of function -@param {Number|String} [color] CSS color string or error type +@param {Number|String} [color] CSS color code ``` ##### Location core/friendly_errors/fes_core.js @@ -63,30 +78,12 @@ _friendlyError(message, func, color) ``` @param {String} message Message to be printed @param {String} [func] Name of the function -@param {Number|String} [color] CSS color string or error type +@param {Number|String} [color] CSS color code ``` ##### Location core/friendly_errors/fes_core.js ### `_friendlyFileLoadError()` -##### Examples -Example of File Loading Error: -````JavaScript -/// missing font file -let myFont; -function preload() { - myFont = loadFont('assets/OpenSans-Regular.ttf'); -}; -function setup() { - fill('#ED225D'); - textFont(myFont); - textSize(36); - text('p5*js', 10, 50); -}; -function draw() {}; -// FES will generate the following message in the console: -// > 🌸 p5.js says: It looks like there was a problem loading your font. Try checking if the file path [assets/OpenSans-Regular.ttf] is correct, hosting the font online, or running a local server.[https://github.com/processing/p5.js/wiki/Local-server] -```` ##### Description `_friendlyFileLoadError()` is called by the `loadX()` functions if there is an error during file loading. @@ -119,24 +116,30 @@ _friendlyFileLoadError(errorType, filePath) @param {Number} errorType Number of file load error type @param {String} filePath Path to file caused the error ``` +##### Examples +File Loading Error Example +````JavaScript +/// missing font file +let myFont; +function preload() { + myFont = loadFont('assets/OpenSans-Regular.ttf'); +}; +function setup() { + fill('#ED225D'); + textFont(myFont); + textSize(36); + text('p5*js', 10, 50); +}; +function draw() {}; +```` +FES will generate the following message in the console: +> 🌸 p5.js says: It looks like there was a problem loading your font. Try checking if the file path [assets/OpenSans-Regular.ttf] is correct, hosting the font online, or running a local server.[https://github.com/processing/p5.js/wiki/Local-server] + + ##### Location core/friendly_errors/file_errors.js ### `validateParameters()` -##### Examples -Example of a Missing Parameter: -````JavaScript -arc(1, 1, 10.5, 10); -// FES will generate the following message in the console: -// > 🌸 p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] -// > 🌸 p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] -```` -Example of a Type Mismatch: -````JavaScript -arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); -// FES will generate the following message in the console: -// > 🌸 p5.js says: arc() was expecting Number for parameter #0 (zero-based index), received string instead. [http://p5js.org/reference/#p5/arc] -```` ##### Description `validateParameters()` runs parameter validation by matching the input parameters with information from `docs/reference/data.json`, which is created from the function's inline documentation. It checks that a function call contains the correct number and the correct types of parameters. @@ -200,49 +203,26 @@ _validateParameters(func, args) @param {String} func Name of the function @param {Array} args User input arguments ``` -##### Location -core/friendly_errors/validate_params.js - -### `fesErrorMonitor()` ##### Examples -Internal Error Example 1 +Example of a Missing Parameter ````JavaScript -function preload() { - // error in background() due to it being called in - // preload - background(200); -} -// FES will show: -// > 🌸 p5.js says: An error with message "Cannot read property 'background' of undefined" occured inside the p5js library when "background" was called (on line 4 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:4:3]). -//If not stated otherwise, it might be due to "background" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function. (http://p5js.org/reference/#/p5/preload) -```` -Internal Error Example 2 -````JavaScript -function setup() { - cnv = createCanvas(200, 200); - cnv.mouseClicked(); -} -// > 🌸 p5.js says: An error with message "Cannot read property 'bind' of undefined" occured inside the p5js library when mouseClicked was called (on line 3 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:3:7]) If not stated otherwise, it might be an issue with the arguments passed to mouseClicked. (http://p5js.org/reference/#/p5/mouseClicked) -```` -Example of an Error in User's Sketch (Scope) -````JavaScript -function setup() { - let b = 1; -} -function draw() { - b += 1; -} -// FES will show: -// > 🌸 p5.js says: There's an error due to "b" not being defined in the current scope (on line 5 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:5:3]). If you have defined it in your code, you should check its scope, spelling, and letter-casing (JavaScript is case-sensitive). For more: https://p5js.org/examples/data-variable-scope.html https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong +arc(1, 1, 10.5, 10); ```` -Example of an Error in User's Sketch Example (Spelling) +FES will generate the following message in the console: +> 🌸 p5.js says: It looks like arc() received an empty variable in spot #4 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] + +> 🌸 p5.js says: It looks like arc() received an empty variable in spot #5 (zero-based index). If not intentional, this is often a problem with scope: [https://p5js.org/examples/data-variable-scope.html]. [http://p5js.org/reference/#p5/arc] + +Example of a Type Mismatch ````JavaScript -function setup() { - colour(1, 2, 3); -} -// FES will show: -// > 🌸 p5.js says: It seems that you may have accidentally written "colour" instead of "color" (on line 2 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:2:3]). Please correct it to color if you wish to use the function from p5.js (http://p5js.org/reference/#/p5/color) +arc('1', 1, 10.5, 10, 0, Math.PI, 'pie'); ```` +FES will generate the following message in the console: +> 🌸 p5.js says: arc() was expecting Number for parameter #0 (zero-based index), received string instead. [http://p5js.org/reference/#p5/arc] +##### Location +core/friendly_errors/validate_params.js + +### `fesErrorMonitor()` ##### Description `fesErrorMonitor()` handles various errors that the browser shows. The function generates global error messages. @@ -282,29 +262,53 @@ fesErrorMonitor(event) ``` @param {*} e Error event ``` -##### Location -core/friendly_errors/fes_core.js - -### `fesCodeReader()` ##### Examples -Example of Redefining p5.js Reserved Constant +Internal Error Example 1 +````JavaScript +function preload() { + // error in background() due to it being called in + // preload + background(200); +} +```` +FES will generate the following message in the console: +> 🌸 p5.js says: An error with message "Cannot read property 'background' of undefined" occured inside the p5js library when "background" was called (on line 4 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:4:3]). +//If not stated otherwise, it might be due to "background" being called from preload. Nothing besides load calls (loadImage, loadJSON, loadFont, loadStrings, etc.) should be inside the preload function. (http://p5js.org/reference/#/p5/preload) + +Internal Error Example 2 ````JavaScript function setup() { - //PI is a p5.js reserved constant - let PI = 100; + cnv = createCanvas(200, 200); + cnv.mouseClicked(); } -// FES will show: -// > 🌸 p5.js says: you have used a p5.js reserved variable "PI" make sure you change the variable name to something else. (https://p5js.org/reference/#/p5/PI) ```` -Example of Redefining p5.js Reserved Function +FES will generate the following message in the console: +> 🌸 p5.js says: An error with message "Cannot read property 'bind' of undefined" occured inside the p5js library when mouseClicked was called (on line 3 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:3:7]) If not stated otherwise, it might be an issue with the arguments passed to mouseClicked. (http://p5js.org/reference/#/p5/mouseClicked) + +Example of an Error in User's Sketch (Scope) ````JavaScript function setup() { - //text is a p5.js reserved function - let text = 100; + let b = 1; +} +function draw() { + b += 1; +} +```` +FES will generate the following message in the console: +> 🌸 p5.js says: There's an error due to "b" not being defined in the current scope (on line 5 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:5:3]). If you have defined it in your code, you should check its scope, spelling, and letter-casing (JavaScript is case-sensitive). For more: https://p5js.org/examples/data-variable-scope.html https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_Defined#What_went_wrong + +Example of an Error in User's Sketch Example (Spelling) +````JavaScript +function setup() { + colour(1, 2, 3); } -// FES will show: -// > 🌸 p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. ```` +FES will generate the following message in the console: +> 🌸 p5.js says: It seems that you may have accidentally written "colour" instead of "color" (on line 2 in sketch.js [http://localhost:8000/lib/empty-example/sketch.js:2:3]). Please correct it to color if you wish to use the function from p5.js (http://p5js.org/reference/#/p5/color) +##### Location +core/friendly_errors/fes_core.js + +### `fesCodeReader()` ##### Description `fesCodeReader()` checks (1) if any p5.js constant or function is used outside of the `setup()` and/or `draw()` function and (2) if any p5.js reserved constant or function is redeclared. @@ -318,18 +322,32 @@ In `setup()` and `draw()` functions it performs: This function is executed whenever the `load` event is triggered. -##### Location -core/friendly_errors/sketch_reader.js - -### `checkForUserDefinedFunctions()` ##### Examples +Example of Redefining p5.js Reserved Constant ````JavaScript -function preLoad() { - loadImage('myimage.png'); +function setup() { + //PI is a p5.js reserved constant + let PI = 100; } -// FES will show: -// > 🌸 p5.js says: It seems that you may have accidentally written preLoad instead of preload. Please correct it if it's not intentional. (http://p5js.org/reference/#/p5/preload) ```` +FES will generate the following message in the console: +> 🌸 p5.js says: you have used a p5.js reserved variable "PI" make sure you change the variable name to something else. (https://p5js.org/reference/#/p5/PI) + +Example of Redefining p5.js Reserved Function +````JavaScript +function setup() { + //text is a p5.js reserved function + let text = 100; +} +```` +FES will generate the following message in the console: +> 🌸 p5.js says: you have used a p5.js reserved function "text" make sure you change the function name to something else. + + +##### Location +core/friendly_errors/sketch_reader.js + +### `checkForUserDefinedFunctions()` ##### Description Checks if any user defined function (`setup()`, `draw()`, `mouseMoved()`, etc.) has been used with a capitalization mistake. @@ -344,12 +362,21 @@ checkForUserDefinedFunctions(context) Set to window in "global mode" and to a p5 instance in "instance mode" ``` +##### Examples +````JavaScript +function preLoad() { + loadImage('myimage.png'); +} +```` +FES will generate the following message in the console: +> 🌸 p5.js says: It seems that you may have accidentally written preLoad instead of preload. Please correct it if it's not intentional. (http://p5js.org/reference/#/p5/preload) + ##### Location core/friendly_errors/fes_core.js ### `_friendlyAutoplayError()` ##### Description -`_friendlyAutoplayError()` is called internally if there is an error with autoplay. +`_friendlyAutoplayError()` is called internally if there is an error linked to playing a media (for example, a video), most likely due to the browser's autoplay policy. Generates and prints a friendly error message using key: `fes.autoplay`. ##### Location @@ -418,9 +445,9 @@ will escape FES, because there is an acceptable parameter pattern (`Number`, `Nu * FES is only able to detect global variables overwritten when declared using `const` or `var`. If `let` is used, they go undetected. This is not currently solvable due to the way `let` instantiates variables. -* The functionality described under **`fesErrorMonitor()`** currently only works on the web editor or if running on a local server. For more details see [this](https://github.com/processing/p5.js/pull/4730). +* The functionality described under **`fesErrorMonitor()`** currently only works on the web editor or if running on a local server. For more details see pull request [#4730](https://github.com/processing/p5.js/pull/4730). -* The extracting variable/function names feature of FES's `sketch_reader` is not perfect and some cases might go undetected (for eg: when all the code is written in a single line). +* `sketch_reader()` may miss some cases (e.g. when all the code is written in a single line) while it extracts variable/function names from the user code. ## Thoughts for Future Work * Re-introduce color coding for the Web Editor. @@ -435,7 +462,7 @@ will escape FES, because there is an acceptable parameter pattern (`Number`, `Nu * Extend Global Error catching. This means catching errors that the browser is throwing to the console and matching them with friendly messages. `fesErrorMonitor()` does this for a select few kinds of errors but help in supporting more is welcome :) -* Improve `sketch_reader.js`'s code reading and variable/function name extracting functionality (which extracts names of the function and variables declared by the user in their code). For example currently `sketch_reader.js` is not able to extract variable/function names properly if all the code is written in a single line. +* Improve `sketch_reader.js`'s code reading and variable/function name extracting functionality (which extracts names of the function and variables declared by the user in their code). For example,`sketch_reader.js` cannot extract variable/function names properly if all the code is written in a single line. We welcome future proposals to identify all these "escape" cases and add unit tests to catch them. * `sketch_reader.js` can be extended and new features (for example: Alerting the user when they have declared a variable in the `draw()` function) can be added to it to better aid the user. ```JavaScript @@ -454,3 +481,5 @@ const original_functions = { }; }); ``` +* Generate the FES reference from the inline doc. This generated reference can be a separate system from our main [p5.js reference], to keep functions for sketch and console separate to reduce possible confusion. +[p5.js reference]: (https://p5js.org/reference/) diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index e8386c94f7..d2e16e6c63 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -1,6 +1,26 @@ /** * @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) _fesErrorMontitor. + * + * _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 */ import p5 from '../main'; import { translator } from '../internationalization'; @@ -132,7 +152,7 @@ if (typeof IS_MINIFIED !== 'undefined') { * @private * @param {String} message Message to be printed * @param {String} [func] Name of function - * @param {Number|String} [color] CSS color string or error type + * @param {Number|String} [color] CSS color code * * @return console logs */ @@ -172,7 +192,7 @@ if (typeof IS_MINIFIED !== 'undefined') { * @private * @param {String} message Message to be printed * @param {String} [func] Name of the function linked to error - * @param {Number|String} [color] CSS color string or error type + * @param {Number|String} [color] CSS color code */ p5._friendlyError = function(message, func, color) { p5._report(message, func, color); From 77ba8e07d53644817b0eb3c74fca314c9f57b3c9 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Tue, 9 Nov 2021 18:59:06 -0800 Subject: [PATCH 16/18] fixed links --- contributor_docs/fes_reference_dev_notes.md | 2 +- contributor_docs/friendly_error_system.md | 28 ++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md index 3eb26ada9b..ed1117d596 100644 --- a/contributor_docs/fes_reference_dev_notes.md +++ b/contributor_docs/fes_reference_dev_notes.md @@ -482,4 +482,4 @@ const original_functions = { }); ``` * Generate the FES reference from the inline doc. This generated reference can be a separate system from our main [p5.js reference], to keep functions for sketch and console separate to reduce possible confusion. -[p5.js reference]: (https://p5js.org/reference/) +[p5.js reference]: https://p5js.org/reference/ diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index e810060335..8c530227c4 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -8,8 +8,8 @@ The FES prints messages in the console window, as seen in the [p5.js Web Editor] *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸] -[p5.js Web Editor]: (https://github.com/processing/p5.js-web-editor) -[🌸 SURVEY 🌸]: (https://forms.gle/4cCGE1ecfoiaMGzt7) +[p5.js Web Editor]: https://github.com/processing/p5.js-web-editor +[🌸 SURVEY 🌸]: https://bit.ly/p5fesSurvey ## Writing Friendly Error Messages @@ -19,8 +19,8 @@ The FES is a part of the p5.js' [internationalization] effort. We generate all F We welcome contributions from all around the world! 🌐 -[internationalization]: (https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) -[i18next]: (https://www.i18next.com/) +[internationalization]: https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md +[i18next]: https://www.i18next.com/ #### Writing Best Practices @@ -34,9 +34,9 @@ Here are some highlights from our upcoming best-practice doc: * Try to spot possible "[expert blind spots]" in an error message and its related docs. * Introduce one technical concept or term at a time—link one external resource written in a beginner-friendly language with plenty of short, practical examples. -[interpolation]: (https://www.i18next.com/translation-function/interpolation) -[p5.js Code of Conduct]: (https://github.com/processing/p5.js/blob/main/CODE_OF_CONDUCT.md#p5js-code-of-conduct) -[expert blind spots]: (https://tilt.colostate.edu/TipsAndGuides/Tip/181) +[interpolation]: https://www.i18next.com/translation-function/interpolation +[p5.js Code of Conduct]: https://github.com/processing/p5.js/blob/main/CODE_OF_CONDUCT.md#p5js-code-of-conduct +[expert blind spots]: https://tilt.colostate.edu/TipsAndGuides/Tip/181 #### Location of Translation Files @@ -89,12 +89,12 @@ translator('fes.fileLoadError.image', { suggestion }); The [internationalization doc] has a step-by-step guide on adding and modifying translation files. -[internationalization doc]: (https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md) +[internationalization doc]: https://github.com/processing/p5.js/blob/main/contributor_docs/internationalization.md ## Understanding How FES Works In this section, we will give an overview of how FES generates and displays messages. For more detailed information on the FES functions, please see our [FES Reference + Dev Notes]. -[FES Reference + Dev Notes]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md) +[FES Reference + Dev Notes]: https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md #### Overview p5.js calls the FES from multiple locations for different situations, when: @@ -116,10 +116,10 @@ These functions are mainly responsible for catching errors and generating FES me For full reference, please see our [Dev Notes]. -[`_friendlyFileLoadError()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#_friendlyfileloaderror) -[`_validateParameters()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#validateparameters) -[`_fesErrorMontitor()`]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#feserrormonitor) -[Dev Notes]: (https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md) +[`_friendlyFileLoadError()`]: https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#_friendlyfileloaderror +[`_validateParameters()`]: https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#validateparameters +[`_fesErrorMontitor()`]: https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md#feserrormonitor +[Dev Notes]: https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md #### FES Message Displayer `fes_core.js/_friendlyError()` prints generated friendly error messages in the console. For example: @@ -147,4 +147,4 @@ function setup() { The single minified file of p5 (i.e., p5.min.js) automatically omits the FES. -[disable the FES for performance]: (https://github.com/processing/p5.js/wiki/Optimizing-p5.js-Code-for-Performance#disable-the-friendly-error-system-fes) +[disable the FES for performance]: https://github.com/processing/p5.js/wiki/Optimizing-p5.js-Code-for-Performance#disable-the-friendly-error-system-fes From 6313258e59d1ccd61cce5a494be3ad3821fd39e1 Mon Sep 17 00:00:00 2001 From: A M Chung Date: Tue, 9 Nov 2021 19:03:28 -0800 Subject: [PATCH 17/18] updated links --- contributor_docs/fes_reference_dev_notes.md | 1 + contributor_docs/friendly_error_system.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contributor_docs/fes_reference_dev_notes.md b/contributor_docs/fes_reference_dev_notes.md index ed1117d596..877a186497 100644 --- a/contributor_docs/fes_reference_dev_notes.md +++ b/contributor_docs/fes_reference_dev_notes.md @@ -482,4 +482,5 @@ const original_functions = { }); ``` * Generate the FES reference from the inline doc. This generated reference can be a separate system from our main [p5.js reference], to keep functions for sketch and console separate to reduce possible confusion. + [p5.js reference]: https://p5js.org/reference/ diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index 8c530227c4..2946728465 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -8,7 +8,7 @@ The FES prints messages in the console window, as seen in the [p5.js Web Editor] *We have an ongoing survey!* Please take a moment to fill out this 5-minute survey to help us improve the FES: [🌸 SURVEY 🌸] -[p5.js Web Editor]: https://github.com/processing/p5.js-web-editor +[p5.js Web Editor]: https://editor.p5js.org/ [🌸 SURVEY 🌸]: https://bit.ly/p5fesSurvey ## Writing Friendly Error Messages From ec88bf01c16fa766d59cbdc0662058209ed9339a Mon Sep 17 00:00:00 2001 From: A M Chung Date: Tue, 9 Nov 2021 19:07:00 -0800 Subject: [PATCH 18/18] updated links --- contributor_docs/friendly_error_system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributor_docs/friendly_error_system.md b/contributor_docs/friendly_error_system.md index 2946728465..99ad4a99d8 100644 --- a/contributor_docs/friendly_error_system.md +++ b/contributor_docs/friendly_error_system.md @@ -111,7 +111,7 @@ You can find the translation files used by the `translator()` inside: #### FES Message Generators These functions are mainly responsible for catching errors and generating FES messages: * [`_friendlyFileLoadError()`] catches file loading errors. -* [`_validateParameters()`] checks a p5.js function’s input parameters based on inline documentations. +* [`_validateParameters()`] checks a p5.js function’s input parameters based on inline documents. * [`_fesErrorMontitor()`] handles global errors. For full reference, please see our [Dev Notes].