1- import * as acorn from 'acorn' ;
2- import * as walk from 'acorn-walk' ;
1+ import { parse } from 'acorn' ;
2+ import { simple as walk } from 'acorn-walk' ;
3+ import * as constants from '../constants' ;
4+
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 = {
337
4- /**
5- * @for p5
6- * @requires core
7- */
8- function sketchVerifier ( p5 , fn ) {
938 /**
1039 * Fetches the contents of a script element in the user's sketch.
11- *
40+ *
41+ * @private
1242 * @method fetchScript
1343 * @param {HTMLScriptElement } script
1444 * @returns {Promise<string> }
15- */
16- fn . fetchScript = async function ( script ) {
45+ */
46+ fetchScript : async function ( script ) {
1747 if ( script . src ) {
1848 try {
1949 const contents = await fetch ( script . src ) . then ( ( res ) => res . text ( ) ) ;
@@ -26,37 +56,20 @@ function sketchVerifier(p5, fn) {
2656 } else {
2757 return script . textContent ;
2858 }
29- }
30-
31- /**
32- * Extracts the user's code from the script fetched. Note that this method
33- * assumes that the user's code is always the last script element in the
34- * sketch.
35- *
36- * @method getUserCode
37- * @returns {Promise<string> } The user's code as a string.
38- */
39- fn . getUserCode = async function ( ) {
40- // TODO: think of a more robust way to get the user's code. Refer to
41- // https://github.com/processing/p5.js/pull/7293.
42- const scripts = document . querySelectorAll ( 'script' ) ;
43- const userCodeScript = scripts [ scripts . length - 1 ] ;
44- const userCode = await fn . fetchScript ( userCodeScript ) ;
45-
46- return userCode ;
47- }
59+ } ,
4860
4961 /**
5062 * Extracts the user-defined variables and functions from the user code with
5163 * the help of Espree parser.
52- *
64+ *
65+ * @private
5366 * @method extractUserDefinedVariablesAndFuncs
5467 * @param {string } code - The code to extract variables and functions from.
5568 * @returns {Object } An object containing the user's defined variables and functions.
5669 * @returns {Array<{name: string, line: number}> } [userDefinitions.variables] Array of user-defined variable names and their line numbers.
5770 * @returns {Array<{name: string, line: number}> } [userDefinitions.functions] Array of user-defined function names and their line numbers.
5871 */
59- fn . extractUserDefinedVariablesAndFuncs = function ( code ) {
72+ extractUserDefinedVariablesAndFuncs : function ( code ) {
6073 const userDefinitions = {
6174 variables : [ ] ,
6275 functions : [ ]
@@ -66,13 +79,13 @@ function sketchVerifier(p5, fn) {
6679 const lineOffset = - 1 ;
6780
6881 try {
69- const ast = acorn . parse ( code , {
82+ const ast = parse ( code , {
7083 ecmaVersion : 2021 ,
7184 sourceType : 'module' ,
7285 locations : true // This helps us get the line number.
7386 } ) ;
7487
75- walk . simple ( ast , {
88+ walk ( ast , {
7689 VariableDeclarator ( node ) {
7790 if ( node . id . type === 'Identifier' ) {
7891 const category = node . init && [ 'ArrowFunctionExpression' , 'FunctionExpression' ] . includes ( node . init . type )
@@ -109,18 +122,116 @@ function sketchVerifier(p5, fn) {
109122 }
110123
111124 return userDefinitions ;
112- }
125+ } ,
113126
114- fn . run = async function ( ) {
115- const userCode = await fn . getUserCode ( ) ;
116- const userDefinedVariablesAndFuncs = fn . extractUserDefinedVariablesAndFuncs ( userCode ) ;
127+ /**
128+ * Checks user-defined variables and functions for conflicts with p5.js
129+ * constants and global functions.
130+ *
131+ * This function performs two main checks:
132+ * 1. Verifies if any user definition conflicts with p5.js constants.
133+ * 2. Checks if any user definition conflicts with global functions from
134+ * p5.js renderer classes.
135+ *
136+ * If a conflict is found, it reports a friendly error message and halts
137+ * further checking.
138+ *
139+ * @private
140+ * @param {Object } userDefinitions - An object containing user-defined variables and functions.
141+ * @param {Array<{name: string, line: number}> } userDefinitions.variables - Array of user-defined variable names and their line numbers.
142+ * @param {Array<{name: string, line: number}> } userDefinitions.functions - Array of user-defined function names and their line numbers.
143+ * @returns {boolean } - Returns true if a conflict is found, false otherwise.
144+ */
145+ checkForConstsAndFuncs : function ( userDefinitions , p5 ) {
146+ const allDefinitions = [
147+ ...userDefinitions . variables ,
148+ ...userDefinitions . functions
149+ ] ;
117150
118- return userDefinedVariablesAndFuncs ;
151+ // Helper function that generates a friendly error message that contains
152+ // the type of redefinition (constant or function), the name of the
153+ // redefinition, the line number in user's code, and a link to its
154+ // reference on the p5.js website.
155+ function generateFriendlyError ( errorType , name , line ) {
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 } ` ;
158+ return message ;
159+ }
160+
161+ // Checks for constant redefinitions.
162+ for ( let { name, line } of allDefinitions ) {
163+ const libDefinition = constants [ name ] ;
164+ if ( libDefinition !== undefined ) {
165+ const message = generateFriendlyError ( 'Constant' , name , line ) ;
166+ console . log ( message ) ;
167+ return true ;
168+ }
169+ }
170+
171+ // The new rules for attaching anything to global are (if true for both of
172+ // the following):
173+ // - It is a member of p5.prototype
174+ // - Its name does not start with `_`
175+ const globalFunctions = new Set (
176+ Object . getOwnPropertyNames ( p5 . prototype )
177+ . filter ( key => ! key . startsWith ( '_' ) && key !== 'constructor' )
178+ ) ;
179+
180+ for ( let { name, line } of allDefinitions ) {
181+ if ( ! ignoreFunction . includes ( name ) && globalFunctions . has ( name ) ) {
182+ const message = generateFriendlyError ( 'Function' , name , line ) ;
183+ console . log ( message ) ;
184+ return true ;
185+ }
186+ }
187+
188+ return false ;
189+ } ,
190+
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 ) ;
206+
207+ return userCode ;
208+ } ,
209+
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 ) ;
119218 }
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+ } ;
120231}
121232
122233export default sketchVerifier ;
123234
124235if ( typeof p5 !== 'undefined' ) {
125236 sketchVerifier ( p5 , p5 . prototype ) ;
126- }
237+ }
0 commit comments