11import { parse } from 'acorn' ;
22import { simple as walk } from 'acorn-walk' ;
33import * as constants from '../constants' ;
4- import { loadP5Constructors } from './friendly_errors_utils' ;
54
6- /**
7- * @for p5
8- * @requires core
9- */
10- function sketchVerifier ( p5 , fn ) {
11- // List of functions to ignore as they either are meant to be re-defined or
12- // generate false positive outputs.
13- const ignoreFunction = [
14- 'setup' ,
15- 'draw' ,
16- 'preload' ,
17- 'deviceMoved' ,
18- 'deviceTurned' ,
19- 'deviceShaken' ,
20- 'doubleClicked' ,
21- 'mousePressed' ,
22- 'mouseReleased' ,
23- 'mouseMoved' ,
24- 'mouseDragged' ,
25- 'mouseClicked' ,
26- 'mouseWheel' ,
27- 'touchStarted' ,
28- 'touchMoved' ,
29- 'touchEnded' ,
30- 'keyPressed' ,
31- 'keyReleased' ,
32- 'keyTyped' ,
33- 'windowResized' ,
34- 'name' ,
35- 'parent' ,
36- 'toString' ,
37- 'print' ,
38- 'stop' ,
39- 'onended'
40- ] ;
5+ // List of functions to ignore as they either are meant to be re-defined or
6+ // generate false positive outputs.
7+ const ignoreFunction = [
8+ 'setup' ,
9+ 'draw' ,
10+ 'preload' ,
11+ 'deviceMoved' ,
12+ 'deviceTurned' ,
13+ 'deviceShaken' ,
14+ 'doubleClicked' ,
15+ 'mousePressed' ,
16+ 'mouseReleased' ,
17+ 'mouseMoved' ,
18+ 'mouseDragged' ,
19+ 'mouseClicked' ,
20+ 'mouseWheel' ,
21+ 'touchStarted' ,
22+ 'touchMoved' ,
23+ 'touchEnded' ,
24+ 'keyPressed' ,
25+ 'keyReleased' ,
26+ 'keyTyped' ,
27+ 'windowResized' ,
28+ // 'name',
29+ // 'parent',
30+ // 'toString',
31+ // 'print',
32+ // 'stop',
33+ // 'onended'
34+ ] ;
35+
36+ export const verifierUtils = {
4137
4238 /**
4339 * Fetches the contents of a script element in the user's sketch.
44- *
40+ *
41+ * @private
4542 * @method fetchScript
4643 * @param {HTMLScriptElement } script
4744 * @returns {Promise<string> }
48- */
49- fn . fetchScript = async function ( script ) {
45+ */
46+ fetchScript : async function ( script ) {
5047 if ( script . src ) {
5148 try {
5249 const contents = await fetch ( script . src ) . then ( ( res ) => res . text ( ) ) ;
@@ -59,37 +56,20 @@ function sketchVerifier(p5, fn) {
5956 } else {
6057 return script . textContent ;
6158 }
62- }
63-
64- /**
65- * Extracts the user's code from the script fetched. Note that this method
66- * assumes that the user's code is always the last script element in the
67- * sketch.
68- *
69- * @method getUserCode
70- * @returns {Promise<string> } The user's code as a string.
71- */
72- fn . getUserCode = async function ( ) {
73- // TODO: think of a more robust way to get the user's code. Refer to
74- // https://github.com/processing/p5.js/pull/7293.
75- const scripts = document . querySelectorAll ( 'script' ) ;
76- const userCodeScript = scripts [ scripts . length - 1 ] ;
77- const userCode = await fn . fetchScript ( userCodeScript ) ;
78-
79- return userCode ;
80- }
59+ } ,
8160
8261 /**
8362 * Extracts the user-defined variables and functions from the user code with
8463 * the help of Espree parser.
85- *
64+ *
65+ * @private
8666 * @method extractUserDefinedVariablesAndFuncs
8767 * @param {string } code - The code to extract variables and functions from.
8868 * @returns {Object } An object containing the user's defined variables and functions.
8969 * @returns {Array<{name: string, line: number}> } [userDefinitions.variables] Array of user-defined variable names and their line numbers.
9070 * @returns {Array<{name: string, line: number}> } [userDefinitions.functions] Array of user-defined function names and their line numbers.
9171 */
92- fn . extractUserDefinedVariablesAndFuncs = function ( code ) {
72+ extractUserDefinedVariablesAndFuncs : function ( code ) {
9373 const userDefinitions = {
9474 variables : [ ] ,
9575 functions : [ ]
@@ -142,26 +122,27 @@ function sketchVerifier(p5, fn) {
142122 }
143123
144124 return userDefinitions ;
145- }
125+ } ,
146126
147127 /**
148128 * Checks user-defined variables and functions for conflicts with p5.js
149129 * constants and global functions.
150- *
130+ *
151131 * This function performs two main checks:
152132 * 1. Verifies if any user definition conflicts with p5.js constants.
153133 * 2. Checks if any user definition conflicts with global functions from
154134 * p5.js renderer classes.
155- *
135+ *
156136 * If a conflict is found, it reports a friendly error message and halts
157137 * further checking.
158- *
138+ *
139+ * @private
159140 * @param {Object } userDefinitions - An object containing user-defined variables and functions.
160141 * @param {Array<{name: string, line: number}> } userDefinitions.variables - Array of user-defined variable names and their line numbers.
161142 * @param {Array<{name: string, line: number}> } userDefinitions.functions - Array of user-defined function names and their line numbers.
162143 * @returns {boolean } - Returns true if a conflict is found, false otherwise.
163144 */
164- fn . checkForConstsAndFuncs = function ( userDefinitions , p5Constructors ) {
145+ checkForConstsAndFuncs : function ( userDefinitions , p5 ) {
165146 const allDefinitions = [
166147 ...userDefinitions . variables ,
167148 ...userDefinitions . functions
@@ -172,78 +153,85 @@ function sketchVerifier(p5, fn) {
172153 // redefinition, the line number in user's code, and a link to its
173154 // reference on the p5.js website.
174155 function generateFriendlyError ( errorType , name , line ) {
175- const url = `https://p5js.org/reference/#/ p5/${ name } ` ;
176- const message = `${ errorType } "${ name } " on line ${ line } is being redeclared and conflicts with a p5.js ${ errorType . toLowerCase ( ) } . JavaScript does not support declaring a ${ errorType . toLowerCase ( ) } more than once. p5.js reference: ${ url } . ` ;
156+ const url = `https://p5js.org/reference/p5/${ name } ` ;
157+ const message = `${ errorType } "${ name } " on line ${ line } is being redeclared and conflicts with a p5.js ${ errorType . toLowerCase ( ) } . p5.js reference: ${ url } ` ;
177158 return message ;
178159 }
179160
180- // Helper function that checks if a user definition has already been defined
181- // in the p5.js library, either as a constant or as a function.
182- function checkForRedefinition ( name , libValue , line , type ) {
183- try {
184- const userValue = eval ( "name" ) ;
185- if ( libValue !== userValue ) {
186- let message = generateFriendlyError ( type , name , line ) ;
187- console . log ( message ) ;
188- return true ;
189- }
190- } catch ( e ) {
191- // If eval fails, the function hasn't been redefined
192- return false ;
193- }
194- return false ;
195- }
196-
197161 // Checks for constant redefinitions.
198162 for ( let { name, line } of allDefinitions ) {
199163 const libDefinition = constants [ name ] ;
200164 if ( libDefinition !== undefined ) {
201- if ( checkForRedefinition ( name , libDefinition , line , "Constant" ) ) {
202- return true ;
203- }
165+ const message = generateFriendlyError ( 'Constant' , name , line ) ;
166+ console . log ( message ) ;
167+ return true ;
204168 }
205169 }
206170
207171 // The new rules for attaching anything to global are (if true for both of
208172 // the following):
209- // - It is a member of p5.prototype
173+ // - It is a member of p5.prototype
210174 // - Its name does not start with `_`
211175 const globalFunctions = new Set (
212- Object . keys ( p5 . prototype ) . filter ( key => ! key . startsWith ( '_' ) )
176+ Object . getOwnPropertyNames ( p5 . prototype )
177+ . filter ( key => ! key . startsWith ( '_' ) && key !== 'constructor' )
213178 ) ;
214179
215180 for ( let { name, line } of allDefinitions ) {
216181 if ( ! ignoreFunction . includes ( name ) && globalFunctions . has ( name ) ) {
217- const prototypeFunc = p5 . prototype [ name ] ;
218- if ( prototypeFunc && checkForRedefinition ( name , prototypeFunc , line , "Function" ) ) {
219- return true ;
220- }
182+ const message = generateFriendlyError ( 'Function' , name , line ) ;
183+ console . log ( message ) ;
184+ return true ;
221185 }
222186 }
223187
224188 return false ;
225- }
189+ } ,
226190
227- fn . run = async function ( ) {
228- const p5Constructors = await new Promise ( resolve => {
229- if ( document . readyState === 'complete' ) {
230- resolve ( loadP5Constructors ( p5 ) ) ;
231- } else {
232- window . addEventListener ( 'load' , ( ) => resolve ( loadP5Constructors ( p5 ) ) ) ;
233- }
234- } ) ;
191+ /**
192+ * Extracts the user's code from the script fetched. Note that this method
193+ * assumes that the user's code is always the last script element in the
194+ * sketch.
195+ *
196+ * @private
197+ * @method getUserCode
198+ * @returns {Promise<string> } The user's code as a string.
199+ */
200+ getUserCode : async function ( ) {
201+ // TODO: think of a more robust way to get the user's code. Refer to
202+ // https://github.com/processing/p5.js/pull/7293.
203+ const scripts = document . querySelectorAll ( 'script' ) ;
204+ const userCodeScript = scripts [ scripts . length - 1 ] ;
205+ const userCode = await verifierUtils . fetchScript ( userCodeScript ) ;
235206
236- const userCode = await fn . getUserCode ( ) ;
237- const userDefinedVariablesAndFuncs = fn . extractUserDefinedVariablesAndFuncs ( userCode ) ;
207+ return userCode ;
208+ } ,
238209
239- if ( fn . checkForConstsAndFuncs ( userDefinedVariablesAndFuncs , p5Constructors ) ) {
240- return ;
241- }
210+ /**
211+ * @private
212+ */
213+ runFES : async function ( p5 ) {
214+ const userCode = await verifierUtils . getUserCode ( ) ;
215+ const userDefinedVariablesAndFuncs = verifierUtils . extractUserDefinedVariablesAndFuncs ( userCode ) ;
216+
217+ verifierUtils . checkForConstsAndFuncs ( userDefinedVariablesAndFuncs , p5 ) ;
242218 }
219+ } ;
220+
221+ /**
222+ * @for p5
223+ * @requires core
224+ */
225+ function sketchVerifier ( p5 , _fn , lifecycles ) {
226+ lifecycles . presetup = async function ( ) {
227+ if ( ! p5 . disableFriendlyErrors ) {
228+ verifierUtils . runFES ( p5 ) ;
229+ }
230+ } ;
243231}
244232
245233export default sketchVerifier ;
246234
247235if ( typeof p5 !== 'undefined' ) {
248236 sketchVerifier ( p5 , p5 . prototype ) ;
249- }
237+ }
0 commit comments