diff --git a/src/core/friendly_errors/sketch_verifier.js b/src/core/friendly_errors/sketch_verifier.js
index fe88ab9fda..c646360f81 100644
--- a/src/core/friendly_errors/sketch_verifier.js
+++ b/src/core/friendly_errors/sketch_verifier.js
@@ -37,6 +37,16 @@ function sketchVerifier(p5, fn) {
return userCode;
}
+ /**
+ * Extracts the user-defined variables and functions from the user code with
+ * the help of Espree parser.
+ *
+ * @method extractUserDefinedVariablesAndFuncs
+ * @param {string} codeStr - The code to extract variables and functions from.
+ * @returns {Object} An object containing the user's defined variables and functions.
+ * @returns {string[]} [userDefinitions.variables] Array of user-defined variable names.
+ * @returns {strings[]} [userDefinitions.functions] Array of user-defined function names.
+ */
fn.extractUserDefinedVariablesAndFuncs = function (codeStr) {
const userDefinitions = {
variables: [],
@@ -53,23 +63,22 @@ function sketchVerifier(p5, fn) {
});
function traverse(node) {
- switch (node.type) {
+ const { type, declarations, id, init } = node;
+
+ switch (type) {
case 'VariableDeclaration':
- node.declarations.forEach(declaration => {
- if (declaration.id.type === 'Identifier') {
- userDefinitions.variables.push(declaration.id.name);
+ declarations.forEach(({ id, init }) => {
+ if (id.type === 'Identifier') {
+ const category = init && ['ArrowFunctionExpression', 'FunctionExpression'].includes(init.type)
+ ? 'functions'
+ : 'variables';
+ userDefinitions[category].push(id.name);
}
});
break;
case 'FunctionDeclaration':
- if (node.id && node.id.type === 'Identifier') {
- userDefinitions.functions.push(node.id.name);
- }
- break;
- case 'ArrowFunctionExpression':
- case 'FunctionExpression':
- if (node.parent && node.parent.type === 'VariableDeclarator') {
- userDefinitions.functions.push(node.parent.id.name);
+ if (id?.type === 'Identifier') {
+ userDefinitions.functions.push(id.name);
}
break;
}
@@ -87,6 +96,7 @@ function sketchVerifier(p5, fn) {
traverse(ast);
} catch (error) {
+ // TODO: Replace this with a friendly error message.
console.error('Error parsing code:', error);
}
@@ -96,7 +106,8 @@ function sketchVerifier(p5, fn) {
fn.run = async function () {
const userCode = await fn.getUserCode();
const userDefinedVariablesAndFuncs = fn.extractUserDefinedVariablesAndFuncs(userCode);
- console.log(userDefinedVariablesAndFuncs);
+
+ return userDefinedVariablesAndFuncs;
}
}
diff --git a/test/unit/core/sketch_overrides.js b/test/unit/core/sketch_overrides.js
new file mode 100644
index 0000000000..7c183ffd73
--- /dev/null
+++ b/test/unit/core/sketch_overrides.js
@@ -0,0 +1,142 @@
+import sketchVerifier from '../../../src/core/friendly_errors/sketch_verifier.js';
+
+suite('Validate Params', function () {
+ const mockP5 = {
+ _validateParameters: vi.fn()
+ };
+ const mockP5Prototype = {};
+
+ beforeAll(function () {
+ sketchVerifier(mockP5, mockP5Prototype);
+ });
+
+ afterAll(function () {
+ });
+
+ suite('fetchScript()', function () {
+ const url = 'https://www.p5test.com/sketch.js';
+ const code = 'p.createCanvas(200, 200);';
+
+ test('Fetches script content from src', async function () {
+ const mockFetch = vi.fn(() =>
+ Promise.resolve({
+ text: () => Promise.resolve(code)
+ })
+ );
+ vi.stubGlobal('fetch', mockFetch);
+
+ const mockScript = { src: url };
+ const result = await mockP5Prototype.fetchScript(mockScript);
+
+ expect(mockFetch).toHaveBeenCalledWith(url);
+ expect(result).toBe(code);
+
+ vi.unstubAllGlobals();
+ });
+
+ test('Fetches code when there is no src attribute', async function () {
+ const mockScript = { textContent: code };
+ const result = await mockP5Prototype.fetchScript(mockScript);
+
+ expect(result).toBe(code);
+ });
+ });
+
+ suite('getUserCode()', function () {
+ const userCode = "let c = p5.Color(20, 20, 20);";
+
+ test('fetches the last script element', async function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+
+ mockP5Prototype.fetchScript = vi.fn(() => Promise.resolve(userCode));
+
+ const result = await mockP5Prototype.getUserCode();
+
+ expect(mockP5Prototype.fetchScript).toHaveBeenCalledTimes(1);
+ expect(result).toBe(userCode);
+ });
+ });
+
+ suite('extractUserDefinedVariablesAndFuncs()', function () {
+ test('Extracts user-defined variables and functions', function () {
+ const code = `
+ let x = 5;
+ const y = 10;
+ var z = 15;
+ let v1, v2, v3
+ function foo() {}
+ const bar = () => {};
+ const baz = (x) => x * 2;
+ `;
+
+ const result = mockP5Prototype.extractUserDefinedVariablesAndFuncs(code);
+
+ expect(result.variables).toEqual(['x', 'y', 'z', 'v1', 'v2', 'v3']);
+ expect(result.functions).toEqual(['foo', 'bar', 'baz']);
+ });
+
+ // Sketch verifier should ignore the following types of lines:
+ // - Comments (both single line and multi-line)
+ // - Function calls
+ // - Non-declaration code
+ test('Ignores other lines', function () {
+ const code = `
+ // This is a comment
+ let x = 5;
+ /* This is a multi-line comment.
+ * This is a multi-line comment.
+ */
+ const y = 10;
+ console.log("This is a statement");
+ foo(5);
+ p5.Math.random();
+ if (true) {
+ let z = 15;
+ }
+ for (let i = 0; i < 5; i++) {}
+ `;
+
+ const result = mockP5Prototype.extractUserDefinedVariablesAndFuncs(code);
+
+ expect(result.variables).toEqual(['x', 'y', 'z', 'i']);
+ expect(result.functions).toEqual([]);
+ });
+
+ test('Handles parsing errors', function () {
+ const invalidCode = 'let x = ;';
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
+
+ const result = mockP5Prototype.extractUserDefinedVariablesAndFuncs(invalidCode);
+
+ expect(consoleSpy).toHaveBeenCalled();
+ expect(result).toEqual({ variables: [], functions: [] });
+
+ consoleSpy.mockRestore();
+ });
+ });
+
+ suite('run()', function () {
+ test('Returns extracted variables and functions', async function () {
+ const mockScript = `
+ let x = 5;
+ const y = 10;
+ function foo() {}
+ const bar = () => {};
+ `;
+ mockP5Prototype.getUserCode = vi.fn(() => Promise.resolve(mockScript));
+
+ const result = await mockP5Prototype.run();
+
+ expect(mockP5Prototype.getUserCode).toHaveBeenCalledTimes(1);
+
+ expect(result).toEqual({
+ variables: ['x', 'y'],
+ functions: ['foo', 'bar']
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/test/unit/spec.js b/test/unit/spec.js
index 995b152693..8df31317f2 100644
--- a/test/unit/spec.js
+++ b/test/unit/spec.js
@@ -13,6 +13,7 @@ var spec = {
'param_errors',
'preload',
'rendering',
+ 'sketch_overrides',
'structure',
'transform',
'version',