Skip to content

Commit

Permalink
perf: reduce function calls for shallower stacks (#1022)
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Aug 28, 2020
1 parent dcae108 commit 01da7e1
Show file tree
Hide file tree
Showing 19 changed files with 346 additions and 529 deletions.
8 changes: 5 additions & 3 deletions src/Condition.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class Condition {
let check =
typeof is === 'function'
? is
: (...values) => values.every(value => value === is);
: (...values) => values.every((value) => value === is);

this.fn = function(...args) {
this.fn = function (...args) {
let options = args.pop();
let schema = args.pop();
let branch = check(...args) ? then : otherwise;
Expand All @@ -37,7 +37,9 @@ class Condition {
}

resolve(base, options) {
let values = this.refs.map(ref => ref.getValue(options));
let values = this.refs.map((ref) =>
ref.getValue(options?.value, options?.parent, options?.context),
);

let schema = this.fn.apply(base, values.concat(base, options));

Expand Down
21 changes: 12 additions & 9 deletions src/Reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,16 @@ export default class Reference {
let prefix = this.isContext
? prefixes.context
: this.isValue
? prefixes.value
: '';
? prefixes.value
: '';

this.path = this.key.slice(prefix.length);
this.getter = this.path && getter(this.path, true);
this.map = options.map;
}

getValue(options) {
let result = this.isContext
? options.context
: this.isValue
? options.value
: options.parent;
getValue(value, parent, context) {
let result = this.isContext ? context : this.isValue ? value : parent;

if (this.getter) result = this.getter(result || {});

Expand All @@ -43,8 +39,15 @@ export default class Reference {
return result;
}

/**
*
* @param {*} value
* @param {Object} options
* @param {Object=} options.context
* @param {Object=} options.parent
*/
cast(value, options) {
return this.getValue({ ...options, value });
return this.getValue(value, options?.parent, options?.context);
}

resolve() {
Expand Down
20 changes: 8 additions & 12 deletions src/ValidationError.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import printValue from './util/printValue';

let strReg = /\$\{\s*(\w+)\s*\}/g;

let replace = str => params =>
str.replace(strReg, (_, key) => printValue(params[key]));

export default function ValidationError(errors, value, field, type) {
this.name = 'ValidationError';
this.value = value;
Expand All @@ -14,7 +11,7 @@ export default function ValidationError(errors, value, field, type) {
this.inner = [];

if (errors)
[].concat(errors).forEach(err => {
[].concat(errors).forEach((err) => {
this.errors = this.errors.concat(err.errors || err);

if (err.inner)
Expand All @@ -32,17 +29,16 @@ export default function ValidationError(errors, value, field, type) {
ValidationError.prototype = Object.create(Error.prototype);
ValidationError.prototype.constructor = ValidationError;

ValidationError.isError = function(err) {
ValidationError.isError = function (err) {
return err && err.name === 'ValidationError';
};

ValidationError.formatError = function(message, params) {
if (typeof message === 'string') message = replace(message);
ValidationError.formatError = function (message, params) {
params.path = params.label || params.path || 'this';

let fn = params => {
params.path = params.label || params.path || 'this';
return typeof message === 'function' ? message(params) : message;
};
if (typeof message === 'string')
return message.replace(strReg, (_, key) => printValue(params[key]));
if (typeof message === 'function') return message(params);

return arguments.length === 1 ? fn : fn(params);
return message;
};
17 changes: 8 additions & 9 deletions src/array.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import inherits from './util/inherits';
import isAbsent from './util/isAbsent';
import isSchema from './util/isSchema';
import makePath from './util/makePath';
import printValue from './util/printValue';
import MixedSchema from './mixed';
import { array as locale } from './locale';
import runValidations, { propagateErrors } from './util/runValidations';
import runTests from './util/runTests';

export default ArraySchema;

Expand Down Expand Up @@ -50,7 +49,7 @@ inherits(ArraySchema, MixedSchema, {
const castArray = value.map((v, idx) => {
const castElement = this.innerType.cast(v, {
..._opts,
path: makePath`${_opts.path}[${idx}]`,
path: `${_opts.path || ''}[${idx}]`,
});
if (castElement !== v) {
isChanged = true;
Expand Down Expand Up @@ -92,10 +91,10 @@ inherits(ArraySchema, MixedSchema, {
originalValue = originalValue || value;

// #950 Ensure that sparse array empty slots are validated
let validations = new Array(value.length);
let tests = new Array(value.length);
for (let idx = 0; idx < value.length; idx++) {
let item = value[idx];
let path = makePath`${options.path}[${idx}]`;
let path = `${options.path || ''}[${idx}]`;

// object._validate note for isStrict explanation
let innerOptions = {
Expand All @@ -107,20 +106,20 @@ inherits(ArraySchema, MixedSchema, {
originalValue: originalValue[idx],
};

validations[idx] = (cb) =>
tests[idx] = (_, cb) =>
innerType.validate
? innerType.validate(item, innerOptions, cb)
: cb(null, true);
: cb(null);
}

runValidations(
runTests(
{
sync,
path,
value,
errors,
endEarly,
validations,
tests,
},
callback,
);
Expand Down
81 changes: 43 additions & 38 deletions src/mixed.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import toArray from 'lodash/toArray';

import { mixed as locale } from './locale';
import Condition from './Condition';
import runValidations from './util/runValidations';
import runTests from './util/runTests';
import prependDeep from './util/prependDeep';
import isSchema from './util/isSchema';
import createValidation from './util/createValidation';
Expand Down Expand Up @@ -186,9 +186,21 @@ const proto = (SchemaType.prototype = {

return schema;
},

/**
*
* @param {*} value
* @param {Object} options
* @param {*=} options.parent
* @param {*=} options.context
*/
cast(value, options = {}) {
let resolvedSchema = this.resolve({ ...options, value });
let resolvedSchema = this.resolve({
value,
...options,
// parent: options.parent,
// context: options.context,
});

let result = resolvedSchema._cast(value, options);

if (
Expand Down Expand Up @@ -230,66 +242,59 @@ const proto = (SchemaType.prototype = {
},

_validate(_value, options = {}, cb) {
let value = _value;
let originalValue =
options.originalValue != null ? options.originalValue : _value;

let isStrict = this._option('strict', options);
let endEarly = this._option('abortEarly', options);

let sync = options.sync;
let path = options.path;
let label = this._label;
let {
sync,
path,
from = [],
originalValue = _value,
strict = this._options.strict,
abortEarly = this._options.abortEarly,
} = options;

if (!isStrict) {
let value = _value;
if (!strict) {
this._validating = true;
value = this._cast(value, { assert: false, ...options });
this._validating = false;
}
// value is cast, we can check if it meets type requirements
let validationParams = {
let args = {
value,
path,
schema: this,
options,
label,
originalValue,
schema: this,
label: this._label,
sync,
from,
};

if (options.from) {
validationParams.from = options.from;
}

let initialTests = [];

if (this._typeError)
initialTests.push((cb) => this._typeError(validationParams, cb));

if (this._whitelistError)
initialTests.push((cb) => this._whitelistError(validationParams, cb));

if (this._blacklistError)
initialTests.push((cb) => this._blacklistError(validationParams, cb));
if (this._typeError) initialTests.push(this._typeError);
if (this._whitelistError) initialTests.push(this._whitelistError);
if (this._blacklistError) initialTests.push(this._blacklistError);

return runValidations(
return runTests(
{
validations: initialTests,
endEarly,
args,
value,
path,
sync,
tests: initialTests,
endEarly: abortEarly,
},
(err, value) => {
(err) => {
if (err) return void cb(err);

runValidations(
runTests(
{
tests: this.tests,
args,
path,
sync,
value,
endEarly,
validations: this.tests.map((fn) => (cb) =>
fn(validationParams, cb),
),
endEarly: abortEarly,
},
cb,
);
Expand Down

0 comments on commit 01da7e1

Please sign in to comment.