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