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 = {
3
37
4
- /**
5
- * @for p5
6
- * @requires core
7
- */
8
- function sketchVerifier ( p5 , fn ) {
9
38
/**
10
39
* Fetches the contents of a script element in the user's sketch.
11
- *
40
+ *
41
+ * @private
12
42
* @method fetchScript
13
43
* @param {HTMLScriptElement } script
14
44
* @returns {Promise<string> }
15
- */
16
- fn . fetchScript = async function ( script ) {
45
+ */
46
+ fetchScript : async function ( script ) {
17
47
if ( script . src ) {
18
48
try {
19
49
const contents = await fetch ( script . src ) . then ( ( res ) => res . text ( ) ) ;
@@ -26,37 +56,20 @@ function sketchVerifier(p5, fn) {
26
56
} else {
27
57
return script . textContent ;
28
58
}
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
+ } ,
48
60
49
61
/**
50
62
* Extracts the user-defined variables and functions from the user code with
51
63
* the help of Espree parser.
52
- *
64
+ *
65
+ * @private
53
66
* @method extractUserDefinedVariablesAndFuncs
54
67
* @param {string } code - The code to extract variables and functions from.
55
68
* @returns {Object } An object containing the user's defined variables and functions.
56
69
* @returns {Array<{name: string, line: number}> } [userDefinitions.variables] Array of user-defined variable names and their line numbers.
57
70
* @returns {Array<{name: string, line: number}> } [userDefinitions.functions] Array of user-defined function names and their line numbers.
58
71
*/
59
- fn . extractUserDefinedVariablesAndFuncs = function ( code ) {
72
+ extractUserDefinedVariablesAndFuncs : function ( code ) {
60
73
const userDefinitions = {
61
74
variables : [ ] ,
62
75
functions : [ ]
@@ -66,13 +79,13 @@ function sketchVerifier(p5, fn) {
66
79
const lineOffset = - 1 ;
67
80
68
81
try {
69
- const ast = acorn . parse ( code , {
82
+ const ast = parse ( code , {
70
83
ecmaVersion : 2021 ,
71
84
sourceType : 'module' ,
72
85
locations : true // This helps us get the line number.
73
86
} ) ;
74
87
75
- walk . simple ( ast , {
88
+ walk ( ast , {
76
89
VariableDeclarator ( node ) {
77
90
if ( node . id . type === 'Identifier' ) {
78
91
const category = node . init && [ 'ArrowFunctionExpression' , 'FunctionExpression' ] . includes ( node . init . type )
@@ -109,18 +122,116 @@ function sketchVerifier(p5, fn) {
109
122
}
110
123
111
124
return userDefinitions ;
112
- }
125
+ } ,
113
126
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
+ ] ;
117
150
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 ) ;
119
218
}
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
+ } ;
120
231
}
121
232
122
233
export default sketchVerifier ;
123
234
124
235
if ( typeof p5 !== 'undefined' ) {
125
236
sketchVerifier ( p5 , p5 . prototype ) ;
126
- }
237
+ }
0 commit comments