1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | 'use strict';
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | const EventValidator = {
|
23 | |
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
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 |
|
48 | function assertAllowsEventType(type, allowedTypes) {
|
49 | if (allowedTypes.indexOf(type) === -1) {
|
50 | throw new TypeError(errorMessageFor(type, allowedTypes));
|
51 | }
|
52 | }
|
53 |
|
54 | function 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 |
|
64 | if (__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 |
|
139 | module.exports = EventValidator;
|