Skip to content

Commit ef1b09c

Browse files
authored
Merge pull request #7293 from sproutleaf/dev-2.0
Add new sketch verifier to FES
2 parents 5eaedaa + 07f4b1b commit ef1b09c

File tree

7 files changed

+441
-32
lines changed

7 files changed

+441
-32
lines changed

package-lock.json

Lines changed: 65 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
},
2323
"version": "1.9.4",
2424
"dependencies": {
25+
"acorn": "^8.12.1",
26+
"acorn-walk": "^8.3.4",
2527
"colorjs.io": "^0.5.2",
2628
"file-saver": "^1.3.8",
2729
"gifenc": "^1.0.3",

preview/index.html

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,44 @@
11
<!DOCTYPE html>
22
<html>
3+
34
<head>
45
<title>P5 test</title>
56
<meta http-equiv="pragma" content="no-cache" />
67
<meta http-equiv="cache-control" content="no-cache" />
78
<meta charset="utf-8">
89

910
<style>
10-
body{
11-
margin:0;
12-
overflow: hidden;
13-
}
11+
body {
12+
margin: 0;
13+
overflow: hidden;
14+
}
1415
</style>
1516
</head>
17+
1618
<body>
17-
<script type="module">
18-
import p5 from '../src/app.js';
19-
// import calculation from './src/math/calculation.js';
19+
<script type="module">
20+
import p5 from '../src/app.js';
21+
// import calculation from './src/math/calculation.js';
2022

21-
// p5.registerAddon(calculation);
23+
// p5.registerAddon(calculation);
2224

23-
const sketch = function(p){
24-
p.setup = function(){
25-
p.createCanvas(200, 200);
26-
};
25+
const sketch = function (p) {
26+
p.setup = function () {
27+
p.createCanvas(200, 200);
28+
};
2729

28-
p.draw = function(){
29-
p.background(0, 50, 50);
30-
p.circle(100, 100, 50);
30+
p.draw = function () {
31+
p.background(0, 50, 50);
32+
p.circle(100, 100, 50);
3133

32-
p.fill('white');
33-
p.textSize(30);
34-
p.text('hello', 10, 30);
35-
};
36-
};
34+
p.fill('white');
35+
p.textSize(30);
36+
p.text('hello', 10, 30);
37+
};
38+
};
3739

38-
new p5(sketch);
39-
</script>
40+
new p5(sketch);
41+
</script>
4042
</body>
43+
4144
</html>

src/core/friendly_errors/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import validateParams from './param_validator.js';
2+
import sketchVerifier from './sketch_verifier.js';
23

34
export default function (p5) {
45
p5.registerAddon(validateParams);
6+
p5.registerAddon(sketchVerifier);
57
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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

Comments
 (0)