Skip to content

Commit 792888c

Browse files
committed
Implement decorator helper function and have FES use it
1 parent b168dc1 commit 792888c

File tree

2 files changed

+49
-64
lines changed

2 files changed

+49
-64
lines changed

src/core/friendly_errors/param_validator.js

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ function validateParams(p5, fn, lifecycles) {
140140
* @param {String} func - Name of the function. Expect global functions like `sin` and class methods like `p5.Vector.add`
141141
* @returns {z.ZodSchema} Zod schema
142142
*/
143-
fn.generateZodSchemasForFunc = function (func) {
143+
const generateZodSchemasForFunc = function (func) {
144144
const { funcName, funcClass } = extractFuncNameAndClass(func);
145145
let funcInfo = dataDoc[funcClass][funcName];
146146

@@ -308,7 +308,7 @@ function validateParams(p5, fn, lifecycles) {
308308
* @param {Array} args - User input arguments.
309309
* @returns {z.ZodSchema} Closest schema matching the input arguments.
310310
*/
311-
fn.findClosestSchema = function (schema, args) {
311+
const findClosestSchema = function (schema, args) {
312312
if (!(schema instanceof z.ZodUnion)) {
313313
return schema;
314314
}
@@ -389,7 +389,7 @@ function validateParams(p5, fn, lifecycles) {
389389
* @param {String} func - Name of the function. Expect global functions like `sin` and class methods like `p5.Vector.add`
390390
* @returns {String} The friendly error message.
391391
*/
392-
fn.friendlyParamError = function (zodErrorObj, func, args) {
392+
const friendlyParamError = function (zodErrorObj, func, args) {
393393
let message = '🌸 p5.js says: ';
394394
let isVersionError = false;
395395
// The `zodErrorObj` might contain multiple errors of equal importance
@@ -520,7 +520,7 @@ function validateParams(p5, fn, lifecycles) {
520520
* @returns {any} [result.data] - The parsed data if validation was successful.
521521
* @returns {String} [result.error] - The validation error message if validation has failed.
522522
*/
523-
fn.validate = function (func, args) {
523+
const validate = function (func, args) {
524524
if (p5.disableFriendlyErrors) {
525525
return; // skip FES
526526
}
@@ -548,7 +548,7 @@ function validateParams(p5, fn, lifecycles) {
548548

549549
let funcSchemas = schemaRegistry.get(func);
550550
if (!funcSchemas) {
551-
funcSchemas = fn.generateZodSchemasForFunc(func);
551+
funcSchemas = generateZodSchemasForFunc(func);
552552
if (!funcSchemas) return;
553553
schemaRegistry.set(func, funcSchemas);
554554
}
@@ -559,9 +559,9 @@ function validateParams(p5, fn, lifecycles) {
559559
data: funcSchemas.parse(args)
560560
};
561561
} catch (error) {
562-
const closestSchema = fn.findClosestSchema(funcSchemas, args);
562+
const closestSchema = findClosestSchema(funcSchemas, args);
563563
const zodError = closestSchema.safeParse(args).error;
564-
const errorMessage = fn.friendlyParamError(zodError, func, args);
564+
const errorMessage = friendlyParamError(zodError, func, args);
565565

566566
return {
567567
success: false,
@@ -570,25 +570,20 @@ function validateParams(p5, fn, lifecycles) {
570570
}
571571
};
572572

573-
lifecycles.presetup = function(){
574-
loadP5Constructors();
575-
576-
if(
577-
p5.disableParameterValidator !== true &&
578-
p5.disableFriendlyErrors !== true
579-
){
580-
const excludes = ['validate'];
581-
for(const f in this){
582-
if(!excludes.includes(f) && !f.startsWith('_') && typeof this[f] === 'function'){
583-
const copy = this[f];
584-
585-
this[f] = function(...args) {
586-
this.validate(f, args);
587-
return copy.call(this, ...args);
588-
};
573+
p5.decorateHelper(
574+
/^(?!_).+$/,
575+
function(target, name){
576+
return function(...args){
577+
if(!p5.disableFriendlyErrors && !p5.disableParameterValidator) {
578+
validate(name, args);
589579
}
590-
}
580+
return target(...args);
581+
};
591582
}
583+
);
584+
585+
lifecycles.presetup = function(){
586+
loadP5Constructors();
592587
};
593588
}
594589

src/core/main.js

Lines changed: 30 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ class p5 {
7777
// ensure correct reporting of window dimensions
7878
this._updateWindowSize();
7979

80+
// Apply addon defined decorations
81+
for(const [patternArray, decoration] of p5.decorations){
82+
for(const member in p5.prototype){
83+
// Member must be a function
84+
if(typeof p5.prototype[member] !== 'function') continue;
85+
86+
if(!patternArray.some(pattern => {
87+
if(typeof pattern === 'string'){
88+
return pattern === member;
89+
}else if(pattern instanceof RegExp){
90+
return pattern.test(member);
91+
}
92+
})) continue;
93+
94+
const copy = p5.prototype[member].bind(this);
95+
p5.prototype[member] = decoration.call(this, copy, member);
96+
}
97+
}
98+
8099
const bindGlobal = createBindGlobal(this);
81100
// If the user has created a global setup or draw function,
82101
// assume "global" mode and make everything global (i.e. on the window)
@@ -145,22 +164,7 @@ class p5 {
145164
static registerAddon(addon) {
146165
const lifecycles = {};
147166

148-
// Create a proxy for p5.prototype that automatically re-binds globals when properties are added
149-
const prototypeProxy = new Proxy(p5.prototype, {
150-
set(target, property, value) {
151-
const result = Reflect.set(target, property, value);
152-
153-
// If we have a global instance and this is a new property, bind it globally
154-
if (p5.instance && p5.instance._isGlobal && property[0] !== '_') {
155-
const bindGlobal = createBindGlobal(p5.instance);
156-
bindGlobal(property);
157-
}
158-
159-
return result;
160-
}
161-
});
162-
163-
addon(p5, prototypeProxy, lifecycles);
167+
addon(p5, p5.prototype, lifecycles);
164168

165169
const validLifecycles = Object.keys(p5.lifecycleHooks);
166170
for(const name of validLifecycles){
@@ -170,6 +174,13 @@ class p5 {
170174
}
171175
}
172176

177+
static decorations = new Map();
178+
static decorateHelper(pattern, decoration){
179+
let patternArray = pattern;
180+
if(!Array.isArray(pattern)) patternArray = [pattern];
181+
p5.decorations.set(patternArray, decoration);
182+
}
183+
173184
#customActions = {};
174185
_customActions = new Proxy({}, {
175186
get: (target, prop) => {
@@ -393,28 +404,9 @@ class p5 {
393404
}
394405

395406
async _runLifecycleHook(hookName) {
396-
// Create a proxy for the instance that automatically re-binds globals when
397-
// properties are added over the course of the lifecycle.
398-
// Afterward, we skip global binding for efficiency.
399-
let inLifecycle = true;
400-
const instanceProxy = new Proxy(this, {
401-
set(target, property, value) {
402-
const result = Reflect.set(target, property, value);
403-
404-
// If this is a global instance and this is a new property, bind it globally
405-
if (inLifecycle && target._isGlobal && property[0] !== '_') {
406-
const bindGlobal = createBindGlobal(target);
407-
bindGlobal(property);
408-
}
409-
410-
return result;
411-
}
412-
});
413-
414407
await Promise.all(p5.lifecycleHooks[hookName].map(hook => {
415-
return hook.call(instanceProxy);
408+
return hook.call(this);
416409
}));
417-
inLifecycle = false;
418410
}
419411

420412
_initializeInstanceVariables() {
@@ -480,7 +472,7 @@ function createBindGlobal(instance) {
480472
Object.defineProperty(window, property, {
481473
configurable: true,
482474
enumerable: true,
483-
value: boundFunction,
475+
value: boundFunction
484476
});
485477
} else {
486478
Object.defineProperty(window, property, {
@@ -498,7 +490,7 @@ function createBindGlobal(instance) {
498490
Object.defineProperty(window, property, {
499491
configurable: true,
500492
enumerable: true,
501-
value: constantValue,
493+
value: constantValue
502494
});
503495
} else {
504496
Object.defineProperty(window, property, {
@@ -785,8 +777,6 @@ for (const k in constants) {
785777
* </code>
786778
* </div>
787779
*/
788-
p5.disableFriendlyErrors = false;
789-
790780
import transform from './transform';
791781
import structure from './structure';
792782
import environment from './environment';

0 commit comments

Comments
 (0)