1
+ import * as acorn from 'acorn' ;
2
+ import * as walk from 'acorn-walk' ;
3
+
4
+ /**
5
+ * @for p5
6
+ * @requires core
7
+ */
8
+ function sketchVerifier ( p5 , fn ) {
9
+ /**
10
+ * Fetches the contents of a script element in the user's sketch.
11
+ *
12
+ * @method fetchScript
13
+ * @param {HTMLScriptElement } script
14
+ * @returns {Promise<string> }
15
+ */
16
+ fn . fetchScript = async function ( script ) {
17
+ if ( script . src ) {
18
+ try {
19
+ const contents = await fetch ( script . src ) . then ( ( res ) => res . text ( ) ) ;
20
+ return contents ;
21
+ } catch ( error ) {
22
+ // TODO: Handle CORS error here.
23
+ console . error ( 'Error fetching script:' , error ) ;
24
+ return '' ;
25
+ }
26
+ } else {
27
+ return script . textContent ;
28
+ }
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
+ }
48
+
49
+ /**
50
+ * Extracts the user-defined variables and functions from the user code with
51
+ * the help of Espree parser.
52
+ *
53
+ * @method extractUserDefinedVariablesAndFuncs
54
+ * @param {string } code - The code to extract variables and functions from.
55
+ * @returns {Object } An object containing the user's defined variables and functions.
56
+ * @returns {Array<{name: string, line: number}> } [userDefinitions.variables] Array of user-defined variable names and their line numbers.
57
+ * @returns {Array<{name: string, line: number}> } [userDefinitions.functions] Array of user-defined function names and their line numbers.
58
+ */
59
+ fn . extractUserDefinedVariablesAndFuncs = function ( code ) {
60
+ const userDefinitions = {
61
+ variables : [ ] ,
62
+ functions : [ ]
63
+ } ;
64
+ // The line numbers from the parser are consistently off by one, add
65
+ // `lineOffset` here to correct them.
66
+ const lineOffset = - 1 ;
67
+
68
+ try {
69
+ const ast = acorn . parse ( code , {
70
+ ecmaVersion : 2021 ,
71
+ sourceType : 'module' ,
72
+ locations : true // This helps us get the line number.
73
+ } ) ;
74
+
75
+ walk . simple ( ast , {
76
+ VariableDeclarator ( node ) {
77
+ if ( node . id . type === 'Identifier' ) {
78
+ const category = node . init && [ 'ArrowFunctionExpression' , 'FunctionExpression' ] . includes ( node . init . type )
79
+ ? 'functions'
80
+ : 'variables' ;
81
+ userDefinitions [ category ] . push ( {
82
+ name : node . id . name ,
83
+ line : node . loc . start . line + lineOffset
84
+ } ) ;
85
+ }
86
+ } ,
87
+ FunctionDeclaration ( node ) {
88
+ if ( node . id && node . id . type === 'Identifier' ) {
89
+ userDefinitions . functions . push ( {
90
+ name : node . id . name ,
91
+ line : node . loc . start . line + lineOffset
92
+ } ) ;
93
+ }
94
+ } ,
95
+ // We consider class declarations to be a special form of variable
96
+ // declaration.
97
+ ClassDeclaration ( node ) {
98
+ if ( node . id && node . id . type === 'Identifier' ) {
99
+ userDefinitions . variables . push ( {
100
+ name : node . id . name ,
101
+ line : node . loc . start . line + lineOffset
102
+ } ) ;
103
+ }
104
+ }
105
+ } ) ;
106
+ } catch ( error ) {
107
+ // TODO: Replace this with a friendly error message.
108
+ console . error ( 'Error parsing code:' , error ) ;
109
+ }
110
+
111
+ return userDefinitions ;
112
+ }
113
+
114
+ fn . run = async function ( ) {
115
+ const userCode = await fn . getUserCode ( ) ;
116
+ const userDefinedVariablesAndFuncs = fn . extractUserDefinedVariablesAndFuncs ( userCode ) ;
117
+
118
+ return userDefinedVariablesAndFuncs ;
119
+ }
120
+ }
121
+
122
+ export default sketchVerifier ;
123
+
124
+ if ( typeof p5 !== 'undefined' ) {
125
+ sketchVerifier ( p5 , p5 . prototype ) ;
126
+ }
0 commit comments