UNPKG

4.06 kBJavaScriptView Raw
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @format
8 * @flow
9 */
10
11'use strict';
12
13/**
14 * EventValidator is designed to validate event types to make it easier to catch
15 * common mistakes. It accepts a map of all of the different types of events
16 * that the emitter can emit. Then, if a user attempts to emit an event that is
17 * not one of those specified types the emitter will throw an error. Also, it
18 * provides a relatively simple matcher so that if it thinks that you likely
19 * mistyped the event name it will suggest what you might have meant to type in
20 * the error message.
21 */
22const EventValidator = {
23 /**
24 * @param {Object} emitter - The object responsible for emitting the actual
25 * events
26 * @param {Object} types - The collection of valid types that will be used to
27 * check for errors
28 * @return {Object} A new emitter with event type validation
29 * @example
30 * const types = {someEvent: true, anotherEvent: true};
31 * const emitter = EventValidator.addValidation(emitter, types);
32 */
33 addValidation: function(emitter: Object, types: Object) {
34 const eventTypes = Object.keys(types);
35 const emitterWithValidation = Object.create(emitter);
36
37 Object.assign(emitterWithValidation, {
38 emit: function emit(type, a, b, c, d, e, _) {
39 assertAllowsEventType(type, eventTypes);
40 return emitter.emit.call(this, type, a, b, c, d, e, _);
41 },
42 });
43
44 return emitterWithValidation;
45 },
46};
47
48function assertAllowsEventType(type, allowedTypes) {
49 if (allowedTypes.indexOf(type) === -1) {
50 throw new TypeError(errorMessageFor(type, allowedTypes));
51 }
52}
53
54function errorMessageFor(type, allowedTypes) {
55 let message = 'Unknown event type "' + type + '". ';
56 if (__DEV__) {
57 message += recommendationFor(type, allowedTypes);
58 }
59 message += 'Known event types: ' + allowedTypes.join(', ') + '.';
60 return message;
61}
62
63// Allow for good error messages
64if (__DEV__) {
65 var recommendationFor = function(type, allowedTypes) {
66 const closestTypeRecommendation = closestTypeFor(type, allowedTypes);
67 if (isCloseEnough(closestTypeRecommendation, type)) {
68 return 'Did you mean "' + closestTypeRecommendation.type + '"? ';
69 } else {
70 return '';
71 }
72 };
73
74 const closestTypeFor = function(type, allowedTypes) {
75 const typeRecommendations = allowedTypes.map(
76 typeRecommendationFor.bind(this, type),
77 );
78 return typeRecommendations.sort(recommendationSort)[0];
79 };
80
81 const typeRecommendationFor = function(type, recommendedType) {
82 return {
83 type: recommendedType,
84 distance: damerauLevenshteinDistance(type, recommendedType),
85 };
86 };
87
88 const recommendationSort = function(recommendationA, recommendationB) {
89 if (recommendationA.distance < recommendationB.distance) {
90 return -1;
91 } else if (recommendationA.distance > recommendationB.distance) {
92 return 1;
93 } else {
94 return 0;
95 }
96 };
97
98 const isCloseEnough = function(closestType, actualType) {
99 return closestType.distance / actualType.length < 0.334;
100 };
101
102 const damerauLevenshteinDistance = function(a, b) {
103 let i, j;
104 const d = [];
105
106 for (i = 0; i <= a.length; i++) {
107 d[i] = [i];
108 }
109
110 for (j = 1; j <= b.length; j++) {
111 d[0][j] = j;
112 }
113
114 for (i = 1; i <= a.length; i++) {
115 for (j = 1; j <= b.length; j++) {
116 const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
117
118 d[i][j] = Math.min(
119 d[i - 1][j] + 1,
120 d[i][j - 1] + 1,
121 d[i - 1][j - 1] + cost,
122 );
123
124 if (
125 i > 1 &&
126 j > 1 &&
127 a.charAt(i - 1) === b.charAt(j - 2) &&
128 a.charAt(i - 2) === b.charAt(j - 1)
129 ) {
130 d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
131 }
132 }
133 }
134
135 return d[a.length][b.length];
136 };
137}
138
139module.exports = EventValidator;