react-native/Libraries/EventEmitter/EventValidator.js

140 lines
4.1 KiB
JavaScript

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule EventValidator
* @flow
*/
'use strict';
const copyProperties = require('copyProperties');
/**
* EventValidator is designed to validate event types to make it easier to catch
* common mistakes. It accepts a map of all of the different types of events
* that the emitter can emit. Then, if a user attempts to emit an event that is
* not one of those specified types the emitter will throw an error. Also, it
* provides a relatively simple matcher so that if it thinks that you likely
* mistyped the event name it will suggest what you might have meant to type in
* the error message.
*/
const EventValidator = {
/**
* @param {Object} emitter - The object responsible for emitting the actual
* events
* @param {Object} types - The collection of valid types that will be used to
* check for errors
* @return {Object} A new emitter with event type validation
* @example
* const types = {someEvent: true, anotherEvent: true};
* const emitter = EventValidator.addValidation(emitter, types);
*/
addValidation: function(emitter: Object, types: Object) {
const eventTypes = Object.keys(types);
const emitterWithValidation = Object.create(emitter);
copyProperties(emitterWithValidation, {
emit: function emit(type, a, b, c, d, e, _) {
assertAllowsEventType(type, eventTypes);
return emitter.emit.call(this, type, a, b, c, d, e, _);
}
});
return emitterWithValidation;
}
};
function assertAllowsEventType(type, allowedTypes) {
if (allowedTypes.indexOf(type) === -1) {
throw new TypeError(errorMessageFor(type, allowedTypes));
}
}
function errorMessageFor(type, allowedTypes) {
let message = 'Unknown event type "' + type + '". ';
if (__DEV__) {
message += recommendationFor(type, allowedTypes);
}
message += 'Known event types: ' + allowedTypes.join(', ') + '.';
return message;
}
// Allow for good error messages
if (__DEV__) {
var recommendationFor = function (type, allowedTypes) {
const closestTypeRecommendation = closestTypeFor(type, allowedTypes);
if (isCloseEnough(closestTypeRecommendation, type)) {
return 'Did you mean "' + closestTypeRecommendation.type + '"? ';
} else {
return '';
}
};
var closestTypeFor = function (type, allowedTypes) {
const typeRecommendations = allowedTypes.map(
typeRecommendationFor.bind(this, type)
);
return typeRecommendations.sort(recommendationSort)[0];
};
var typeRecommendationFor = function (type, recomendedType) {
return {
type: recomendedType,
distance: damerauLevenshteinDistance(type, recomendedType)
};
};
var recommendationSort = function (recommendationA, recommendationB) {
if (recommendationA.distance < recommendationB.distance) {
return -1;
} else if (recommendationA.distance > recommendationB.distance) {
return 1;
} else {
return 0;
}
};
var isCloseEnough = function (closestType, actualType) {
return (closestType.distance / actualType.length) < 0.334;
};
var damerauLevenshteinDistance = function (a, b) {
let i, j;
const d = [];
for (i = 0; i <= a.length; i++) {
d[i] = [i];
}
for (j = 1; j <= b.length; j++) {
d[0][j] = j;
}
for (i = 1; i <= a.length; i++) {
for (j = 1; j <= b.length; j++) {
const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
d[i][j] = Math.min(
d[i - 1][j] + 1,
d[i][j - 1] + 1,
d[i - 1][j - 1] + cost
);
if (i > 1 && j > 1 &&
a.charAt(i - 1) === b.charAt(j - 2) &&
a.charAt(i - 2) === b.charAt(j - 1)) {
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
}
}
}
return d[a.length][b.length];
};
}
module.exports = EventValidator;